
-----------------------------------------------------------------------------------
-- Veaf scripts 5.75.1;2025.01.04.11.18.12
-----------------------------------------------------------------------------------


------------------ START script veaf.lua  ------------------

------------------------------------------------------------------
-- VEAF root script for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Contains all the constants and utility functions required by the other VEAF script libraries
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veaf = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the root VEAF constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veaf.Id = "VEAF"

--- Version.
veaf.Version = "1.53.0"

--- Development version ?
veaf.Development = false
veaf.SecurityDisabled = false

-- trace level, specific to this module
--veaf.LogLevel = "debug"
--veaf.LogLevel = "trace"
--veaf.ForcedLogLevel = "trace"

-- log level, limiting all the modules
veaf.BaseLogLevel = 5 --trace

veaf.DEFAULT_GROUND_SPEED_KPH = 30
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veaf.config = {}
veaf.triggerZones = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------
veaf.theatreName = 
{
    Caucasus = "Caucasus",
    Nevada = "Nevada",
    Normandy = "Normandy",
    PersianGulf = "PersianGulf",
    TheChannel = "TheChannel",
    Syria = "Syria",
    MarianaIslands = "MarianaIslands",
    Falklands = "Falklands",
    Sinai = "SinaiMap",
    Kola = "Kola",
    Afghanistan = "Afghanistan",
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veaf.EVENTMETA = {
    [world.event.S_EVENT_SHOT] = {
        Order = 1,
        Side = "I",
        Event = "OnEventShot",
        Text = "S_EVENT_SHOT"
    },
    [world.event.S_EVENT_HIT] = {
        Order = 1,
        Side = "T",
        Event = "OnEventHit",
        Text = "S_EVENT_HIT"
    },
    [world.event.S_EVENT_TAKEOFF] = {
        Order = 1,
        Side = "I",
        Event = "OnEventTakeoff",
        Text = "S_EVENT_TAKEOFF"
    },
    [world.event.S_EVENT_LAND] = {
        Order = 1,
        Side = "I",
        Event = "OnEventLand",
        Text = "S_EVENT_LAND"
    },
    [world.event.S_EVENT_CRASH] = {
        Order = -1,
        Side = "I",
        Event = "OnEventCrash",
        Text = "S_EVENT_CRASH"
    },
    [world.event.S_EVENT_EJECTION] = {
        Order = 1,
        Side = "I",
        Event = "OnEventEjection",
        Text = "S_EVENT_EJECTION"
    },
    [world.event.S_EVENT_REFUELING] = {
        Order = 1,
        Side = "I",
        Event = "OnEventRefueling",
        Text = "S_EVENT_REFUELING"
    },
    [world.event.S_EVENT_DEAD] = {
        Order = -1,
        Side = "I",
        Event = "OnEventDead",
        Text = "S_EVENT_DEAD"
    },
    [world.event.S_EVENT_PILOT_DEAD] = {
        Order = 1,
        Side = "I",
        Event = "OnEventPilotDead",
        Text = "S_EVENT_PILOT_DEAD"
    },
    [world.event.S_EVENT_BASE_CAPTURED] = {
        Order = 1,
        Side = "I",
        Event = "OnEventBaseCaptured",
        Text = "S_EVENT_BASE_CAPTURED"
    },
    [world.event.S_EVENT_MISSION_START] = {
        Order = 1,
        Side = "N",
        Event = "OnEventMissionStart",
        Text = "S_EVENT_MISSION_START"
    },
    [world.event.S_EVENT_MISSION_END] = {
        Order = 1,
        Side = "N",
        Event = "OnEventMissionEnd",
        Text = "S_EVENT_MISSION_END"
    },
    [world.event.S_EVENT_TOOK_CONTROL] = {
        Order = 1,
        Side = "N",
        Event = "OnEventTookControl",
        Text = "S_EVENT_TOOK_CONTROL"
    },
    [world.event.S_EVENT_REFUELING_STOP] = {
        Order = 1,
        Side = "I",
        Event = "OnEventRefuelingStop",
        Text = "S_EVENT_REFUELING_STOP"
    },
    [world.event.S_EVENT_BIRTH] = {
        Order = 1,
        Side = "I",
        Event = "OnEventBirth",
        Text = "S_EVENT_BIRTH"
    },
    [world.event.S_EVENT_HUMAN_FAILURE] = {
        Order = 1,
        Side = "I",
        Event = "OnEventHumanFailure",
        Text = "S_EVENT_HUMAN_FAILURE"
    },
    [world.event.S_EVENT_ENGINE_STARTUP] = {
        Order = 1,
        Side = "I",
        Event = "OnEventEngineStartup",
        Text = "S_EVENT_ENGINE_STARTUP"
    },
    [world.event.S_EVENT_ENGINE_SHUTDOWN] = {
        Order = 1,
        Side = "I",
        Event = "OnEventEngineShutdown",
        Text = "S_EVENT_ENGINE_SHUTDOWN"
    },
    [world.event.S_EVENT_PLAYER_ENTER_UNIT] = {
        Order = 1,
        Side = "I",
        Event = "OnEventPlayerEnterUnit",
        Text = "S_EVENT_PLAYER_ENTER_UNIT"
    },
    [world.event.S_EVENT_PLAYER_LEAVE_UNIT] = {
        Order = -1,
        Side = "I",
        Event = "OnEventPlayerLeaveUnit",
        Text = "S_EVENT_PLAYER_LEAVE_UNIT"
    },
    [world.event.S_EVENT_PLAYER_COMMENT] = {
        Order = 1,
        Side = "I",
        Event = "OnEventPlayerComment",
        Text = "S_EVENT_PLAYER_COMMENT"
    },
    [world.event.S_EVENT_SHOOTING_START] = {
        Order = 1,
        Side = "I",
        Event = "OnEventShootingStart",
        Text = "S_EVENT_SHOOTING_START"
    },
    [world.event.S_EVENT_SHOOTING_END] = {
        Order = 1,
        Side = "I",
        Event = "OnEventShootingEnd",
        Text = "S_EVENT_SHOOTING_END"
    },
    [world.event.S_EVENT_MARK_ADDED] = {
        Order = 1,
        Side = "I",
        Event = "OnEventMarkAdded",
        Text = "S_EVENT_MARK_ADDED"
    },
    [world.event.S_EVENT_MARK_CHANGE] = {
        Order = 1,
        Side = "I",
        Event = "OnEventMarkChange",
        Text = "S_EVENT_MARK_CHANGE"
    },
    [world.event.S_EVENT_MARK_REMOVED] = {
        Order = 1,
        Side = "I",
        Event = "OnEventMarkRemoved",
        Text = "S_EVENT_MARK_REMOVED"
    }
}

--[[ json.lua

Used from https://gist.github.com/tylerneylon/59f4bcf316be525b30ab with authorization

A compact pure-Lua JSON library.
The main functions are: json.stringify, json.parse.
## json.stringify:
This expects the following to be true of any tables being encoded:
 * They only have string or number keys. Number keys must be represented as
   strings in json; this is part of the json spec.
 * They are not recursive. Such a structure cannot be specified in json.
A Lua table is considered to be an array if and only if its set of keys is a
consecutive sequence of positive integers starting at 1. Arrays are encoded like
so: `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a json
object, encoded like so: `{"key1": 2, "key2": false}`.
Because the Lua nil value cannot be a key, and as a table value is considerd
equivalent to a missing key, there is no way to express the json "null" value in
a Lua table. The only way this will output "null" is if your entire input obj is
nil itself.
An empty Lua table, {}, could be considered either a json object or array -
it's an ambiguous edge case. We choose to treat this as an object as it is the
more general type.
To be clear, none of the above considerations is a limitation of this code.
Rather, it is what we get when we completely observe the json specification for
as arbitrary a Lua object as json is capable of expressing.
## json.parse:
This function parses json, with the exception that it does not pay attention to
\u-escaped unicode code points in strings.
It is difficult for Lua to return null as a value. In order to prevent the loss
of keys with a null value in a json string, this function uses the one-off
table value json.null (which is just an empty table) to indicate null values.
This way you can check if a value is null with the conditional
`val == json.null`.
If you have control over the data and are using Lua, I would recommend just
avoiding null values in your data to begin with.
--]]


veaf.json = {}


-- Internal functions.

local function kind_of(obj)
  if type(obj) ~= 'table' then return type(obj) end
  local i = 1
  for _ in pairs(obj) do
    if obj[i] ~= nil then i = i + 1 else return 'table' end
  end
  if i == 1 then return 'table' else return 'array' end
end

local function escape_str(s)
  local in_char  = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
  local out_char = {'\\', '"', '/',  'b',  'f',  'n',  'r',  't'}
  for i, c in ipairs(in_char) do
    s = s:gsub(c, '\\' .. out_char[i])
  end
  return s
end

-- Returns pos, did_find; there are two cases:
-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
-- 2. Delimiter not found: pos = pos after leading space;     did_find = false.
-- This throws an error if err_if_missing is true and the delim is not found.
local function skip_delim(str, pos, delim, err_if_missing)
  pos = pos + #str:match('^%s*', pos)
  if str:sub(pos, pos) ~= delim then
    if err_if_missing then
      error('Expected ' .. delim .. ' near position ' .. pos)
    end
    return pos, false
  end
  return pos + 1, true
end

-- Expects the given pos to be the first character after the opening quote.
-- Returns val, pos; the returned pos is after the closing quote character.
local function parse_str_val(str, pos, val)
  val = val or ''
  local early_end_error = 'End of input found while parsing string.'
  if pos > #str then error(early_end_error) end
  local c = str:sub(pos, pos)
  if c == '"'  then return val, pos + 1 end
  if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
  -- We must have a \ character.
  local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
  local nextc = str:sub(pos + 1, pos + 1)
  if not nextc then error(early_end_error) end
  return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
end

-- Returns val, pos; the returned pos is after the number's final character.
local function parse_num_val(str, pos)
  local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
  local val = tonumber(num_str)
  if not val then error('Error parsing number at position ' .. pos .. '.') end
  return val, pos + #num_str
end


-- Public values and functions.

function veaf.json.stringify(obj, as_key)
  local s = {}  -- We'll build the string as an array of strings to be concatenated.
  local kind = kind_of(obj)  -- This is 'array' if it's an array or type(obj) otherwise.
  if kind == 'array' then
    if as_key then error('Can\'t encode array as key.') end
    s[#s + 1] = '['
    for i, val in ipairs(obj) do
      if i > 1 then s[#s + 1] = ', ' end
      s[#s + 1] = veaf.json.stringify(val)
    end
    s[#s + 1] = ']'
  elseif kind == 'table' then
    if as_key then error('Can\'t encode table as key.') end
    s[#s + 1] = '{'
    for k, v in pairs(obj) do
      if #s > 1 then s[#s + 1] = ', ' end
      s[#s + 1] = veaf.json.stringify(k, true)
      s[#s + 1] = ':'
      s[#s + 1] = veaf.json.stringify(v)
    end
    s[#s + 1] = '}'
  elseif kind == 'string' then
    return '"' .. escape_str(obj) .. '"'
  elseif kind == 'number' then
    if as_key then return '"' .. tostring(obj) .. '"' end
    return tostring(obj)
  elseif kind == 'boolean' then
    return tostring(obj)
  elseif kind == 'nil' then
    return 'null'
  else
    return '"Unjsonifiable type: ' .. kind .. '."'
    --error('Unjsonifiable type: ' .. kind .. '.')
  end
  return table.concat(s)
end

veaf.json.null = {}  -- This is a one-off table to represent the null value.

function veaf.json.parse(str, pos, end_delim)
  pos = pos or 1
  if pos > #str then error('Reached unexpected end of input.') end
  local pos = pos + #str:match('^%s*', pos)  -- Skip whitespace.
  local first = str:sub(pos, pos)
  if first == '{' then  -- Parse an object.
    local obj, key, delim_found = {}, true, true
    pos = pos + 1
    while true do
      -- not my code !
      ---@diagnostic disable-next-line: cast-local-type
      key, pos = veaf.json.parse(str, pos, '}')
      if key == nil then return obj, pos end
      if not delim_found then error('Comma missing between object items.') end
      pos = skip_delim(str, pos, ':', true)  -- true -> error if missing.
      -- not my code !
      ---@diagnostic disable-next-line: need-check-nil
      obj[key], pos = veaf.json.parse(str, pos)
      pos, delim_found = skip_delim(str, pos, ',')
    end
  elseif first == '[' then  -- Parse an array.
    local arr, val, delim_found = {}, true, true
    pos = pos + 1
    while true do
      -- not my code !
      ---@diagnostic disable-next-line: cast-local-type
      val, pos = veaf.json.parse(str, pos, ']')
      if val == nil then return arr, pos end
      if not delim_found then error('Comma missing between array items.') end
      arr[#arr + 1] = val
      pos, delim_found = skip_delim(str, pos, ',')
    end
  elseif first == '"' then  -- Parse a string.
    return parse_str_val(str, pos + 1)
  elseif first == '-' or first:match('%d') then  -- Parse a number.
    return parse_num_val(str, pos)
  elseif first == end_delim then  -- End of an object or array.
    return nil, pos + 1
  else  -- Parse true, false, or null.
    local literals = {['true'] = true, ['false'] = false, ['null'] = veaf.json.null}
    for lit_str, lit_val in pairs(literals) do
      local lit_end = pos + #lit_str - 1
      if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
    end
    local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
    error('Invalid json syntax starting at ' .. pos_info_str)
  end
end

local escapeChars = nil
---Escapes a string so it can no longer be a pattern (regex)
---@param stringToEscape string
---@return string
function veaf.escapeRegex(stringToEscape)
    local regexCharsToEscape = "^$()%.[]*+-?"
    if not escapeChars then
        escapeChars = {}
        for i = 1, string.len(regexCharsToEscape) do
            local char = string.sub(regexCharsToEscape,i,i)
            escapeChars[char] = true
        end
    end

    local result = ""
    if stringToEscape then
        for i = 1, string.len(stringToEscape) do
            local char = string.sub(stringToEscape,i,i)
            if escapeChars[char] then
                result = result .. "%"
            end
            result = result .. char
        end
    end
    return result
end

--- efficiently remove elements from a table
--- credit : Mitch McMabers (https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating)
function veaf.arrayRemoveWhen(t, fnKeep)
    local pristine = true
    local j, n = 1, #t;
    for i=1,n do
        if (fnKeep(t, i, j)) then
            if (i ~= j) then
                -- Keep i's value, move it to j's pos.
                t[j] = t[i];
                t[i] = nil;
            else
                -- Keep i's value, already at j's pos.
            end
            j = j + 1;
        else
            t[i] = nil;
            pristine = false
        end
    end
    return not pristine;
end

function veaf.vecToString(vec)
    local result = ""
    if vec.x then
        result = result .. string.format(" x=%.1f", vec.x)
    end
    if vec.y then
        result = result .. string.format(" y=%.1f", vec.y)
    end
    if vec.z then
        result = result .. string.format(" z=%.1f", vec.z)
    end
    return result
end

function veaf.discoverMetadata(o)
    local text = ""
    for key,value in pairs(getmetatable(o)) do
       text = text .. " - ".. key.."\n";
    end
	return text
end

function veaf.serialize(name, value, level)
    -- mostly based on slMod serializer 

    local function _basicSerialize(s)
      if s == nil then
        return "\"\""
      else
        if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'table') or (type(s) == 'userdata') ) then
          return tostring(s)
        elseif type(s) == 'string' then
          return string.format('%q', s)
        end
      end
    end

    -----Based on ED's serialize_simple2
    local basicSerialize = function(o)
        if type(o) == "number" then
            return tostring(o)
        elseif type(o) == "boolean" then
            return tostring(o)
        else -- assume it is a string
            return _basicSerialize(o)
        end
    end

    local function _sortNumberOrCaseInsensitive(a,b)
        if type(a) == "string" or type(b) == "string" then
          return string.lower(a) < string.lower(b)
        else
          return a < b
        end
      end

    local serialize_to_t = function(name, value, level)
        ----Based on ED's serialize_simple2

        local var_str_tbl = {}
        if level == nil then
            level = ""
        end
        if level ~= "" then
            level = level .. "  "
        end

        table.insert(var_str_tbl, level .. name .. " = ")

        if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then
            table.insert(var_str_tbl, basicSerialize(value) .. ",\n")
        elseif type(value) == "table" then
            table.insert(var_str_tbl, "{\n")
            local tkeys = {}
            -- populate the table that holds the keys
            for k in pairs(value) do table.insert(tkeys, k) end
            -- sort the keys
            table.sort(tkeys, _sortNumberOrCaseInsensitive)
            -- use the keys to retrieve the values in the sorted order
            for _, k in ipairs(tkeys) do  -- serialize its fields
              local v = value[k]
                local key
                if type(k) == "number" then
                    key = string.format("[%s]", k)
                else
                    key = string.format("[%q]", k)
                end

                table.insert(var_str_tbl, veaf.serialize(key, v, level .. "  "))
            end
            if level == "" then
                table.insert(var_str_tbl, level .. "} -- end of " .. name .. "\n")
            else
                table.insert(var_str_tbl, level .. "}, -- end of " .. name .. "\n")
            end
        else
            veaf.loggers.get(veaf.Id):error("Cannot serialize a " .. type(value))
        end
        return var_str_tbl
    end

    local t_str = serialize_to_t(name, value, level)

    return table.concat(t_str)
end

function veaf.ifnn(o, field)
    if o then
        if o[field] then
            if type(o[field]) == "function" then
                local sta, res = pcall(o[field],o)
                if sta then
                    return res
                else
                    return nil
                end
            else
                return o[field]
            end
        end
    else
        return nil
    end
end

function veaf.ifnns(o, fields)
    local result = nil
    if o then
        result = {}
        if type(fields) ~= "table" then
            local field = fields
            fields = { field }
        end
        for _, field in pairs(fields) do
            if o[field] then
                if type(o[field]) == "function" then
                    local sta, res = pcall(o[field],o)
                    if sta then
                        result[field] = res
                    else
                        result[field] = nil
                    end
                else
                    result[field] = o[field]
                end
            end
        end
    end
    return result
end

function veaf.isNullOrEmpty(s)
    return (s == nil or (type(s) == "string" and s == ""))
end

function veaf.tableContains(table, element)
    if (table == nil or element == nil) then
        return false
    end
    
    for _, e in pairs(table) do
        if (e == element) then 
            return true 
        end
    end
    return false
end

function veaf.p(o, level, skip, includeMeta, dontRecurse)
    if o and type(o) == "table" and (o.x and o.z and o.y and #o == 3) then
        return string.format("{x=%s, z=%s, y=%s}", veaf.p(o.x), veaf.p(o.z), veaf.p(o.y))
    elseif o and type(o) == "table" and (o.x and o.y and #o == 2)  then
        return string.format("{x=%s, y=%s}", veaf.p(o.x), veaf.p(o.y))
    end
    local skip = skip
    if skip and type(skip)=="table" then
        for _, value in ipairs(skip) do
            skip[value]=true
        end
    end
    return veaf._p(o, level, skip, includeMeta, dontRecurse)
end

function veaf._p(o, level, skip, includeMeta, dontRecurse)
    local MAX_LEVEL = 20
    if level == nil then level = 0 end
    if level > MAX_LEVEL then
        veaf.loggers.get(veaf.Id):error("max depth reached in veaf.p : "..tostring(MAX_LEVEL))
        return ""
    end
    local text = ""
    if o == nil then
        text = "[nil]"
    elseif (type(o) == "table") and not(dontRecurse) then
        text = "\n"
        local keys = {}
        local values = {}
        for key, value in pairs(o) do
            local sKey = tostring(key)
            table.insert(keys, sKey)
            values[sKey] = value
        end
        table.sort(keys)
        for _, key in pairs(keys) do
            local value = values[key]
            for i=0, level do
                text = text .. " "
            end
            if not (skip and skip[key]) then
                text = text .. ".".. key.."="..veaf.p(value, level+1, skip, includeMeta, dontRecurse) .. "\n"
            else
                text = text .. ".".. key.."= [[SKIPPED]]\n"
            end
        end
        if includeMeta then
            local metatable = getmetatable(o)
            if metatable then
                text = "\n"
                local keys = {}
                local values = {}
                for key, value in pairs(metatable) do
                    local sKey = tostring(key)
                    table.insert(keys, sKey)
                    values[sKey] = value
                end
                table.sort(keys)
                for _, key in pairs(keys) do
                    local value = values[key]
                    for i=0, level do
                        text = text .. " "
                    end
                    if not (skip and skip[key]) then
                        if key == "getID" then
                            value = o:getID()
                        elseif key == "getName" then
                            value = o:getName()
                        elseif key == "getTypeName" then
                            value = o:getTypeName()
                        elseif key == "getDesc" then
                            value = o:getDesc()
                        end
                        text = text .. "[META].".. key.."="..veaf.p(value, level+1, skip, includeMeta, true) .. "\n"
                    else
                        text = text .. "[META].".. key.."= [[SKIPPED]]\n"
                    end
                end
            end
        end
    elseif (type(o) == "function") then
        text = "[function]"
    elseif (type(o) == "boolean") then
        if o == true then
            text = "[true]"
        else
            text = "[false]"
        end
    else
        text = tostring(o)
    end
    return text
end

function veaf.length(T)
    local count = 0
    if T ~= nil then
        for _ in pairs(T) do count = count + 1 end
    end
    return count
end

--- Simple round
function veaf.round(num, numDecimalPlaces)
  local mult = 10^(numDecimalPlaces or 0)
  return math.floor(num * mult + 0.5) / mult
end

--- shuffle a table elements around
function veaf.shuffle(tbl)
    for i = #tbl, 2, -1 do
      local j = math.random(i)
      tbl[i], tbl[j] = tbl[j], tbl[i]
    end
    return tbl
end

--- Return the height of the land at the coordinate.
function veaf.getLandHeight(vec3)
-- LOGGING DISABLED WHEN COMPILING(string.format("getLandHeight: vec3  x=%.1f y=%.1f, z=%.1f", vec3.x, vec3.y, vec3.z))
    local vec2 = {x = vec3.x, y = vec3.z}
-- LOGGING DISABLED WHEN COMPILING(string.format("getLandHeight: vec2  x=%.1f z=%.1f", vec3.x, vec3.z))
    -- We add 1 m "safety margin" because data from getlandheight gives the surface and wind at or below the surface is zero!
    local height = math.floor(land.getHeight(vec2) + 1)
-- LOGGING DISABLED WHEN COMPILING(string.format("getLandHeight: result  height=%.1f",height))
    return height
end

function veaf.invertHeading(heading)
-- LOGGING DISABLED WHEN COMPILING(string.format("invertHeading(%s)", veaf.p(heading)))
    local result = heading - 180
    if result <= 0 then
        result = result + 360
    end
    return result
end

-- get a LL position based on a string 
-- can be UTM (U38TMP334456 or u37TMP4351)
-- can be LL with either : or - as a separator, and either DMS, DM decimal, or D decimal (N42:23:45E044-12.5 or N42.3345E044-12.5)
function veaf.computeLLFromString(value)
    local function _computeLLValueFromString(value)
        local result = -1
        if value:find(":") or value:find("-") then
            -- convert in arc-seconds
            local values = veaf.splitWithPattern(value, "[:-]+")
            local weights = {3600, 60, 1}
            for _, element in pairs(values) do
-- LOGGING DISABLED WHEN COMPILING(string.format("element=%s",veaf.p(element)))
                local weight = table.remove(weights, 1)
                local elementInArcSec = tonumber(element)*weight
                result = result + elementInArcSec
            end
            return result / 3600
        else
            -- decimals
            return tonumber(value)
        end
    end

    if value then
        local _value = value:lower()
        local _firstChar = _value:sub(1,1)
        if _firstChar == "u" then
            -- UTM coordinates
            local _zone, _digraph, _digits = _value:match("u(%d%d[a-z])([a-z][a-z])(%d+)")
-- LOGGING DISABLED WHEN COMPILING(string.format("_zone=%s",veaf.p(_zone)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_digraph=%s",veaf.p(_digraph)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_digits=%s",veaf.p(_digits)))
            if _zone and _digraph and _digits then
                local _nDigits = #_digits
                local _northingString = _digits:sub(_nDigits/2+1)
                local _northing = tonumber(_northingString)
-- LOGGING DISABLED WHEN COMPILING(string.format("_northing=%s",veaf.p(_northing)))
                if #_northingString == 1 then
                    _northing = _northing * 10000
                elseif #_northingString == 2 then
                    _northing = _northing * 1000
                elseif #_northingString == 3 then
                    _northing = _northing * 100
                elseif #_northingString == 4 then
                    _northing = _northing * 10
                end

                local _eastingString = _digits:sub(1, _nDigits/2)
                local _easting = tonumber(_eastingString)
-- LOGGING DISABLED WHEN COMPILING(string.format("_easting=%s",veaf.p(_easting)))
                if #_eastingString == 1 then
                    _easting = _easting * 10000
                elseif #_eastingString == 2 then
                    _easting = _easting * 1000
                elseif #_eastingString == 3 then
                    _easting = _easting * 100
                elseif #_eastingString == 4 then
                    _easting = _easting * 10
                end

                local _utm= { UTMZone = _zone:upper(), MGRSDigraph = _digraph:upper(), Easting = _easting, Northing = _northing }
-- LOGGING DISABLED WHEN COMPILING(string.format("_utm=%s",veaf.p(_utm)))
                return coord.MGRStoLL(_utm)
            end
        elseif _firstChar == "n" or _firstChar == "s" or _firstChar == "e" or _firstChar == "w" then
            -- LL coordinates
            local _signLat, _digitsLat, _signLon, _digitsLon = _value:match([[([news])([%d:\.-]+)([news])([%d:\.-]+)]])
            local _multLat = 1
            if _signLat == "s" then
                _multLat = -1
            end
            local _multLon = 1
            if _signLon == "w" then
                _multLon = -1
            end
            local _lat = _multLat * _computeLLValueFromString(_digitsLat)
            local _lon = _multLon * _computeLLValueFromString(_digitsLon)
            return _lat, _lon
        end
    end
    -- unrecognized format
    return nil
end

function veaf.findDcsAirbase(name)
    local dcsAirbase = Airbase.getByName(name)
    if (dcsAirbase) then
        return dcsAirbase
    end

    -- Remove "AIRBASE " prefix if it exists (case insensitive)
    name = name:gsub("^[Aa][Ii][Rr][Bb][Aa][Ss][Ee]%s+", "")

    -- Helper function to normalize strings
    local function normalize(s)
        -- Convert to lowercase
        s = s:lower()
        -- Remove spaces and punctuation
        s = s:gsub("[%s%p]", "")
        return s
    end
        
    name = normalize(name)
    
    local airBases = world.getAirbases()
    for i = 1, #airBases do
        dcsAirbase = airBases[i]
        local sAirbaseName = dcsAirbase:getName()
        -- Normalize each list item
        sAirbaseName = normalize(sAirbaseName)
        
        -- Compare normalized strings
        if (sAirbaseName == name) then
            return dcsAirbase
        end
    end
        
    return nil
end

function veaf.silenceAtcOnAllAirbases()
    local bases = world.getAirbases()
    for _, base in pairs(bases) do
        if base:getDesc() then
            if base:getDesc().category == Airbase.Category.AIRDROME then
                veaf.loggers.get(veaf.Id):info("silencing ATC at base %s", veaf.p(base:getDesc().displayName))
                base:setRadioSilentMode(true)
            end
        end
    end
end

--- Return a point at the same coordinates, but on the surface
function veaf.placePointOnLand(vec3)
    -- convert a vec2 to a vec3
    if not vec3.z then
        vec3.z = vec3.y
        vec3.y = 0
    end

    if not vec3.y then
        vec3.y = 0
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("getLandHeight: vec3  x=%.1f y=%.1f, z=%.1f", vec3.x, vec3.y, vec3.z))
    local height = veaf.getLandHeight(vec3)
-- LOGGING DISABLED WHEN COMPILING(string.format("getLandHeight: result  height=%.1f",height))
    local result={x=vec3.x, y=height, z=vec3.z}
-- LOGGING DISABLED WHEN COMPILING(string.format("placePointOnLand: result  x=%.1f y=%.1f, z=%.1f", result.x, result.y, result.z))
    return result
end

--- Trim a string
function veaf.trim(s)
    local a = s:match('^%s*()')
    local b = s:match('()%s*$', a)
    return s:sub(a,b-1)
end

--- Split string. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua
function veaf.splitWithPattern(str, pat)
    local t = {}  -- NOTE: use {n = 0} in Lua-5.0
    local fpat = "(.-)" .. pat
    local last_end = 1
    local s, e, cap = str:find(fpat, 1)
    while s do
        if s ~= 1 or cap ~= "" then
            table.insert(t, cap)
        end
        last_end = e+1
        s, e, cap = str:find(fpat, last_end)
    end
    if last_end <= #str then
        cap = str:sub(last_end)
        table.insert(t, cap)
    end
    return t
end

function veaf.split(str, sep)
    local result = {}
    local regex = ("([^%s]+)"):format(sep)
    for each in str:gmatch(regex) do
        table.insert(result, each)
    end
    return result
end

--- Break string around a separator
function veaf.breakString(str, sep)
    local regex = ("^([^%s]+)%s(.*)$"):format(sep, sep)
    local a, b = str:match(regex)
    if not a then a = str end
    local result = {a, b}
    return result
end

--- Get the average center of a group position (average point of all units position)
function veaf.getAveragePosition(group)
    if type(group) == "string" then
        group = Group.getByName(group)
    end

    local count

	local totalPosition = {x = 0,y = 0,z = 0}
	if group then
		local units = Group.getUnits(group)
		for count = 1,#units do
			if units[count] then
				totalPosition = mist.vec.add(totalPosition,Unit.getPosition(units[count]).p)
			end
		end
		if #units > 0 then
			return mist.vec.scalar_mult(totalPosition,1/#units)
		else
			return nil
		end
	else
		return nil
	end
end

function veaf.emptyFunction()
end

function veaf.getMagneticDeclination()
    local nDeclination = 0
    local sTheatre = string.lower(env.mission.theatre)

    if (sTheatre == "caucasus") then
        nDeclination = 6
    elseif (sTheatre == "persiangulf") then
        nDeclination = 2
    elseif (sTheatre == "nevada") then
        nDeclination = 12
    elseif (sTheatre == "normandy") then
        nDeclination = -10
    elseif (sTheatre == "thechannel") then
        nDeclination = -10
    elseif (sTheatre == "syria") then
        nDeclination = 5
    elseif (sTheatre == "marianaislands") then
        nDeclination = 2
    elseif (sTheatre == "falklands") then
        nDeclination = 12
    elseif (sTheatre == "sinaimap") then
        nDeclination = 4.8
    elseif (sTheatre == "kola") then
        nDeclination = 15
    elseif (sTheatre == "afghanistan") then
        nDeclination = 3
    end
  
    return nDeclination
end

--- Returns the wind direction (from) and strength.
function veaf.getWind(point)

    -- Get wind velocity vector.
    local windvec3  = atmosphere.getWind(point)
    local direction = math.floor(math.deg(math.atan2(windvec3.z, windvec3.x)))

    if direction < 0 then
      direction = direction + 360
    end

    -- Convert TO direction to FROM direction. 
    if direction > 180 then
      direction = direction-180
    else
      direction = direction+180
    end

    -- Calc 2D strength.
    local strength=math.floor(math.sqrt((windvec3.x)^2+(windvec3.z)^2))

    -- Debug output.
-- LOGGING DISABLED WHEN COMPILING(string.format("Wind data: point x=%.1f y=%.1f, z=%.1f", point.x, point.y,point.z))
-- LOGGING DISABLED WHEN COMPILING(string.format("Wind data: wind  x=%.1f y=%.1f, z=%.1f", windvec3.x, windvec3.y,windvec3.z))
-- LOGGING DISABLED WHEN COMPILING(string.format("Wind data: |v| = %.1f", strength))
-- LOGGING DISABLED WHEN COMPILING(string.format("Wind data: ang = %.1f", direction))

    -- Return wind direction and strength (in m/s).
    return direction, strength, windvec3
end

---comment
---@param mach number the mach number
---@param altitude any in feet, defaults to 10000
---@param temperature any in celsius, defaults to ISA temperature at altitude
function veaf.convertMachSpeed(mach, altitude, temperature)
    return veaf.convertSpeeds(mach, nil, nil, altitude, temperature)
end

---comment
---@param ktas number the true airspeed in knots
---@param altitude any in feet, defaults to 10000
---@param temperature any in celsius, defaults to ISA temperature at altitude
function veaf.convertTrueAirSpeed(ktas, altitude, temperature)
    return veaf.convertSpeeds(nil, nil, ktas, altitude, temperature)
end

---comment
---@param kias number the indicated airspeed in knots
---@param altitude any in feet, defaults to 10000
---@param temperature any in celsius, defaults to ISA temperature at altitude
function veaf.convertIndicatedAirSpeed(kias, altitude, temperature)
    return veaf.convertSpeeds(nil, kias, nil, altitude, temperature)
end

---Computes speeds based on a speed parameter (mach, tas, ias) and altitude/temperature
---@param mach number? the mach number
---@param kias number? the indicated airspeed in knots
---@param ktas number? the true airspeed in knots
---@param altitude any in meters, defaults to 10000
---@param temperature any in celsius, defaults to ISA temperature at altitude
---@param pressure any in pa, defaults to ISA temperature at altitude
---@return table result containing KTAS, KIAS, Mach, IAS_ms and TAS_ms
function veaf.convertSpeeds(mach, kias, ktas, altitude, temperature, pressure)
-- LOGGING DISABLED WHEN COMPILING("veaf.convertSpeeds(mach=%s, kias=%s, ktas=%s, altitude=%s, temperature=%s, pressure=%s) -> initial", veaf.p(mach), veaf.p(kias), veaf.p(ktas), veaf.p(altitude), veaf.p(temperature), veaf.p(pressure))

    local result = {
        KTAS = 0,
        KIAS = 0,
        Mach = 0,
    }

    local h_tropopause = 11000 --m, tropopause start altitude<

    local altitude = altitude
    if not altitude then
        altitude = 10000 -- default to 10000m
    end

    local T0 = 288.15 --K, ISA+0 altitude, may need to be corrected for mission ground temp
    local Tz = -0.0065 --K/m, ISA temperature gradient in troposphere
    local T_tropopause = 216.65 --K, temperature at the border between tropopause and troposphere (temperature in the tropopause)
    local P0 = 101325 --Pa, standard pressure
    local Gamma = 1.4 --Air heat capacity ratio
    local r = 287.03 --J/kg/K Perfect Gas constant for air
    local g = 9.81 --m/s^2 gravity constant on earth, might need to account for which planet ED is on

    local temperature = temperature
    if not temperature then
        -- compute ISA temperature based on altitude
        if altitude<h_tropopause then
            temperature = T0 + Tz*altitude --troposphere (temp in K)
        else
            temperature = T_tropopause --tropopause (max altitude 20000m) (temp in K)
        end
    else
        temperature = temperature + 273.15 --conversion to Kelvin
    end

    local function P_troposphere(temperature)
        return P0*(1+(temperature-T0)/T0)^(-g/(r*Tz))
    end

    local pressure = pressure
    if not pressure then
        -- compute pressure based on altitude and ISA temperature
        if altitude<h_tropopause then
            pressure = P_troposphere(temperature)
        else
            pressure = P_troposphere(T_tropopause) * math.exp(-g*(altitude-h_tropopause)/(r*T_tropopause))
        end
    end

    ---comment
    ---@param temperature number temperature in K
    ---@return number speed of sound in m/s
    local function speedOfSound(temperature)
        return math.sqrt(Gamma*r*temperature)
    end

    local B = Gamma/(Gamma-1)

    ---comment
    ---@param mach number mach number to calculate (Pt-Ps)/Ps with Pt/Ps given by isentropic relations (NOTE : (Pt-Ps)=deltaP)
    ---@return number returns the ratio deltaP/P (DPP) (what a pitot tube would measure for M<1)
    local function isentropicDPP(mach)
        return (1+(Gamma-1)*mach^2/2)^B-1;
    end

    ---comment
    ---@param mach number mach number to calculate (Pt-Ps)/Ps after a normal shock (M>1) (NOTE : (Pt-Ps)=deltaP)
    ---@return number returns the ratio deltaP/P (DPP) after the normal shock (what a pitot tube would measure for M>1)
    local function lord_rayleighDPP(mach)
        local A = ((Gamma+1)*mach^2/2)^B
        local C = ((Gamma+1)/(2*Gamma*mach^2-Gamma+1))^(B/Gamma);
        return A*C-1;
    end

    ---comment
    ---@param mach1 number the starting mach (mach_0 or mach_p) which determines the deltaP/P1 being computed (for a pitot tube at sea level, subscript 0 (IAS) or at altitude (TAS), subscript p)
    ---@param getTAS boolean? if true, switches to conversion mode from IAS to TAS
    ---@return number so if you provide only mach_P (TAS), this will return mach_0 (IAS), and if you provide mach_0 and getTAS true (IAS), this will return mach_P (TAS)
    local function getConvertedMach(mach1, getTAS)

-- LOGGING DISABLED WHEN COMPILING("getConvertedMach(mach1 = %s, getTAS = %s", veaf.p(mach1), veaf.p(getTAS))

        local DPP1 = 0;
        if mach1 > 1 then
            DPP1 = lord_rayleighDPP(mach1); --At this point it's still deltaP / Pp (DPPP) (subscript p = at pitot tube, subscript 0 = at sea level)
        else
            DPP1 = isentropicDPP(mach1); --At this point it's still deltaP / Pp (DPPP) (subscript p = at pitot tube, subscript 0 = at sea level)
        end

-- LOGGING DISABLED WHEN COMPILING("DPP1 = %s -> initial", veaf.p(DPP1))

        if getTAS then
            DPP1 = P0*DPP1/pressure --conversion from DPP0 to DPPP
        else
            DPP1 = pressure*DPP1/P0 --conversion from DPPP to DPP0
        end

-- LOGGING DISABLED WHEN COMPILING("DPP1 = %s -> final", veaf.p(DPP1))

        local mach2 = 1

        local function converge_2_DPP(machStep)
            while(lord_rayleighDPP(mach2) < DPP1) do --DPP2 = lord_rayleighDPP(mach2)
                mach2 = mach2+machStep
            end

            return mach2
        end

        if DPP1 > lord_rayleighDPP(1) then

            mach2 = converge_2_DPP(0.25) - 0.25 --coarse
-- LOGGING DISABLED WHEN COMPILING("coarse mach2 = %s", veaf.p(mach2))
            mach2 = converge_2_DPP(0.0125) - 0.0125 --medium
-- LOGGING DISABLED WHEN COMPILING("medium mach2 = %s", veaf.p(mach2))
            mach2 = converge_2_DPP(0.00625) --fine
-- LOGGING DISABLED WHEN COMPILING("fine mach2 = %s", veaf.p(mach2))

        else
            mach2 = math.sqrt(2*((DPP1+1)^(1/B)-1)/(Gamma-1))
-- LOGGING DISABLED WHEN COMPILING("subsonic mach2 = %s", veaf.p(mach2))
        end

        return mach2
    end

    local ms_2_kt = 1.94384
    local a1 = speedOfSound(temperature)
    local a0 = speedOfSound(T0)
-- LOGGING DISABLED WHEN COMPILING("a0 = %s, a1 = %s", veaf.p(a0), veaf.p(a1))


    if mach then
        -- compute speeds from mach number
        result.Mach = mach

        result.TAS_ms = mach * a1
        result.KTAS = result.TAS_ms * ms_2_kt

        result.IAS_ms = getConvertedMach(result.Mach)*a0
        result.KIAS = result.IAS_ms * ms_2_kt
    elseif kias then
        -- compute speeds from ias
        result.KIAS = kias
        result.IAS_ms = result.KIAS / ms_2_kt

        result.TAS_ms = getConvertedMach(result.IAS_ms/a0, true)*a1
        result.KTAS = result.TAS_ms * ms_2_kt

        result.Mach = result.TAS_ms / a1
    elseif ktas then
        -- compute speeds from tas
        result.KTAS = ktas
        result.TAS_ms = result.KTAS / ms_2_kt

        result.Mach = result.TAS_ms / a1

        result.IAS_ms = getConvertedMach(result.Mach)*a0
        result.KIAS = result.IAS_ms * ms_2_kt
    end

-- LOGGING DISABLED WHEN COMPILING("veaf.convertSpeeds(mach=%s, kias=%s, ktas=%s, altitude=%s, temperature=%s, pressure=%s) -> final", veaf.p(result.Mach), veaf.p(result.KIAS), veaf.p(result.KTAS), veaf.p(altitude), veaf.p(temperature), veaf.p(pressure))

    return result
end

--- Find a suitable point for spawning a unit in a <dispersion>-sized circle around a spot
function veaf.findPointInZone(spawnSpot, dispersion, isShip)
    local unitPosition
    local tryCounter = 1000
    local dispersion = dispersion or 0
    local _dispersion = dispersion
    repeat -- Place the unit in a "dispersion" ft radius circle from the spawn spot
        unitPosition = mist.getRandPointInCircle(spawnSpot, _dispersion)
        local landType = land.getSurfaceType(unitPosition)
        tryCounter = tryCounter - 1
        _dispersion = _dispersion + dispersion
    until ((isShip and landType == land.SurfaceType.WATER) or (not(isShip) and (landType == land.SurfaceType.LAND or landType == land.SurfaceType.ROAD or landType == land.SurfaceType.RUNWAY))) or tryCounter == 0
    if tryCounter == 0 then
        return nil
    else
        return unitPosition
    end
end

---Fixes a table of mixed units and unit names and returns a table of DCS units
---@param unitsOrNames table a list of units, unit names, or a mix
---@return table the DCS units
function veaf.fixUnitsTable(unitsOrNames)
	local units = {}
    for _, unitOrName in pairs(unitsOrNames) do
        local unit = nil
        if type(unitOrName) == "table" then
            -- already an unit
            unit = unitOrName
        elseif type(unitOrName) == "string" then
            -- find by name
            unit = Unit.getByName(unitOrName) or StaticObject.getByName(unitOrName)
        end
        if unit then
            table.insert(units, unit)
        end
    end
    return units
end

---checks if a unit is in a trigger zone
---@param unitOrName any a DCS unit or an unit name
---@param zoneOrName any a DCS trigger zone or a trigger zone name (any type)
---@return boolean true if the unit is in the trigger zone
function veaf.isUnitInZone(unitOrName, zoneOrName)
    local unitIsInZone = false
    local unit = nil
    if unitOrName then
        if type(unitOrName) == "table" then
            -- already an unit
            unit = unitOrName
        elseif type(unitOrName) == "string" then
            -- find by name
            unit = Unit.getByName(unitOrName) or StaticObject.getByName(unitOrName)
        end
    end

    local zone = nil
    if zoneOrName then
        if type(zoneOrName) == "table" then
            -- already a DCS zone
            zone = zoneOrName
        elseif type(zoneOrName) == "string" then
            -- find by name
            zone = veaf.getTriggerZone(zoneOrName)
        end
    end
    if zone and unit then
        local unitPosition = unit:getPosition().p
        local objectCategory = Object.getCategory(unit)
        if unitPosition and ((objectCategory == 1 and unit:isActive() == true) or objectCategory ~= 1) then -- it is a unit and is active or it is not a unit
            if zone.verticies  then
                local pointInPolygon = mist.pointInPolygon(unitPosition, zone.verticies)
                if pointInPolygon then
                    unitIsInZone = true
                end
            else
                if ((unitPosition.x - zone.x)^2 + (unitPosition.z - zone.y)^2)^0.5 <= zone.radius then
                    unitIsInZone = true
                end
            end
        end
    end
    return unitIsInZone
end

--- TODO doc
function veaf.generateVehiclesRoute(startPoint, destination, onRoad, speed, patrol, groupName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.generateVehiclesRoute(onRoad=[%s], speed=[%s], patrol=[%s])", tostring(onRoad or ""), tostring(speed or ""), tostring(patrol or "")))

    speed = speed or veaf.DEFAULT_GROUND_SPEED_KPH
    onRoad = onRoad or false
    patrol = patrol or false
-- LOGGING DISABLED WHEN COMPILING(string.format("startPoint = {x = %d, y = %d, z = %d}", startPoint.x, startPoint.y, startPoint.z))
    local action = "Diamond"
    if onRoad then
        action = "On Road"
    end

    local endPoint = veafNamedPoints.getPoint(destination)
    if not(endPoint) then
        -- check if these are coordinates
        local _lat, _lon = veaf.computeLLFromString(destination)
-- LOGGING DISABLED WHEN COMPILING(string.format("_lat=%s",veaf.p(_lat)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_lon=%s",veaf.p(_lon)))
        if _lat and _lon then
            endPoint = coord.LLtoLO(_lat, _lon)
        end
    end
    if not(endPoint) then
        local msg = "A point named "..destination.." cannot be found, and these are not valid coordinates !"
        veaf.loggers.get(veaf.Id):warn(msg)
        trigger.action.outText(msg, 5)
        return
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("endPoint=%s", veaf.p(endPoint)))

    local road_x = nil
    local road_z = nil
    local trueStartPoint = mist.utils.deepCopy(startPoint)
    if onRoad then
-- LOGGING DISABLED WHEN COMPILING("setting startPoint on a road")
        road_x, road_z = land.getClosestPointOnRoads('roads',startPoint.x, startPoint.z)
        startPoint = veaf.placePointOnLand({x = road_x, y = 0, z = road_z})
    else
        startPoint = veaf.placePointOnLand({x = startPoint.x, y = 0, z = startPoint.z})
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("startPoint = {x = %d, y = %d, z = %d}", startPoint.x, startPoint.y, startPoint.z))

    local trueEndPoint = mist.utils.deepCopy(endPoint)
    if onRoad then
-- LOGGING DISABLED WHEN COMPILING("setting endPoint on a road")
        road_x, road_z =land.getClosestPointOnRoads('roads',endPoint.x, endPoint.z)
        endPoint = veaf.placePointOnLand({x = road_x, y = 0, z = road_z})
    else
        endPoint = veaf.placePointOnLand({x = endPoint.x, y = 0, z = endPoint.z})
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("endPoint = {x = %d, y = %d, z = %d}", endPoint.x, endPoint.y, endPoint.z))

    local vehiclesRoute = {
        [1] =
        {
            ["x"] = trueStartPoint.x,
            ["y"] = trueStartPoint.z,
            ["alt"] = trueStartPoint.y,
            ["type"] = "Turning Point",
            ["ETA"] = 0,
            ["alt_type"] = "BARO",
            ["formation_template"] = "",
            ["name"] = "T_STA",
            ["ETA_locked"] = false,
            ["speed"] = 0,
            ["action"] = "Off Road",
            ["speed_locked"] = true,
        }, -- end of [1]
        [2] =
        {
            ["x"] = startPoint.x,
            ["y"] = startPoint.z,
            ["alt"] = startPoint.y,
            ["type"] = "Turning Point",
            ["ETA"] = 1,
            ["alt_type"] = "BARO",
            ["formation_template"] = "",
            ["name"] = "STA",
            ["ETA_locked"] = false,
            ["speed"] = speed / 3.6,
            ["action"] = action,
            ["speed_locked"] = false,
        }, -- end of [2]
        [3] =
        {
            ["x"] = endPoint.x,
            ["y"] = endPoint.z,
            ["alt"] = endPoint.y,
            ["type"] = "Turning Point",
            ["ETA"] = 2,
            ["alt_type"] = "BARO",
            ["formation_template"] = "",
            ["name"] = "END",
            ["ETA_locked"] = false,
            ["speed"] = speed / 3.6,
            ["action"] = action,
            ["speed_locked"] = true,
        }, -- end of [3]
    }

    if patrol then

        vehiclesRoute[4] =
        {
            ["x"] = startPoint.x,
            ["y"] = startPoint.z,
            ["alt"] = startPoint.y,
            ["type"] = "Turning Point",
            ["ETA"] = 3,
            ["alt_type"] = "BARO",
            ["formation_template"] = "",
            ["name"] = "STA2",
            ["ETA_locked"] = false,
            ["speed"] = speed / 3.6,
            ["action"] = action,
            ["task"] =
            {
                ["id"] = "ComboTask",
                ["params"] =
                {
                    ["tasks"] =
                    {
                        --sounds good ! doesn't work, pathfinding goes dumb if done this way
                        --[1] = 
                        --{
                        --    ["enabled"] = true,
                        --    ["auto"] = false,
                        --    ["id"] = "GoToWaypoint",
                        --    ["number"] = 1,
                        --    ["params"] = 
                        --    {
                        --        ["fromWaypointIndex"] = 4,
                        --        ["nWaypointIndx"] = 2,
                        --    }, -- end of ["params"]
                        --}, -- end of [1]
                    }, -- end of ["tasks"]
                }, -- end of ["params"]
            }, -- end of ["task"]
            ["speed_locked"] = true,
        }

        veaf.PatrolWatchdog(groupName, vehiclesRoute, speed/3.6, "notSeen")
    elseif onRoad then
        vehiclesRoute[4] =
        {
            ["x"] = trueEndPoint.x,
            ["y"] = trueEndPoint.z,
            ["alt"] = trueEndPoint.y,
            ["type"] = "Turning Point",
            ["ETA"] = 4,
            ["alt_type"] = "BARO",
            ["formation_template"] = "",
            ["name"] = "T_END",
            ["ETA_locked"] = false,
            ["speed"] = speed / 3.6,
            ["action"] = "Diamond",
            ["speed_locked"] = true,
        }
    end

    if not patrol then
        local endWaypoint = vehiclesRoute[4]
        if not onRoad then
            endWaypoint = vehiclesRoute[3]
        end

        endWaypoint.task = {}
        endWaypoint.task =
        {
            ["id"] = "ComboTask",
            ["params"] =
            {
                ["tasks"] =
                {
                    [1] =
                    {
                        ["number"] = 1,
                        ["auto"] = false,
                        ["id"] = "WrappedAction",
                        ["enabled"] = true,
                        ["params"] =
                        {
                            ["action"] =
                            {
                                ["id"] = "Option",
                                ["params"] =
                                {
                                    ["value"] = 2, --Alarm State RED
                                    ["name"] = 9, --Alarm State
                                }, -- end of ["params"]
                            }, -- end of ["action"]
                        }, -- end of ["params"]
                    }, -- end of [1]
                }, -- end of ["tasks"]
            }, -- end of ["params"]
        }
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("vehiclesRoute = %s", veaf.p(vehiclesRoute)))

    return vehiclesRoute
end

function veaf.PatrolWatchdog(groupName,patrolRoute,speed,firstPass)
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.PatrolWatchdog(groupName=%s, speed=%s, firstPass=%s)", veaf.p(groupName), veaf.p(speed), veaf.p(firstPass)))
-- LOGGING DISABLED WHEN COMPILING(string.format("patrolRoute=%s", veaf.p(patrolRoute)))

    local rescheduleTime = 30
    local maxDist = 10
    if firstPass then
        maxDist = 200
    end
    local startPoint = {x = patrolRoute[1].x, z = patrolRoute[1].y}

    local group = Group.getByName(groupName)
    if group then
        local controller = group:getController()
        if controller then
            veaf.loggers.get(veaf.Id):info("Checking if patrol is within " .. maxDist .. "m of it's start point...")

            local groupUnits = group:getUnits()

            if groupUnits and groupUnits[1] and groupUnits[1]:isActive() then
                local leadPos = groupUnits[1]:getPosition().p
-- LOGGING DISABLED WHEN COMPILING(string.format("Lead vehicule name : %s", veaf.p(groupUnits[1]:getName())))
-- LOGGING DISABLED WHEN COMPILING(string.format("Lead vehicule position : %s", veaf.p(leadPos)))

                if leadPos then
                    local distanceToStart = (leadPos.x-startPoint.x)^2+(leadPos.z-startPoint.z)^2
                    local result = distanceToStart < maxDist^2

                    if firstPass == "notSeen" and result then
                        firstPass = "seenOnce"
                   elseif firstPass == "seenOnce" and not result then
                        firstPass = false
                    end

                    if not firstPass and result then

                        veaf.loggers.get(veaf.Id):info("Lead vehicle in range, setting route !")
                        mist.goRoute(group,patrolRoute)
                        controller:setSpeed(speed)
                        firstPass = "notSeen"

                    elseif firstPass then
-- LOGGING DISABLED WHEN COMPILING("Lead vehicle is passing in the bubble, rescheduling in " .. rescheduleTime .. "s !")
                    else
-- LOGGING DISABLED WHEN COMPILING("Lead vehicle/lead controller not found or lead vehicle not within " .. maxDist .. "m, rescheduling in " .. rescheduleTime .. "s !")
                    end

                    mist.scheduleFunction(veaf.PatrolWatchdog,{groupName, patrolRoute, speed, firstPass}, timer.getTime()+rescheduleTime)
                end
            elseif not groupUnits[1]:isActive() then
-- LOGGING DISABLED WHEN COMPILING("Lead vehicle not active, rescheduling in 60s !")
                mist.scheduleFunction(veaf.PatrolWatchdog,{groupName, patrolRoute, speed, firstPass}, timer.getTime()+60)
            end
        end
    end

-- LOGGING DISABLED WHEN COMPILING("========================================================================")
end


--- Add a unit to the <group> on a suitable point in a <dispersion>-sized circle around a spot
function veaf.addUnit(group, spawnSpot, dispersion, unitType, unitName, skill)
    local unitPosition = veaf.findPointInZone(spawnSpot, dispersion, false)
    if unitPosition ~= nil then
        table.insert(
            group,
            {
                ["x"] = unitPosition.x,
                ["y"] = unitPosition.y,
                ["type"] = unitType,
                ["name"] = unitName,
                ["heading"] = 0,
                ["skill"] = skill
            }
        )
    else
        veaf.loggers.get(veaf.Id):info("cannot find a suitable position for unit "..unitType)
    end
end

--- Makes a group move to a waypoint set at a specific heading and at a distance covered at a specific speed in an hour
function veaf.moveGroupAt(groupName, leadUnitName, heading, speed, timeInSeconds, endPosition, pMiddlePointDistance)
-- LOGGING DISABLED WHEN COMPILING("veaf.moveGroupAt(groupName=" .. groupName .. ", heading="..heading.. ", speed=".. speed..", timeInSeconds="..(timeInSeconds or 0))

    local unitGroup = Group.getByName(groupName)
    if unitGroup == nil then
        veaf.loggers.get(veaf.Id):error("veaf.moveGroupAt: " .. groupName .. ' not found')
		return false
    end

    local leadUnit = unitGroup:getUnits()[1]
    if leadUnitName then
        leadUnit = Unit.getByName(leadUnitName)
    end
    if leadUnit == nil then
        veaf.loggers.get(veaf.Id):error("veaf.moveGroupAt: " .. leadUnitName .. ' not found')
		return false
    end

    local headingRad = mist.utils.toRadian(heading)
-- LOGGING DISABLED WHEN COMPILING("headingRad="..headingRad)
    local fromPosition = leadUnit:getPosition().p
    fromPosition = { x = fromPosition.x, y = fromPosition.z }
-- LOGGING DISABLED WHEN COMPILING("fromPosition="..veaf.vecToString(fromPosition))

    local mission = {
		id = 'Mission',
		params = {
			["communication"] = true,
			["start_time"] = 0,
			route = {
				points = {
					-- first point
                    [1] =
                    {
                        --["alt"] = 0,
                        ["type"] = "Turning Point",
                        --["formation_template"] = "Diamond",
                        --["alt_type"] = "BARO",
                        ["x"] = fromPosition.x,
                        ["y"] = fromPosition.z,
                        ["name"] = "Starting position",
                        ["action"] = "Turning Point",
                        ["speed"] = 9999, -- ahead flank
                        ["speed_locked"] = true,
                    }, -- end of [1]
				},
			}
		}
	}

    if pMiddlePointDistance then
        -- middle point (helps with having a more exact final bearing, specially with big hunks of steel like carriers)
        local middlePointDistance = 2000
        if pMiddlePointDistance then
            middlePointDistance = pMiddlePointDistance
        end

        local newWaypoint1 = {
            x = fromPosition.x + middlePointDistance * math.cos(headingRad),
            y = fromPosition.y + middlePointDistance * math.sin(headingRad),
        }
        fromPosition.x = newWaypoint1.x
        fromPosition.y = newWaypoint1.y
-- LOGGING DISABLED WHEN COMPILING("newWaypoint1="..veaf.vecToString(newWaypoint1))

        table.insert(mission.params.route.points,
            {
                --["alt"] = 0,
                ["type"] = "Turning Point",
                --["formation_template"] = "Diamond",
                --["alt_type"] = "BARO",
                ["x"] = newWaypoint1.x,
                ["y"] = newWaypoint1.y,
                ["name"] = "Middle point",
                ["action"] = "Turning Point",
                ["speed"] = 9999, -- ahead flank
                ["speed_locked"] = true,
            }
        )
    end

    local length
    if timeInSeconds then
        length = speed * timeInSeconds
    else
        length = speed * 3600 -- m travelled in 1 hour
    end
-- LOGGING DISABLED WHEN COMPILING("length="..length .. " m")

    -- new route point
	local newWaypoint2 = {
		x = fromPosition.x + length * math.cos(headingRad),
		y = fromPosition.y + length * math.sin(headingRad),
	}
-- LOGGING DISABLED WHEN COMPILING("newWaypoint2="..veaf.vecToString(newWaypoint2))

    table.insert(mission.params.route.points,
        {
            --["alt"] = 0,
            ["type"] = "Turning Point",
            --["formation_template"] = "Diamond",
            --["alt_type"] = "BARO",
            ["x"] = newWaypoint2.x,
            ["y"] = newWaypoint2.y,
            ["name"] = "",
            ["action"] = "Turning Point",
            ["speed"] = speed,
            ["speed_locked"] = true,
        }
    )

    if endPosition then
        table.insert(mission.params.route.points,
            {
                --["alt"] = 0,
                ["type"] = "Turning Point",
                --["formation_template"] = "Diamond",
                --["alt_type"] = "BARO",
                ["x"] = endPosition.x,
                ["y"] = endPosition.z,
                ["name"] = "Back to starting position",
                ["action"] = "Turning Point",
                ["speed"] = 9999, -- ahead flank
                ["speed_locked"] = true,
            }
        )
    end

	-- replace whole mission
	unitGroup:getController():setTask(mission)

    return true
end

veaf.defaultAlarmState = 2

function veaf.readyForCombat(group, alarm, disperseTime)
-- LOGGING DISABLED WHEN COMPILING(string.format("group=%s, alarm=%s, disperseTime=%s", veaf.p(group), veaf.p(alarm), veaf.p(disperseTime)))
    if type(group) == 'string' then
        group = Group.getByName(group)
    end
    if group then
-- LOGGING DISABLED WHEN COMPILING("got group")

        local alarm = alarm
        if not alarm or alarm < 0 or alarm > 2 then
            alarm = veaf.defaultAlarmState
        end

        local disperseTime = disperseTime
        if not disperseTime or disperseTime < 0 then
            disperseTime = 0
        end

        local cont = group:getController()
        cont:setOnOff(true)
        cont:setOption(AI.Option.Ground.id.ALARM_STATE, alarm)
        cont:setOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, disperseTime) -- set disperse on attack according to the option
        cont:setOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE) -- set fire at will
        cont:setOption(AI.Option.Ground.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE) -- set fire at will
        cont:setOption(AI.Option.Naval.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE) -- set fire at will
        cont:setOption(AI.Option.Ground.id.ENGAGE_AIR_WEAPONS, true) -- engage air-to-ground weapons with SAMs
    end
end

-- Makes a group move to a specific waypoint at a specific speed
function veaf.moveGroupTo(groupName, pos, speed, altitude)
    if not(altitude) then
        altitude = 0
    end
-- LOGGING DISABLED WHEN COMPILING("veaf.moveGroupTo(groupName=" .. groupName .. ", speed=".. speed .. ", altitude=".. altitude)
-- LOGGING DISABLED WHEN COMPILING("pos="..veaf.vecToString(pos))

	local unitGroup = Group.getByName(groupName)
    if unitGroup == nil then
        veaf.loggers.get(veaf.Id):error("veaf.moveGroupTo: " .. groupName .. ' not found')
		return false
    end

    local route = {
        [1] =
        {
            ["alt"] = altitude,
            ["action"] = "Turning Point",
            ["alt_type"] = "BARO",
            ["speed"] = veaf.round(speed, 2),
            ["type"] = "Turning Point",
            ["x"] = pos.x,
            ["y"] = pos.z,
            ["speed_locked"] = true,
        },
        [2] =
        {
            ["alt"] = altitude,
            ["action"] = "Turning Point",
            ["alt_type"] = "BARO",
            ["speed"] = 0,
            ["type"] = "Turning Point",
            ["x"] = pos.x,
            ["y"] = pos.z,
            ["speed_locked"] = true,
        },
    }

    -- order group to new waypoint
	mist.goRoute(groupName, route)

    return true
end

function veaf.getAvgGroupPos(groupName) -- stolen from Mist and corrected
	local group = groupName -- sometimes this parameter is actually a group
	if type(groupName) == 'string' and Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then
		group = Group.getByName(groupName)
	end
	local units = {}
	for i = 1, group:getSize() do
		table.insert(units, group:getUnit(i):getName())
	end

	return mist.getAvgPos(units)
end

--- Computes the coordinates of a point offset from a route of a certain distance, at a certain distance from route start
--- e.g. we go from [startingPoint] to [destinationPoint], and at [distanceFromStartingPoint] we look at [offset] meters (left if <0, right else)
function veaf.computeCoordinatesOffsetFromRoute(startingPoint, destinationPoint, distanceFromStartingPoint, offset)
-- LOGGING DISABLED WHEN COMPILING("startingPoint="..veaf.vecToString(startingPoint))
-- LOGGING DISABLED WHEN COMPILING("destinationPoint="..veaf.vecToString(destinationPoint))

    local vecAB = {x = destinationPoint.x +- startingPoint.x, y = destinationPoint.y - startingPoint.y, z = destinationPoint.z - startingPoint.z}
-- LOGGING DISABLED WHEN COMPILING("vecAB="..veaf.vecToString(vecAB))
    local alpha = math.atan2(vecAB.x, vecAB.z) -- atan2(y, x) 
-- LOGGING DISABLED WHEN COMPILING("alpha="..alpha)
    local r = math.sqrt(distanceFromStartingPoint * distanceFromStartingPoint + offset * offset)
-- LOGGING DISABLED WHEN COMPILING("r="..r)
    local beta = math.atan(offset / distanceFromStartingPoint)
-- LOGGING DISABLED WHEN COMPILING("beta="..beta)
    local tho = alpha + beta
-- LOGGING DISABLED WHEN COMPILING("tho="..tho)
    local offsetPoint = { z = r * math.cos(tho) + startingPoint.z, y = 0, x = r * math.sin(tho) + startingPoint.x}
-- LOGGING DISABLED WHEN COMPILING("offsetPoint="..veaf.vecToString(offsetPoint))
    local offsetPointOnLand = veaf.placePointOnLand(offsetPoint)
-- LOGGING DISABLED WHEN COMPILING("offsetPointOnLand="..veaf.vecToString(offsetPointOnLand))

    return offsetPointOnLand, offsetPoint
end

function veaf.getBearingAndRangeFromTo(fromPoint, toPoint)
-- LOGGING DISABLED WHEN COMPILING("fromPoint="..veaf.vecToString(fromPoint))
-- LOGGING DISABLED WHEN COMPILING("toPoint="..veaf.vecToString(toPoint))

    local vec = { z = toPoint.z - fromPoint.z, x = toPoint.x - fromPoint.x}
    local angle = mist.utils.round(mist.utils.toDegree(mist.utils.getDir(vec)), 0)
    local distance = mist.utils.get2DDist(toPoint, fromPoint)
    return angle, distance, mist.utils.round(distance / 1000, 0), mist.utils.round(mist.utils.metersToNM(distance), 0)
end

function veaf.getGroupsOfCoalition(coa)
    local coalitions = { coalition.side.RED, coalition.side.BLUE, coalition.side.NEUTRAL}
    if coa then
        coalitions = { coa }
    end
    local allDcsGroups = {}
    for _, coa in pairs(coalitions) do
        local dcsGroups = coalition.getGroups(coa)
        for _, dcsGroup in pairs(dcsGroups) do
            table.insert(allDcsGroups, dcsGroup)
        end
    end
    return allDcsGroups
end

function veaf.getStaticsOfCoalition(coa)
    local coalitions = { coalition.side.RED, coalition.side.BLUE, coalition.side.NEUTRAL}
    if coa then
        coalitions = { coa }
    end
    local allDcsStatics = {}
    for _, coa in pairs(coalitions) do
        local dcsStatics = coalition.getStaticObjects(coa)
        for _, dcsStatic in pairs(dcsStatics) do
            table.insert(allDcsStatics, dcsStatic)
        end
    end
    return allDcsStatics
end

function veaf.getUnitsOfAllCoalitions(includeStatics)
    return veaf.getUnitsOfCoalition(includeStatics)
end

function veaf.getUnitsOfCoalition(includeStatics, coa)
    local allDcsUnits = {}
    local allDcsGroups = veaf.getGroupsOfCoalition(coa)
    for _, group in pairs(allDcsGroups) do
        for _, unit in pairs(group:getUnits()) do
            table.insert(allDcsUnits, unit)
        end
    end
    if includeStatics then
        local allDcsStatics = veaf.getStaticsOfCoalition(coa)
        for _, staticUnit in pairs(allDcsStatics) do
            table.insert(allDcsUnits, staticUnit)
        end
    end
    return allDcsUnits
end

function veaf.getUnitsNamesOfCoalition(includeStatics, coa)
    local allDcsUnits = {}
    local allDcsGroups = veaf.getGroupsOfCoalition(coa)
    for _, group in pairs(allDcsGroups) do
        for _, unit in pairs(group:getUnits()) do
            table.insert(allDcsUnits, unit:getName())
        end
    end
    if includeStatics then
        local allDcsStatics = veaf.getStaticsOfCoalition(coa)
        for _, staticUnit in pairs(allDcsStatics) do
            table.insert(allDcsUnits, StaticObject.getName(staticUnit))
        end
    end
    return allDcsUnits
end

function veaf.findUnitsInCircle(center, radius, includeStatics, onlyTheseUnits)
-- LOGGING DISABLED WHEN COMPILING(string.format("findUnitsInCircle(radius=%s)", tostring(radius)))
-- LOGGING DISABLED WHEN COMPILING(string.format("center=%s", veaf.p(center)))

    local unitsToCheck = {}
    if onlyTheseUnits then
        for k = 1, #onlyTheseUnits do
            local unit = Unit.getByName(onlyTheseUnits[k]) or StaticObject.getByName(onlyTheseUnits[k])
            if unit then
                unitsToCheck[#unitsToCheck + 1] = unit
            end
        end
    else
        unitsToCheck = veaf.getUnitsOfAllCoalitions(includeStatics)
    end

    local result = {}
    for _, unit in pairs(unitsToCheck) do
        local pos = unit:getPosition().p
        if pos then -- you never know O.o
            local name = unit:getName()
            local distanceFromCenter = ((pos.x - center.x)^2 + (pos.z - center.z)^2)^0.5
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s; distanceFromCenter=%s", tostring(name), veaf.p(distanceFromCenter)))
            if distanceFromCenter <= radius then
                result[name] = unit
            end
        end
    end
    return result
end

--- modified version of mist.getGroupRoute that returns raw DCS group data
function veaf.getGroupData(groupIdent)
    -- refactor to search by groupId and allow groupId and groupName as inputs
    local gpId = groupIdent
        if mist.DBs.MEgroupsByName[groupIdent] then
            gpId = mist.DBs.MEgroupsByName[groupIdent].groupId
        else
            veaf.loggers.get(veaf.Id):info(groupIdent..' not found in mist.DBs.MEgroupsByName')
        end

    for coa_name, coa_data in pairs(env.mission.coalition) do
        if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then
            if coa_data.country then --there is a country table
                for cntry_id, cntry_data in pairs(coa_data.country) do
                    for obj_type_name, obj_type_data in pairs(cntry_data) do
                        if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then	-- only these types have points
                            if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then	--there's a group!
                                for group_num, group_data in pairs(obj_type_data.group) do
                                    if group_data and group_data.groupId == gpId	then -- this is the group we are looking for
                                        return group_data
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end

    veaf.loggers.get(veaf.Id):info(' no group data found for '..groupIdent)
    return nil
end

function veaf.findInTable(data, key)
    local result = nil
    if data then
        result = data[key]
    end
    if result then
-- LOGGING DISABLED WHEN COMPILING(".findInTable found ".. key)
    end
    return result
end

function veaf.getTankerData(tankerGroupName)
-- LOGGING DISABLED WHEN COMPILING("getTankerData " .. tankerGroupName)
    local result = nil
    local tankerData = veaf.getGroupData(tankerGroupName)
    if tankerData then
        result = {}
        -- find callsign
        local units = veaf.findInTable(tankerData, "units")
        if units and units[1] then
            local callsign = veaf.findInTable(units[1], "callsign")
            if callsign then
                local name = veaf.findInTable(callsign, "name")
                if name then
                    result.tankerCallsign = name
                end
            end
        end

        -- find frequency
        local communication = veaf.findInTable(tankerData, "communication")
        if communication == true then
            local frequency = veaf.findInTable(tankerData, "frequency")
            if frequency then
                result.tankerFrequency = frequency
            end
        end
        local route = veaf.findInTable(tankerData, "route")
        local points = veaf.findInTable(route, "points")
        if points then
-- LOGGING DISABLED WHEN COMPILING("found a " .. #points .. "-points route for tanker " .. tankerGroupName)
            for i, point in pairs(points) do
-- LOGGING DISABLED WHEN COMPILING("found point #" .. i)
                local task = veaf.findInTable(point, "task")
                if task then
                    local tasks = task.params.tasks
                    if (tasks) then
-- LOGGING DISABLED WHEN COMPILING("found " .. #tasks .. " tasks")
                        for j, task in pairs(tasks) do
-- LOGGING DISABLED WHEN COMPILING("found task #" .. j)
                            if task.params then
-- LOGGING DISABLED WHEN COMPILING("has .params")
                                if task.params.action then
-- LOGGING DISABLED WHEN COMPILING("has .action")
                                    if task.params.action.params then
-- LOGGING DISABLED WHEN COMPILING("has .params")
                                        if task.params.action.params.channel then
-- LOGGING DISABLED WHEN COMPILING("has .channel")
                                            veaf.loggers.get(veaf.Id):info("Found a TACAN task for tanker " .. tankerGroupName)
                                            result.tankerTacanTask = task
                                            result.tankerTacanChannel = task.params.action.params.channel
                                            result.tankerTacanMode = task.params.action.params.modeChannel
                                            break
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    return result
end

function veaf.getCarrierATCdata(carrierGroupName, carrierUnitName)
-- LOGGING DISABLED WHEN COMPILING("getCarrierData Group: " .. carrierGroupName .. " Unit: " .. carrierUnitName)
    local result = nil
    local carrierData = veaf.getGroupData(carrierGroupName)
    if carrierData then
        result = {}
        -- find carrier unit within group and gather the information
        local units = veaf.findInTable(carrierData, "units")
        local carrierUnitId = nil
        if units then
            for _,unit in pairs(units) do
                if unit and unit.name and unit.name == carrierUnitName then

                    --get the unit ID which will be used later when searching for ICLS etc. assigned to the carrier itself and get the tower freq/modulation data
                    carrierUnitId = unit.unitId
                    if carrierUnitId then
                        if unit.frequency then
                            local towerString = string.format("%.2f", unit.frequency / 1000000)
                            local towerMod = "AM"
                            if unit.modulation and unit.modulation == 1 then
                                towerMod = "FM"
                            end
                            result.tower = towerString .. " " .. towerMod .. " (Check Freq. Plan)"
                        end
                    end
                end
            end
        end

        --if the carrier was found and is identifiable
        if carrierUnitId then
            --find programmed tasks for the carrier (ACLS, ICLS, etc.)
            local tasks = veaf.findInTable(carrierData, "tasks")
            if tasks then
-- LOGGING DISABLED WHEN COMPILING("found " .. #tasks .. " programmed tasks for carrier " .. carrierUnitName .. " in group " .. carrierGroupName)
                for i, task in pairs(tasks) do
                    if task then
-- LOGGING DISABLED WHEN COMPILING("found task #" .. i)
                        if task.params then
-- LOGGING DISABLED WHEN COMPILING("has .params")
                            if task.params.action then
                                local action = task.params.action
-- LOGGING DISABLED WHEN COMPILING("has .action")
                                if task.params.action.params then
                                    local actionParams = task.params.action.params
-- LOGGING DISABLED WHEN COMPILING("action has .params")
                                    if task.params.action.params.unitId and task.params.action.params.unitId == carrierUnitId then
-- LOGGING DISABLED WHEN COMPILING("programmed task is linked to carrier unit")

                                        if action.id == "ActivateBeacon" and actionParams.channel then
                                            veaf.loggers.get(veaf.Id):info("Found a programmed TACAN task for carrier group " .. carrierGroupName)
                                            local channel = actionParams.channel
                                            local mode = "X"
                                            if actionParams.modeChannel and actionParams.modeChannel == "Y" then --should never happen for carriers
                                                mode = "Y"
                                            end
                                            local callsign = "No Code"
                                            if actionParams.callsign then
                                                callsign = actionParams.callsign
                                            end
                                            result.tacan = channel .. mode .. " (" .. callsign .. ")"
                                        elseif action.id == "ActivateICLS" and actionParams.channel then
                                            veaf.loggers.get(veaf.Id):info("Found a programmed ICLS task for carrier group " .. carrierGroupName)
                                            result.icls = actionParams.channel
                                        elseif action.id == "ActivateLink4" and actionParams.frequency then
                                            veaf.loggers.get(veaf.Id):info("Found a programmed Link4 task for carrier group " .. carrierGroupName)
                                            result.link4 = string.format("%.2f".."MHz",actionParams.frequency / 1000000)
                                        elseif action.id == "ActivateACLS" then
                                            veaf.loggers.get(veaf.Id):info("Found a programmed ACLS task for carrier group " .. carrierGroupName)
                                            result.acls = true
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    return result
end

function veaf.outTextForUnit(unitName, message, duration, forAllGroup)
    local unitId = nil
    local groupId = nil
    if unitName then
        local unit = Unit.getByName(unitName)
        if unit then
            unitId = unit:getID()
            local group = unit:getGroup()
            if group then
                groupId = group:getID()
            end
        end
    end
    if unitId and not forAllGroup then
        trigger.action.outTextForUnit(unitId, message, duration)
    elseif groupId then
        trigger.action.outTextForGroup(groupId, message, duration)
    else
        trigger.action.outText(message, duration)
    end
end

function veaf.outTextForGroup(unitName, message, duration)
    return veaf.outTextForUnit(unitName, message, duration, true)
end

--- Weather Report. Report pressure QFE/QNH, temperature, wind at certain location.
--- stolen from the weatherReport script and modified to fit our usage
function veaf.weatherReport(vec3, alt, withLASTE)

    -- Get Temperature [K] and Pressure [Pa] at vec3.
    local T
    local Pqfe
    if not alt then
        alt = veaf.getLandHeight(vec3) + 15 -- get the weather at 15m over the ground, as it's done IRL in general aviation
    end

    -- At user specified altitude.
    T,Pqfe=atmosphere.getTemperatureAndPressure({x=vec3.x, y=alt, z=vec3.z})
-- LOGGING DISABLED WHEN COMPILING(string.format("T = %.1f, Pqfe = %.2f", T,Pqfe))

    -- Get pressure at sea level.
    local _,Pqnh=atmosphere.getTemperatureAndPressure({x=vec3.x, y=0, z=vec3.z})
-- LOGGING DISABLED WHEN COMPILING(string.format("Pqnh = %.2f", Pqnh))

    -- Convert pressure from Pascal to hecto Pascal.
    Pqfe=Pqfe/100
    Pqnh=Pqnh/100

    -- Pressure unit conversion hPa --> mmHg or inHg
    local _Pqnh=string.format("%.2f mmHg (%.2f inHg)", Pqnh * weathermark.hPa2mmHg, Pqnh * weathermark.hPa2inHg)
    local _Pqfe=string.format("%.2f mmHg (%.2f inHg)", Pqfe * weathermark.hPa2mmHg, Pqfe * weathermark.hPa2inHg)

    -- Temperature unit conversion: Kelvin to Celsius or Fahrenheit.
    T=T-273.15
    local _T=string.format('%d°C (%d°F)', T, weathermark._CelsiusToFahrenheit(T))

    -- Get wind direction and speed.
    local Dir,Vel=weathermark._GetWind(vec3, alt)
-- LOGGING DISABLED WHEN COMPILING(string.format("Dir = %.1f, Vel = %.1f", Dir,Vel))

    -- Get Beaufort wind scale.
    local Bn,Bd=weathermark._BeaufortScale(Vel)

    -- Formatted wind direction.
    local Ds = string.format('%03d°', Dir)

    -- Velocity in player units.
    local Vs=string.format('%.1f m/s (%.1f kn)', Vel, Vel * weathermark.mps2knots)

    -- Altitude.
    local _Alt=string.format("%d m (%d ft)", alt, alt * weathermark.meter2feet)

    local text=""
    text=text..string.format("Altitude %s ASL\n",_Alt)
    text=text..string.format("QFE %.2f hPa = %s\n", Pqfe,_Pqfe)
    text=text..string.format("QNH %.2f hPa = %s\n", Pqnh,_Pqnh)
    text=text..string.format("Temperature %s\n",_T)
    if Vel > 0 then
        text=text..string.format("Wind from %s at %s (%s)", Ds, Vs, Bd)
    else
        text=text.."No wind"
    end

    local function getLASTEat(vec3, alt)
        local T,_=atmosphere.getTemperatureAndPressure({x=vec3.x, y=alt, z=vec3.z})
        local Dir,Vel=weathermark._GetWind(vec3, alt)
        local laste = string.format("\nFL%02d W%03d/%02d T%d", alt * weathermark.meter2feet / 1000, Dir, Vel * weathermark.mps2knots, T-273.15)
        return laste
    end

    if withLASTE then
        text=text.."\n\nLASTE:"
        text=text..getLASTEat(vec3, math.floor(((alt * weathermark.meter2feet + 2000)/1000)*1000+500)/weathermark.meter2feet)
        text=text..getLASTEat(vec3, math.floor(((alt * weathermark.meter2feet + 8000)/1000)*1000+500)/weathermark.meter2feet)
        text=text..getLASTEat(vec3, math.floor(((alt * weathermark.meter2feet + 16000)/1000)*1000+500)/weathermark.meter2feet)
        --text=text..getLASTEat(vec3, _Alt + 7500)
    end

    return text
end

local function _initializeCountriesAndCoalitions()
    veaf.countriesByCoalition={}
    veaf.coalitionByCountry={}
    veaf.countriesByName={}
    veaf.countriesNamesById={}

    local function _sortByImportance(c1,c2)
        local importantCountries = { ['usa']=true, ['russia']=true}
        if c1 then
            return importantCountries[c1:lower()]
        end
        return string.lower(c1) < string.lower(c2)
    end

    for coalitionName, countries in pairs(mist.DBs.units) do
        coalitionName = coalitionName:lower()
-- LOGGING DISABLED WHEN COMPILING("coalitionName=%s", veaf.p(coalitionName))

        if not veaf.countriesByCoalition[coalitionName] then
            veaf.countriesByCoalition[coalitionName]={}
        end
-- LOGGING DISABLED WHEN COMPILING("countries=%s", veaf.p(countries))
        for countryName, country in pairs(countries) do
-- LOGGING DISABLED WHEN COMPILING("country=%s", veaf.p(country))
            countryName = countryName:lower()
            table.insert(veaf.countriesByCoalition[coalitionName], countryName)
            veaf.coalitionByCountry[countryName]=coalitionName:lower()
            veaf.countriesByName[countryName] = country
            veaf.countriesNamesById[country.countryId] = countryName
        end

        table.sort(veaf.countriesByCoalition[coalitionName], _sortByImportance)
    end

-- LOGGING DISABLED WHEN COMPILING("veaf.countriesByCoalition=%s", veaf.p(veaf.countriesByCoalition))
-- LOGGING DISABLED WHEN COMPILING("veaf.coalitionByCountry=%s", veaf.p(veaf.coalitionByCountry))
-- LOGGING DISABLED WHEN COMPILING("veaf.countriesByName=%s", veaf.p(veaf.countriesByName))
-- LOGGING DISABLED WHEN COMPILING("veaf.countriesNamesById=%s", veaf.p(veaf.countriesNamesById))
end

function veaf.getCountryId(countryName)
-- LOGGING DISABLED WHEN COMPILING("veaf.getCountryId(%s)", veaf.p(countryName))
    if not veaf.countriesByName then
        _initializeCountriesAndCoalitions()
    end
    local countryName = string.lower(countryName or "")
    local country = veaf.countriesByName[countryName]
    if country then
        return country.countryId
    else
        return 0
    end
end

function veaf.getCountryName(countryId)
-- LOGGING DISABLED WHEN COMPILING("veaf.getCountryName(%s)", veaf.p(countryId))
    if not veaf.coalitionByCountry then
        _initializeCountriesAndCoalitions()
    end
    local countryName = veaf.countriesNamesById[countryId]
    return countryName
end

function veaf.getCountryForCoalition(coalition)
-- LOGGING DISABLED WHEN COMPILING("veaf.getCountryForCoalition(coalition=%s)", tostring(coalition))
    local coalition = coalition
    if not coalition then
        coalition = 1
    end

    local coalitionName = nil
    if type(coalition) == "number" then
        if coalition == 1 then
            coalitionName = "red"
        elseif coalition == 2 then
            coalitionName = "blue"
        else
            coalitionName = "neutral"
        end
    else
        coalitionName = tostring(coalition)
    end

    if coalitionName then
        coalitionName = coalitionName:lower()
    else
        return nil
    end

    if not veaf.countriesByCoalition then
        _initializeCountriesAndCoalitions()
    end

    return veaf.countriesByCoalition[coalitionName][1]
end

function veaf.getCoalitionForCountry(countryName, asNumber)
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.getCoalitionForCountry(countryName=%s, asNumber=%s)", tostring(countryName), tostring(asNumber)))

    if countryName then
        countryName = countryName:lower()
    else
        return nil
    end

    if not veaf.coalitionByCountry then
        _initializeCountriesAndCoalitions()
    end

    local result = veaf.coalitionByCountry[countryName]
    if asNumber then
        if result == 'neutral' then result = 0 end
        if result == 'red' then result = 1 end
        if result == 'blue' then result = 2 end
    end
    return result
end

function veaf.getAirbaseForCoalition(airbase_name, coa)
    local airbase = nil

-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.getAirbaseforCoalition(airbase_name = %s, coa = %s)", veaf.p(airbase_name), veaf.p(coa)))
    if coa and airbase_name then

        if type(coa) == 'string' then
            if coa:lower() == "red" then
                coa = coalition.side.RED
            elseif coa:lower() == "blue" then
                coa = coalition.side.BLUE
            end
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("final coalition is = %s", veaf.p(coa)))

        if (coa == coalition.side.RED or coa == coalition.side.BLUE) and type(airbase_name) == 'string' then
            local temp = Airbase.getByName(airbase_name)
-- LOGGING DISABLED WHEN COMPILING(string.format("Associed Airbase ID : %s", veaf.p(temp)))

            if temp then
-- LOGGING DISABLED WHEN COMPILING(string.format("Associed Airbase Coalition : %s", veaf.p(temp:getCoalition())))
                if temp:getCoalition() == coa then
-- LOGGING DISABLED WHEN COMPILING(string.format("The Airbase was found and is held by the correct coalition"))
                    airbase = temp
                end
            end
        end
    end

    return airbase
end

veaf.AIRBASES_LIFE0 = {}
veaf.STANDARD_CARRIER_LIFE0 = 1000 --this fluctuates a lot from ship to ship, took the lowest
veaf.STANDARD_AIRBASE_LIFE0 = 3600
veaf.STANDARD_HELIPAD_LIFE0 = 10000000
veaf.STANDARD_BUILDING_LIFE0 = 3600

function veaf.loadAirbasesLife0()
    local airbases = world.getAirbases()
-- LOGGING DISABLED WHEN COMPILING(string.format("Loading Life0 of airbases..."))

    for _,airbase in pairs(airbases) do
        local airbase_name = airbase:getName()
-- LOGGING DISABLED WHEN COMPILING(string.format("Checking airbase named %s", veaf.p(airbase_name)))
        veaf.AIRBASES_LIFE0[airbase_name] = veaf.getAirbaseLife(airbase_name, false, true)

        if veaf.AIRBASES_LIFE0[airbase_name] == 0 then
-- LOGGING DISABLED WHEN COMPILING(string.format("Returned Life0 is 0, discarding result"))
            veaf.AIRBASES_LIFE0[airbase_name] = nil
        end
    end
end

--This method is used to get the life of any airbase/FARP/Carrier/HeloCarrier etc. through it's unit name. You can choose to have the life returned as a percentage (0 to 1) and also to not automatically adjust/store the maximum lifes of the airbases you might check through loading = true (loading mode is used for the function veaf.loadAirbasesLife0())
--Beware that, some airbases do not posses a life or a life0 to calculate a percentage. This method will return -1 if so.
function veaf.getAirbaseLife(airbase_name, percentage, loading)
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.getAirbaseLife(airbase_name = %s, percentage = %s, loading = %s)", veaf.p(airbase_name), veaf.p(percentage), veaf.p(loading)))

    local airbase_life = -1
    local airbase_life0 = -1

    if airbase_name and type(airbase_name) == 'string' then
        local airbase = Airbase.getByName(airbase_name)
-- LOGGING DISABLED WHEN COMPILING(string.format("Airbase ID : %s", veaf.p(airbase)))

        if airbase then
            local airbase_desc = airbase:getDesc()
-- LOGGING DISABLED WHEN COMPILING(string.format("Airbase Desc : %s", veaf.p(airbase_desc)))

            if airbase_desc and airbase_desc.life and airbase_desc.attributes then
                airbase_life0 = veaf.AIRBASES_LIFE0[airbase_name]
                airbase_life = airbase_desc.life

                -- local AirbaseUnit = StaticObject.getByName(airbase_name)
                -- if AirbaseUnit then
-- LOGGING DISABLED WHEN COMPILING(string.format("Got an AirbaseUnit through StaticObject.getByName(), associated life is %s", veaf.p(AirbaseUnit:getLife())))
                -- end

                if airbase_desc.attributes["AircraftCarrier"] or airbase_desc.attributes["Aircraft Carriers"] or airbase_desc.attributes["HelicopterCarrier"] then
                    local AircraftCarrier_unit = Unit.getByName(airbase_name)
-- LOGGING DISABLED WHEN COMPILING(string.format("Airbase is a Carrier Unit ID : %s", veaf.p(AircraftCarrier_unit)))

                    if AircraftCarrier_unit then
                        --airbase_life0 = AircraftCarrier_unit:getLife0()  --returns 0, thanks ED, had to load them at mission start to counter this issue
                        if not airbase_life0 then
                            airbase_life0 = veaf.STANDARD_CARRIER_LIFE0
-- LOGGING DISABLED WHEN COMPILING(string.format("Carrier doesn't have a Life0 stored yet, using default of %s", veaf.p(veaf.STANDARD_CARRIER_LIFE0)))
                        end
                        airbase_life = AircraftCarrier_unit:getLife()
-- LOGGING DISABLED WHEN COMPILING(string.format("Carrier Life : %s", veaf.p(airbase_life)))
                    end
                elseif airbase_desc.attributes["Helipad"] and not airbase_life0 then
                    airbase_life0 = veaf.STANDARD_HELIPAD_LIFE0
-- LOGGING DISABLED WHEN COMPILING(string.format("Helipad doesn't have a Life0 stored yet, using default of %s", veaf.p(veaf.STANDARD_HELIPAD_LIFE0)))
                elseif airbase_desc.attributes["Airfields"] and not airbase_life0 then
                    airbase_life0 = veaf.STANDARD_AIRBASE_LIFE0
-- LOGGING DISABLED WHEN COMPILING(string.format("Airfield doesn't have a Life0 stored yet, using default of %s", veaf.p(veaf.STANDARD_AIRBASE_LIFE0)))
                elseif airbase_desc.attributes["Buildings"] then
                    local BuildingUnit = StaticObject.getByName(airbase_name)
-- LOGGING DISABLED WHEN COMPILING(string.format("Airbase is a Building Unit ID : %s", veaf.p(BuildingUnit)))

                    if BuildingUnit then
                        if not airbase_life0 then
                            airbase_life0 = veaf.STANDARD_BUILDING_LIFE0
-- LOGGING DISABLED WHEN COMPILING(string.format("Building doesn't have a Life0 stored yet, using default of %s", veaf.p(veaf.STANDARD_BUILDING_LIFE0)))
                        end
                        airbase_life = BuildingUnit:getLife()
-- LOGGING DISABLED WHEN COMPILING(string.format("Building Life : %s", veaf.p(airbase_life)))
                    else
                        airbase_life0 = -1
                        airbase_life = -1
-- LOGGING DISABLED WHEN COMPILING(string.format("Building that is an airbase doesn't have any life data, discarding"))
                    end
                elseif not airbase_life0 then
                    if airbase_life > 0 then
                        airbase_life0 = airbase_life
-- LOGGING DISABLED WHEN COMPILING(string.format("Airbase category does not have a default life0 setting, using life instead"))
                    else
                        airbase_life = -1
                        airbase_life0 = -1
-- LOGGING DISABLED WHEN COMPILING(string.format("Airbase category does not have a default life0 setting nor does it have a life, discarding"))
                    end
                end

-- LOGGING DISABLED WHEN COMPILING(string.format("Airbase Life : %s, Airbase Life0 : %s", veaf.p(airbase_life), veaf.p(airbase_life0)))
            end
        end
    end

    if airbase_life0 and airbase_life0 > 0 and airbase_life and airbase_life > 0 then
        local airbase_life_percentage = airbase_life/airbase_life0

        if not loading then
            --if the airbase life percentage is superior to 100%, there standard life0 chosen was obviously wrong and needs updating
            if airbase_life_percentage > 1 then
                airbase_life_percentage = 1
                veaf.AIRBASES_LIFE0[airbase_name] = airbase_life
-- LOGGING DISABLED WHEN COMPILING(string.format("Storing Life0 = Life for airbase..."))
            elseif not veaf.AIRBASES_LIFE0[airbase_name] then
                veaf.AIRBASES_LIFE0[airbase_name] = airbase_life0
-- LOGGING DISABLED WHEN COMPILING(string.format("Storing default Life0 for airbase type..."))
            end
        end

        if percentage then
            airbase_life = airbase_life_percentage
        end
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("Final Airbase (named %s) Life : %s, isPercentage = %s", veaf.p(airbase_name), veaf.p(airbase_life), veaf.p(percentage)))
    return airbase_life
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- mission restart at a certain hour of the day
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veaf._endMission(delay1, message1, delay2, message2, delay3, message3)
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf._endMission(delay1=%s, message1=%s, delay2=%s, message2=%s, delay3=%s, message3=%s)", veaf.p(delay1), veaf.p(message1), veaf.p(delay2), veaf.p(message2), veaf.p(delay3), veaf.p(message3)))

    if not delay1 then
        -- no more delay, let's end this !
        trigger.action.outText("Ending mission !",30)
        veaf.loggers.get(veaf.Id):info("ending mission")
        trigger.action.setUserFlag("666", 1)
    else
        -- show the message
        trigger.action.outText(message1,30)
        -- schedule this function after "delay1" seconds
        veaf.loggers.get(veaf.Id):info(string.format("schedule veaf._endMission after %d seconds", delay1))
        mist.scheduleFunction(veaf._endMission, {delay2, message2, delay3, message3}, timer.getTime()+delay1)
    end
end

function veaf._checkForEndMission(endTimeInSeconds, checkIntervalInSeconds, checkMessage, delay1, message1, delay2, message2, delay3, message3)
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf._checkForEndMission(endTimeInSeconds=%s, checkIntervalInSeconds=%s, checkMessage=%s, delay1=%s, message1=%s, delay2=%s, message2=%s, delay3=%s, message3=%s)", veaf.p(endTimeInSeconds), veaf.p(checkIntervalInSeconds), veaf.p(checkMessage), veaf.p(delay1), veaf.p(message1), veaf.p(delay2), veaf.p(message2), veaf.p(delay3), veaf.p(message3)))

-- LOGGING DISABLED WHEN COMPILING(string.format("timer.getAbsTime()=%d", timer.getAbsTime()))

    if timer.getAbsTime() >= endTimeInSeconds then
-- LOGGING DISABLED WHEN COMPILING("calling veaf._endMission")
        veaf._endMission(delay1, message1, delay2, message2, delay3, message3)
    else
        -- output the message if specified
        if checkMessage then
            trigger.action.outText(checkMessage,30)
        end
        -- schedule this function after a delay
-- LOGGING DISABLED WHEN COMPILING(string.format("schedule veaf._checkForEndMission after %d seconds", checkIntervalInSeconds))
        mist.scheduleFunction(veaf._checkForEndMission, {endTimeInSeconds, checkIntervalInSeconds, checkMessage, delay1, message1, delay2, message2, delay3, message3}, timer.getTime()+checkIntervalInSeconds)
    end
end

function veaf.endMissionAt(endTimeHour, endTimeMinute, checkIntervalInSeconds, checkMessage, delay1, message1, delay2, message2, delay3, message3)
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.endMissionAt(endTimeHour=%s, endTimeMinute=%s, checkIntervalInSeconds=%s, checkMessage=%s, delay1=%s, message1=%s, delay2=%s, message2=%s, delay3=%s, message3=%s)", veaf.p(endTimeHour), veaf.p(endTimeMinute), veaf.p(checkIntervalInSeconds), veaf.p(checkMessage), veaf.p(delay1), veaf.p(message1), veaf.p(delay2), veaf.p(message2), veaf.p(delay3), veaf.p(message3)))

    local endTimeInSeconds = endTimeHour * 3600 + endTimeMinute * 60
-- LOGGING DISABLED WHEN COMPILING(string.format("endTimeInSeconds=%d", endTimeInSeconds))
    veaf._checkForEndMission(endTimeInSeconds, checkIntervalInSeconds, checkMessage, delay1, message1, delay2, message2, delay3, message3)
end

function veaf.randomlyChooseFrom(aTable, bias)   
-- LOGGING DISABLED WHEN COMPILING(string.format("randomlyChooseFrom(%d):%s",bias or 0, veaf.p(aTable)))
    if aTable == nil or #aTable == 0 then
        return nil
    elseif #aTable == 1 then
        return aTable[1]
    end
    local index = math.floor(math.random(1, #aTable)) + (bias or 0)
    if index < 1 then index = 1 end
    if index > #aTable then index = #aTable end
-- LOGGING DISABLED WHEN COMPILING(string.format("index = %s", veaf.p(index)))
    return aTable[index]
end

function veaf.safeUnpack(package)
    if type(package) == 'table' then
        return unpack(package)
    else
        return package
    end
end

function veaf.getRandomizableNumeric_random(val)
-- LOGGING DISABLED WHEN COMPILING(string.format("getRandomizableNumeric_random(%s)", tostring(val)))
    local MIN = 0
    local MAX = 99
    local nVal = tonumber(val)
-- LOGGING DISABLED WHEN COMPILING("nVal=%s", veaf.p(nVal))
    if nVal == nil then
        local dashPos = string.find(val,"%-")
-- LOGGING DISABLED WHEN COMPILING("dashPos=%s", veaf.p(dashPos))
        if dashPos then
            local lower = val:sub(1, dashPos-1)
-- LOGGING DISABLED WHEN COMPILING("lower=%s", veaf.p(lower))
            if lower then
                lower = tonumber(lower)
            end
            if lower == nil then
                lower = MIN
            end
            local upper = val:sub(dashPos+1)
-- LOGGING DISABLED WHEN COMPILING("upper=%s", veaf.p(upper))
            if upper then
                upper = tonumber(upper)
            end
            if upper == nil then
                upper = MAX
            end
            nVal = math.random(lower, upper)
-- LOGGING DISABLED WHEN COMPILING("nVal=%s", veaf.p(nVal))
        end
    end
        --[[ 

        if val == "0-1" then nVal = math.random(0,1) end
        if val == "0-2" then nVal = math.random(0,2) end
        if val == "0-3" then nVal = math.random(0,3) end
        if val == "0-4" then nVal = math.random(0,4) end
        if val == "0-5" then nVal = math.random(0,5) end
        if val == "0-6" then nVal = math.random(0,6) end
        if val == "0-7" then nVal = math.random(0,7) end
        if val == "0-8" then nVal = math.random(0,8) end
        if val == "0-9" then nVal = math.random(0,9) end
        if val == "0-10" then nVal = math.random(0,10) end
        if val == "0-11" then nVal = math.random(0,11) end
        if val == "0-12" then nVal = math.random(0,12) end
        if val == "0-13" then nVal = math.random(0,13) end
        if val == "0-14" then nVal = math.random(0,14) end
        if val == "0-15" then nVal = math.random(0,15) end
        if val == "0-16" then nVal = math.random(0,16) end
        if val == "0-17" then nVal = math.random(0,17) end
        if val == "0-18" then nVal = math.random(0,18) end
        if val == "0-19" then nVal = math.random(0,19) end

        if val == "1-2" then nVal = math.random(1,2) end
        if val == "1-3" then nVal = math.random(1,3) end
        if val == "1-4" then nVal = math.random(1,4) end
        if val == "1-5" then nVal = math.random(1,5) end
        if val == "1-6" then nVal = math.random(1,6) end
        if val == "1-7" then nVal = math.random(1,7) end
        if val == "1-8" then nVal = math.random(1,8) end
        if val == "1-9" then nVal = math.random(1,9) end
        if val == "1-10" then nVal = math.random(1,10) end
        if val == "1-11" then nVal = math.random(1,11) end
        if val == "1-12" then nVal = math.random(1,12) end
        if val == "1-13" then nVal = math.random(1,13) end
        if val == "1-14" then nVal = math.random(1,14) end
        if val == "1-15" then nVal = math.random(1,15) end
        if val == "1-16" then nVal = math.random(1,16) end
        if val == "1-17" then nVal = math.random(1,17) end
        if val == "1-18" then nVal = math.random(1,18) end
        if val == "1-19" then nVal = math.random(1,19) end

        if val == "2-3" then nVal = math.random(2,3) end
        if val == "2-4" then nVal = math.random(2,4) end
        if val == "2-5" then nVal = math.random(2,5) end
        if val == "2-6" then nVal = math.random(2,6) end
        if val == "2-7" then nVal = math.random(2,7) end
        if val == "2-8" then nVal = math.random(2,8) end
        if val == "2-9" then nVal = math.random(2,9) end
        if val == "2-10" then nVal = math.random(2,10) end
        if val == "2-11" then nVal = math.random(2,11) end
        if val == "2-12" then nVal = math.random(2,12) end
        if val == "2-13" then nVal = math.random(2,13) end
        if val == "2-14" then nVal = math.random(2,14) end
        if val == "2-15" then nVal = math.random(2,15) end
        if val == "2-16" then nVal = math.random(2,16) end
        if val == "2-17" then nVal = math.random(2,17) end
        if val == "2-18" then nVal = math.random(2,18) end
        if val == "2-19" then nVal = math.random(2,19) end

        if val == "3-4" then nVal = math.random(3,4) end
        if val == "3-5" then nVal = math.random(3,5) end
        if val == "3-6" then nVal = math.random(3,6) end
        if val == "3-7" then nVal = math.random(3,7) end
        if val == "3-8" then nVal = math.random(3,8) end
        if val == "3-9" then nVal = math.random(3,9) end
        if val == "3-10" then nVal = math.random(3,10) end
        if val == "3-11" then nVal = math.random(3,11) end
        if val == "3-12" then nVal = math.random(3,12) end
        if val == "3-13" then nVal = math.random(3,13) end
        if val == "3-14" then nVal = math.random(3,14) end
        if val == "3-15" then nVal = math.random(3,15) end
        if val == "3-16" then nVal = math.random(3,16) end
        if val == "3-17" then nVal = math.random(3,17) end
        if val == "3-18" then nVal = math.random(3,18) end
        if val == "3-19" then nVal = math.random(3,19) end

        if val == "4-5" then nVal = math.random(4,5) end
        if val == "4-6" then nVal = math.random(4,6) end
        if val == "4-7" then nVal = math.random(4,7) end
        if val == "4-8" then nVal = math.random(4,8) end
        if val == "4-9" then nVal = math.random(4,9) end
        if val == "4-10" then nVal = math.random(4,10) end
        if val == "4-11" then nVal = math.random(4,11) end
        if val == "4-12" then nVal = math.random(4,12) end
        if val == "4-13" then nVal = math.random(4,13) end
        if val == "4-14" then nVal = math.random(4,14) end
        if val == "4-15" then nVal = math.random(4,15) end
        if val == "4-16" then nVal = math.random(4,16) end
        if val == "4-17" then nVal = math.random(4,17) end
        if val == "4-18" then nVal = math.random(4,18) end
        if val == "4-19" then nVal = math.random(4,19) end

        if val == "5-6" then nVal = math.random(5,6) end
        if val == "5-7" then nVal = math.random(5,7) end
        if val == "5-8" then nVal = math.random(5,8) end
        if val == "5-9" then nVal = math.random(5,9) end
        if val == "5-10" then nVal = math.random(5,10) end
        if val == "5-11" then nVal = math.random(5,11) end
        if val == "5-12" then nVal = math.random(5,12) end
        if val == "5-13" then nVal = math.random(5,13) end
        if val == "5-14" then nVal = math.random(5,14) end
        if val == "5-15" then nVal = math.random(5,15) end
        if val == "5-16" then nVal = math.random(5,16) end
        if val == "5-17" then nVal = math.random(5,17) end
        if val == "5-18" then nVal = math.random(5,18) end
        if val == "5-19" then nVal = math.random(5,19) end

        if val == "6-7" then nVal = math.random(6,7) end
        if val == "6-8" then nVal = math.random(6,8) end
        if val == "6-9" then nVal = math.random(6,9) end
        if val == "6-10" then nVal = math.random(6,10) end
        if val == "6-11" then nVal = math.random(6,11) end
        if val == "6-12" then nVal = math.random(6,12) end
        if val == "6-13" then nVal = math.random(6,13) end
        if val == "6-14" then nVal = math.random(6,14) end
        if val == "6-15" then nVal = math.random(6,15) end
        if val == "6-16" then nVal = math.random(6,16) end
        if val == "6-17" then nVal = math.random(6,17) end
        if val == "6-18" then nVal = math.random(6,18) end
        if val == "6-19" then nVal = math.random(6,19) end

        if val == "7-8" then nVal = math.random(7,8) end
        if val == "7-9" then nVal = math.random(7,9) end
        if val == "7-10" then nVal = math.random(7,10) end
        if val == "7-11" then nVal = math.random(7,11) end
        if val == "7-12" then nVal = math.random(7,12) end
        if val == "7-13" then nVal = math.random(7,13) end
        if val == "7-14" then nVal = math.random(7,14) end
        if val == "7-15" then nVal = math.random(7,15) end
        if val == "7-16" then nVal = math.random(7,16) end
        if val == "7-17" then nVal = math.random(7,17) end
        if val == "7-18" then nVal = math.random(7,18) end
        if val == "7-19" then nVal = math.random(7,19) end

        if val == "8-9" then nVal = math.random(8,9) end
        if val == "8-10" then nVal = math.random(8,10) end
        if val == "8-11" then nVal = math.random(8,11) end
        if val == "8-12" then nVal = math.random(8,12) end
        if val == "8-13" then nVal = math.random(8,13) end
        if val == "8-14" then nVal = math.random(8,14) end
        if val == "8-15" then nVal = math.random(8,15) end
        if val == "8-16" then nVal = math.random(8,16) end
        if val == "8-17" then nVal = math.random(8,17) end
        if val == "8-18" then nVal = math.random(8,18) end
        if val == "8-19" then nVal = math.random(8,19) end

        if val == "9-10" then nVal = math.random(9,10) end
        if val == "9-11" then nVal = math.random(9,11) end
        if val == "9-12" then nVal = math.random(9,12) end
        if val == "9-13" then nVal = math.random(9,13) end
        if val == "9-14" then nVal = math.random(9,14) end
        if val == "9-15" then nVal = math.random(9,15) end
        if val == "9-16" then nVal = math.random(9,16) end
        if val == "9-17" then nVal = math.random(9,17) end
        if val == "9-18" then nVal = math.random(9,18) end
        if val == "9-19" then nVal = math.random(9,19) end

        if val == "10-11" then nVal = math.random(10,11) end
        if val == "10-12" then nVal = math.random(10,12) end
        if val == "10-13" then nVal = math.random(10,13) end
        if val == "10-14" then nVal = math.random(10,14) end
        if val == "10-15" then nVal = math.random(10,15) end
        if val == "10-16" then nVal = math.random(10,16) end
        if val == "10-17" then nVal = math.random(10,17) end
        if val == "10-18" then nVal = math.random(10,18) end
        if val == "10-19" then nVal = math.random(10,19) end

        if val == "11-12" then nVal = math.random(11,12) end
        if val == "11-13" then nVal = math.random(11,13) end
        if val == "11-14" then nVal = math.random(11,14) end
        if val == "11-15" then nVal = math.random(11,15) end
        if val == "11-16" then nVal = math.random(11,16) end
        if val == "11-17" then nVal = math.random(11,17) end
        if val == "11-18" then nVal = math.random(11,18) end
        if val == "11-19" then nVal = math.random(11,19) end

        if val == "12-13" then nVal = math.random(12,13) end
        if val == "12-14" then nVal = math.random(12,14) end
        if val == "12-15" then nVal = math.random(12,15) end
        if val == "12-16" then nVal = math.random(12,16) end
        if val == "12-17" then nVal = math.random(12,17) end
        if val == "12-18" then nVal = math.random(12,18) end
        if val == "12-19" then nVal = math.random(12,19) end

        if val == "13-14" then nVal = math.random(13,14) end
        if val == "13-15" then nVal = math.random(13,15) end
        if val == "13-16" then nVal = math.random(13,16) end
        if val == "13-17" then nVal = math.random(13,17) end
        if val == "13-18" then nVal = math.random(13,18) end
        if val == "13-19" then nVal = math.random(13,19) end

        if val == "14-15" then nVal = math.random(14,15) end
        if val == "14-16" then nVal = math.random(14,16) end
        if val == "14-17" then nVal = math.random(14,17) end
        if val == "14-18" then nVal = math.random(14,18) end
        if val == "14-19" then nVal = math.random(14,19) end

        if val == "15-16" then nVal = math.random(15,16) end
        if val == "15-17" then nVal = math.random(15,17) end
        if val == "15-18" then nVal = math.random(15,18) end
        if val == "15-19" then nVal = math.random(15,19) end

        if val == "16-17" then nVal = math.random(16,17) end
        if val == "16-18" then nVal = math.random(16,18) end
        if val == "16-19" then nVal = math.random(16,19) end

        if val == "17-18" then nVal = math.random(17,18) end
        if val == "17-19" then nVal = math.random(17,19) end

        if val == "18-19" then nVal = math.random(18,19) end
        ]]

-- LOGGING DISABLED WHEN COMPILING(string.format("nVal=%s", tostring(nVal)))
    return nVal
end

function veaf.getRandomizableNumeric_norandom(val)
-- LOGGING DISABLED WHEN COMPILING(string.format("getRandomizableNumeric_norandom(%s)", tostring(val)))
    local nVal = tonumber(val)
-- LOGGING DISABLED WHEN COMPILING(string.format("nVal=%s", tostring(nVal)))
    if nVal == nil then
        if val == "1-2" then nVal = 2 end
        if val == "1-3" then nVal = 3 end
        if val == "1-4" then nVal = 3 end
        if val == "1-5" then nVal = 3 end

        if val == "2-3" then nVal = 2 end
        if val == "2-4" then nVal = 3 end
        if val == "2-5" then nVal = 3 end

        if val == "3-4" then nVal = 3 end
        if val == "3-5" then nVal = 4 end

        if val == "4-5" then nVal = 4 end

        if val == "5-10" then nVal = 7 end

        if val == "10-15" then nVal = 12 end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("nVal=%s", tostring(nVal)))
    return nVal
end

function veaf.getRandomizableNumeric(val)
-- LOGGING DISABLED WHEN COMPILING(string.format("getRandomizableNumeric(%s)", tostring(val)))
    return veaf.getRandomizableNumeric_random(val)
end

function veaf.writeLineToTextFile(line, filename, filepath)
    local l_lfs = lfs
    if not l_lfs and SERVER_CONFIG and SERVER_CONFIG.getModule then
        l_lfs = SERVER_CONFIG.getModule("lfs")
    end

    local l_io = io
    if not l_io and SERVER_CONFIG and SERVER_CONFIG.getModule then
        l_io = SERVER_CONFIG.getModule("io")
    end

    local l_os = os
    if not l_os and SERVER_CONFIG and SERVER_CONFIG.getModule then
        l_os = SERVER_CONFIG.getModule("os")
    end

    local l_filepath = filepath
    if not l_filepath and l_os then
        l_filepath = l_os.getenv("VEAF_EXPORT_DIR")
        if l_filepath then l_filepath = l_filepath .. "\\" end
-- LOGGING DISABLED WHEN COMPILING(string.format("filepath=%s", veaf.p(l_filepath)))
    end
    if not l_filepath and l_lfs then
        l_filepath = l_lfs.writedir()
-- LOGGING DISABLED WHEN COMPILING(string.format("filepath=%s", veaf.p(l_filepath)))
    end
    if not l_filepath and l_os then
        l_filepath = l_os.getenv("TEMP")
        if l_filepath then l_filepath = l_filepath .. "\\" end
-- LOGGING DISABLED WHEN COMPILING(string.format("filepath=%s", veaf.p(l_filepath)))
    end
    if l_filepath == "SERVER_SAVEDGAMES_DIR" then
        l_filepath = l_lfs.writedir()
-- LOGGING DISABLED WHEN COMPILING(string.format("filepath=%s", veaf.p(l_filepath)))
    end

    if not l_filepath then
        return
    end

    local l_filename = l_filepath .. (filename or "default.log")

    local date = ""
    if l_os then
        date = tostring(l_os.date('%Y-%m-%d %H:%M:%S.000'))
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("filename=%s", veaf.p(l_filename)))
    local file = l_io.open(l_filename, "a")
    if file then
-- LOGGING DISABLED WHEN COMPILING(string.format("file:write(%s)", veaf.p(line)))
        file:write(string.format("[%s] %s\r\n", date, line))
        file:close()
    end
end

function veaf.exportAsJson(data, name, jsonify, filename, export_path)

    local l_lfs = lfs
    if not l_lfs and SERVER_CONFIG and SERVER_CONFIG.getModule then
        l_lfs = SERVER_CONFIG.getModule("lfs")
    end

    local l_io = io
    if not l_io and SERVER_CONFIG and SERVER_CONFIG.getModule then
        l_io = SERVER_CONFIG.getModule("io")
    end

    local l_os = os
    if not l_os and SERVER_CONFIG and SERVER_CONFIG.getModule then
        l_os = SERVER_CONFIG.getModule("os")
    end

    local l_export_path = export_path
    if not l_export_path and l_os then
        l_export_path = l_os.getenv("VEAF_EXPORT_DIR")
        if l_export_path then l_export_path = l_export_path .. "\\" end
-- LOGGING DISABLED WHEN COMPILING(string.format("filepath=%s", veaf.p(l_export_path)))
    end
    if not l_export_path and l_lfs then
        l_export_path = l_lfs.writedir()
-- LOGGING DISABLED WHEN COMPILING(string.format("filepath=%s", veaf.p(l_export_path)))
    end
    if not l_export_path and l_os then
        l_export_path = l_os.getenv("TEMP")
        if l_export_path then l_export_path = l_export_path .. "\\" end
-- LOGGING DISABLED WHEN COMPILING(string.format("filepath=%s", veaf.p(l_export_path)))
    end
    if l_export_path == "SERVER_SAVEDGAMES_DIR" then
        l_export_path = l_lfs.writedir()
-- LOGGING DISABLED WHEN COMPILING(string.format("filepath=%s", veaf.p(l_export_path)))
    end

    if not l_export_path then
        return
    end

    local function writeln(file, text)
        file:write(text.."\r\n")
    end

    local filename = filename or name .. ".json"
-- LOGGING DISABLED WHEN COMPILING(string.format("filename=%s", veaf.p(filename)))

    veaf.loggers.get(veaf.Id):info("Dumping ".. name .." as json to "..filename .. " in ".. l_export_path)

    local header =    '{\n'
    header = header .. '  "' .. name .. '": [\n'

    local content = {}
    for key, value in pairs(data) do
        local line =  jsonify(key, value)
-- LOGGING DISABLED WHEN COMPILING("line=%s", veaf.p(line))
        table.insert(content, line)
    end
    local footer =    '\n'
    footer = footer .. ']\n'
    footer = footer .. '}\n'

    local file = l_io.open(l_export_path .. filename, "w")
    writeln(file, header)
    writeln(file, table.concat(content, ",\n"))
    writeln(file, footer)
    if file then file:close() end
end

function veaf.isUnitAlive(unit)
    return unit and unit:isExist() and unit:isActive()
end

function veaf.getUnitLifeRelative(unit)
    if unit and veaf.isUnitAlive(unit) then
        local unitLife=unit:getLife()
        local unitLife0 = 0
        if unit.getLife0 then -- statics have no life0
          unitLife0 = unit:getLife0()
        end
        if unitLife0 > 0 then
            return unitLife/unitLife0
        else
            return unitLife
        end
    else
        return 0
    end
end

function veaf.setServerName(value)
    veaf.config.SERVER_NAME = value
end

function veaf.getPolygonFromUnits(unitNames)

-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.getPolygonFromUnits()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("unitNames = %s", veaf.p(unitNames)))
    local polygon = {}
    for _, unitName in pairs(unitNames) do
-- LOGGING DISABLED WHEN COMPILING(string.format("unitName = %s", veaf.p(unitName)))
        local unit = Unit.getByName(unitName)
        if not unit then
            local group = Group.getByName(unitName)
            if group then
                unit = group:getUnit(1)
            end
        end
        if unit then
            -- get position, place tracing marker and remove the unit
            local position = unit:getPosition().p
            unit:destroy()
-- LOGGING DISABLED WHEN COMPILING(string.format("position = %s", veaf.p(position)))
            table.insert(polygon, mist.utils.deepCopy(position))
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("polygon = %s", veaf.p(polygon)))
    return polygon
end

function veaf.laserCodeToDigit(code)
    local codeDigit = {}
    codeDigit.units=code%10
    codeDigit.tens=(code%100-codeDigit.units)/10
    codeDigit.hundreds=(code%1000-codeDigit.tens*10-codeDigit.units)/100
    codeDigit.thousands=(code-codeDigit.hundreds*100-codeDigit.tens*10-codeDigit.units)/1000

-- LOGGING DISABLED WHEN COMPILING(string.format("laser code : %s", veaf.p(code)))
-- LOGGING DISABLED WHEN COMPILING(string.format("laser code digits : %s", veaf.p(codeDigit)))

    return codeDigit
end

--computes the heading between two points in radians
function veaf.headingBetweenPoints(point1, point2)

    local hdg

    if point1 and point2 and point1.x and point1.y and point2.x and point2.y then
        -- if hdg is not set, compute heading between point2 and point3
        hdg = math.floor(math.deg(math.atan2(point2.y - point1.y, point2.x - point1.x)))
        if hdg < 0 then
            hdg = hdg + 360
        end
    end

    -- convert heading to radians
    hdg = hdg * math.pi / 180

    return hdg

end

---checks if a string starts with a prefix
---@param aString any
---@param aPrefix any
---@param caseSensitive? boolean   ; if true, case sensitive search
---@return boolean
function veaf.startsWith(aString, aPrefix, caseSensitive)
    local aString = aString
    if not aString then
        veaf.loggers.get(veaf.Id):error("veaf.startsWith: parameter aString is mandatory")
        return false
    elseif not caseSensitive then
        aString = aString:upper()
    end
    local aPrefix = aPrefix
    if not aPrefix then
        veaf.loggers.get(veaf.Id):error("veaf.startsWith: parameter aPrefix is mandatory")
        return false
    elseif not caseSensitive then
        aPrefix = aPrefix:upper()
    end
    return string.sub(aString,1,string.len(aPrefix))==aPrefix
end

function veaf.getDcsTypeName(dcsElementName)
-- LOGGING DISABLED WHEN COMPILING("veaf.getDcsTypeName(dcsElementName=%s", veaf.p(dcsElementName))

    local result = "unknown"

    if (not veaf.isNullOrEmpty(dcsElementName)) then
        -- first check for a unit named like this, because the group and its units may have the same name
        local dcsUnit = Unit.getByName(dcsElementName)
-- LOGGING DISABLED WHEN COMPILING("Unit.getByName(dcsElementName)=%s", veaf.p(dcsUnit))
        if not dcsUnit then
            -- then check for a group named like that
            local dcsGroup = Group.getByName(dcsElementName)
-- LOGGING DISABLED WHEN COMPILING("Group.getByName(dcsElementName)=%s", veaf.p(dcsGroup))
            dcsUnit = dcsGroup and dcsGroup:getUnit(1)
        end
        if (dcsUnit) then
-- LOGGING DISABLED WHEN COMPILING("dcsUnit=%s", veaf.p(dcsUnit, nil, nil, true, false))
            result = dcsUnit:getTypeName()
        end
    end

-- LOGGING DISABLED WHEN COMPILING("result=%s", veaf.p(result))

    return result
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Logging
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veaf.loggers = {}
veaf.loggers.dict = {}

veaf.Logger =
{
    -- technical name
    name = nil,
    -- logging level
    level = nil,
}
veaf.Logger.__index = veaf.Logger

veaf.Logger.LEVEL = {
    ["error"]=1,
    ["warning"]=2,
    ["info"]=3,
    ["debug"]=4,
    ["trace"]=5,
}

function veaf.Logger:new(name, level)
    local self = setmetatable({}, veaf.Logger)
    self:setName(name)
    self:setLevel(level)
    return self
end

function veaf.Logger:setName(value)
    self.name = value
    return self
end

function veaf.Logger:getName()
    return self.name
end

function veaf.Logger:setLevel(value, force)
    if veaf.ForcedLogLevel then
        value = veaf.ForcedLogLevel
    end
    local level = value
    if type(level) == "string" then
        level = veaf.Logger.LEVEL[level:lower()]
    end
    if not level then
        level = veaf.Logger.LEVEL["info"]
    end
    if veaf.BaseLogLevel < level and not force then
        level = veaf.BaseLogLevel
    end
    self.level = level
    return self
end

function veaf.Logger:getLevel()
    return self.level
end

function veaf.Logger.splitText(text)
    local tbl = {}
    while text:len() > 4000 do
        local sub = text:sub(1, 4000)
        text = text:sub(4001)
        table.insert(tbl, sub)
    end
    table.insert(tbl, text)
    return tbl
end

function veaf.Logger.formatText(text, ...)
    if not text then
        return ""
    end
    if type(text) ~= 'string' then
        text = veaf.p(text)
    else
        local args = ...
        if args and args.n and args.n > 0 then
            local pArgs = {}
            for i=1,args.n do
                pArgs[i] = veaf.p(args[i])
            end
                text = text:format(unpack(pArgs))
            end
        end
    local fName = nil
    local cLine = nil
    if debug and debug.getinfo then
        local dInfo = debug.getinfo(3)
        fName = dInfo.name
        cLine = dInfo.currentline
        -- local fsrc = dinfo.short_src
        --local fLine = dInfo.linedefined
    end
    if fName and cLine then
        return fName .. '|' .. cLine .. ': ' .. text
    elseif cLine then
        return cLine .. ': ' .. text
    else
        return ' ' .. text
    end
end

function veaf.Logger:print(level, text)
    local texts = veaf.Logger.splitText(text)
    local levelChar = 'E'
    local logFunction = env.error
    if level == veaf.Logger.LEVEL["warning"] then
        levelChar = 'W'
        logFunction = env.warning
    elseif level == veaf.Logger.LEVEL["info"] then
        levelChar = 'I'
        logFunction = env.info
    elseif level == veaf.Logger.LEVEL["debug"] then
        levelChar = 'D'
        logFunction = env.info
    elseif level == veaf.Logger.LEVEL["trace"] then
        levelChar = 'T'
        logFunction = env.info
    end
    for i = 1, #texts do
        if i == 1 then
            logFunction(self.name .. '|' .. levelChar .. '|' .. texts[i])
        else
            logFunction(texts[i])
        end
    end
end

function veaf.Logger:error(text, ...)
    if self.level >= 1 then
        text = veaf.Logger.formatText(text, arg)
        local mText = text
		if debug and debug.traceback then
			mText = mText .. "\n" .. debug.traceback()
		end
        self:print(1, mText)
    end
end

function veaf.Logger:warn(text, ...)
    if self.level >= 2 then
        text = veaf.Logger.formatText(text, arg)
        self:print(2, text)
    end
end

function veaf.Logger:info(text, ...)
    if self.level >= 3 then
        text = veaf.Logger.formatText(text, arg)
        self:print(3, text)
    end
end

function veaf.Logger:debug(text, ...)
    if self.level >= 4 then
        text = veaf.Logger.formatText(text, arg)
        self:print(4, text)
    end
end

function veaf.Logger:trace(text, ...)
    if self.level >= 5 then
        text = veaf.Logger.formatText(text, arg)
        self:print(5, text)
    end
end

function veaf.Logger:wouldLogWarn()
    return self.level >= 2
end

function veaf.Logger:wouldLogInfo()
    return self.level >= 3
end

function veaf.Logger:wouldLogDebug()
    return self.level >= 4
end

function veaf.Logger:wouldLogTrace()
    return self.level >= 5
end

function veaf.Logger:marker(id, header, message, position, markersTable, radius, fillColor)
    if not id then
        id = 99999
    end
    if self.level >= 5 then
        local correctedPos = {}
        correctedPos.x = position.x
        if not(position.z) then
            correctedPos.z = position.y
            correctedPos.y = position.alt
        else
            correctedPos.z = position.z
            correctedPos.y = position.y
        end
        if not (correctedPos.y) then
            correctedPos.y = 0
        end
        local message = message
        if header and id then
            message = header..id.." "..message
        end
        self:trace("creating trace marker #%s at point %s", id, veaf.vecToString(correctedPos))
        if radius then
            trigger.action.circleToAll(-1, id, correctedPos, radius, fillColor, fillColor, 3, false)
        else
            trigger.action.markToAll(id, message, correctedPos, false)
        end
        if markersTable then
            table.insert(markersTable, id)
            --self:trace("markersTable=%s", veaf.p(markersTable))
        end
    end
    return id + 1
end

function veaf.Logger:markerArrow(id, header, message, positionStart, positionEnd, markersTable, lineType, fillColor)
    if not id then
        id = 99999
    end
    if self.level >= 5 then
        local points = { positionStart, positionEnd }
        for _, point in ipairs(points) do
            local correctedPos = {}
            correctedPos.x = point.x
            if not(point.z) then
                correctedPos.z = point.y
                correctedPos.y = point.alt
            else
                correctedPos.z = point.z
                correctedPos.y = point.y
            end
            if not (correctedPos.y) then
                correctedPos.y = 0
            end
            point.x = correctedPos.x
            point.y = correctedPos.y
            point.z = correctedPos.z
        end
        local positionStart = points[1]
        local positionEnd = points[2]

        local message = message
        if header and id then
            message = header..id.." "..message
        end

        self:trace("creating trace arrow #%s from point %s to point %s", id, veaf.vecToString(positionStart), veaf.vecToString(positionEnd))

        trigger.action.arrowToAll(-1, id, positionEnd, positionStart, fillColor, fillColor, lineType, false, message)
        if markersTable then
            table.insert(markersTable, id)
            --self:trace("markersTable=%s", veaf.p(markersTable))
        end
    end
    return id + 1
end

function veaf.Logger:markerQuad(id, header, message, points, markersTable, lineType, fillColor)
    if not id then
        id = 99999
    end
    if self.level >= 5 then
        local points = points
        for _, point in ipairs(points) do
            local correctedPos = {}
            correctedPos.x = point.x
            if not(point.z) then
                correctedPos.z = point.y
                correctedPos.y = point.alt
            else
                correctedPos.z = point.z
                correctedPos.y = point.y
            end
            if not (correctedPos.y) then
                correctedPos.y = 0
            end
            point.x = correctedPos.x
            point.y = correctedPos.y
            point.z = correctedPos.z
        end

        local message = message
        if header and id then
            message = header..id.." "..message
        end

        self:trace("creating trace quad #%s", id)

        trigger.action.quadToAll(-1, id, points[1], points[2], points[3], points[4], fillColor, fillColor, lineType, false, message)
        if markersTable then
            table.insert(markersTable, id)
            --self:trace("markersTable=%s", veaf.p(markersTable))
        end
    end
    return id + 1
end

function veaf.Logger:cleanupMarkers(markersTable)
    local n=#markersTable
    for i=1,n do
        local markerId = markersTable[i]
        markersTable[i] = nil
        self:trace("deleting trace marker #%s at pos", markerId, i)
        trigger.action.removeMark(markerId)
    end
end

function veaf.loggers.setBaseLevel(level)
    veaf.BaseLogLevel = level
    -- reset all loggers level if lower than the base level
    for name, logger in pairs(veaf.loggers.dict) do
        logger:setLevel(logger:getLevel())
    end
end

function veaf.loggers.new(loggerId, level)
    if not loggerId or #loggerId == 0 then
        return nil
    end
    local result = veaf.Logger:new(loggerId:upper(), level)
    veaf.loggers.dict[loggerId:lower()] = result
    return result
end

function veaf.loggers.get(loggerId)
    local result = nil
    if loggerId and #loggerId > 0 then
        result = veaf.loggers.dict[loggerId:lower()]
    end
    if not result then
        result = veaf.loggers.get("veaf")
    end
    return result
end

if veaf.Development then
    veaf.loggers.setBaseLevel(veaf.Logger.LEVEL["trace"])
end

veaf.loggers.new(veaf.Id, veaf.LogLevel)

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- unique identifers
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veaf.UNIQUE_ID = 10000 + math.random(50,500)

function veaf.getUniqueIdentifier()
    veaf.UNIQUE_ID = veaf.UNIQUE_ID + 1
    return veaf.UNIQUE_ID
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- lines and figures on the map
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafDrawingOnMap = {}
VeafDrawingOnMap.DEFAULT_COLOR = {170/255, 10/255, 0/255, 220/255}
VeafDrawingOnMap.DEFAULT_FILLCOLOR = {170/255, 10/255, 0/255, 170/255}
function VeafDrawingOnMap.init(object)
    -- technical name (identifier)
    object.name = nil
    -- coalition
    object.coalition = coalition.side.BLUE
    -- points forming the drawing
    object.points = {}
    -- color ({r, g, b, a})
    object.color = VeafDrawingOnMap.DEFAULT_COLOR
    -- fill color ({r, g, b, a})
    object.fillColor = VeafDrawingOnMap.DEFAULT_FILLCOLOR
    -- type of line (member of VeafDrawingOnMap.LINE_TYPE)
    object.lineType = VeafDrawingOnMap.LINE_TYPE["solid"]
    -- if true, the line is an arrow
    object.isArrow = false
    -- marker ids
    object.dcsMarkerIds = {}
end

-- Type of line marking the zone
-- 0  No Line
-- 1  Solid
-- 2  Dashed
-- 3  Dotted
-- 4  Dot Dash
-- 5  Long Dash
-- 6  Two Dash
VeafDrawingOnMap.LINE_TYPE = {
    ["none"] = 0,
    ["solid"] = 1,
    ["dashed"] = 2,
    ["dotted"] = 3,
    ["dotdash"] = 4,
    ["longdash"] = 5,
    ["twodashes"] = 6
}

VeafDrawingOnMap.COLORS = {
    ["transparent"] = {0, 0, 0, 0},
    ["black"] = {0, 0, 0, 1},
    ["white"] = {1, 1, 1, 1},
    ["red"] = {1, 0, 0, 1},
    ["pink"] = {1, 0, 0, 0.3},
    ["green"] = {0, 1, 0, 1},
    ["blue"] = {0, 0, 1, 1}
}

function VeafDrawingOnMap:new(objectToCopy)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap:new()")
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object
    VeafDrawingOnMap.init(objectToCreate)

    return objectToCreate
end

function VeafDrawingOnMap:setName(value)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[]:setName(%s)", veaf.p(value))
    self.name = value
    return self
end

function VeafDrawingOnMap:getName()
    return self.name
end

function VeafDrawingOnMap:setCoalition(value)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:setCoalition(%s)", veaf.p(self:getName()), veaf.p(value))
    self.coalition = value
    return self
end

function VeafDrawingOnMap:getCoalition()
    return self.coalition
end

function VeafDrawingOnMap:addPoint(value)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:addPoint(%s)", veaf.p(self.name), veaf.p(value))
    table.insert(self.points, 1, mist.utils.deepCopy(value))
    return self
end

function VeafDrawingOnMap:addPoints(value)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:addPoints(%s)", veaf.p(self.name), veaf.p(value))
    if value and #value > 0 then
        for _, item in pairs(value) do
            self:addPoint(item)
        end
    end
    return self
end

function VeafDrawingOnMap:setPointsFromUnits(unitNames)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:setPointsFromUnits()", veaf.p(self.name))
    local polygon = veaf.getPolygonFromUnits(unitNames)
    self:addPoints(polygon)
    return self
end

function VeafDrawingOnMap:setColor(value)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:setColor(%s)", veaf.p(self:getName()), veaf.p(value))
    if value and type(value) == "string" then
        value = VeafDrawingOnMap.COLORS[value:lower()]
    end
    if value then
        self.color = mist.utils.deepCopy(value)
    end
    return self
end

function VeafDrawingOnMap:setFillColor(value)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:setFillColor(%s)", veaf.p(self:getName()), veaf.p(value))
    if value and type(value) == "string" then
        value = VeafDrawingOnMap.COLORS[value:lower()]
    end
    if value then
        self.fillColor = mist.utils.deepCopy(value)
    end
    return self
end

function VeafDrawingOnMap:setLineType(value)
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:setLineType(%s)", veaf.p(self:getName()), veaf.p(value))
    if value and type(value) == "string" then
        value = VeafDrawingOnMap.LINE_TYPE[value:lower()]
    end
    if value then
        self.lineType = value
    end
    return self
end

function VeafDrawingOnMap:setArrow()
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:setArrow()", veaf.p(self:getName()))
    self.isArrow = true
    return self
end

function VeafDrawingOnMap:draw()
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:draw()", veaf.p(self:getName()))

    -- start by erasing the drawing if it already is drawn
    self:erase()

    -- then draw it
    local lastPoint = nil
    local firstPoint = nil
    for _, point in pairs(self.points) do
-- LOGGING DISABLED WHEN COMPILING("drawing line [%s] - [%s]", veaf.p(lastPoint), veaf.p(point))
        local id = veaf.getUniqueIdentifier()
        if lastPoint then
-- LOGGING DISABLED WHEN COMPILING("id=[%s]", veaf.p(id))
            if self.isArrow then
                trigger.action.arrowToAll(self:getCoalition(), id, lastPoint, point, self.color, self.fillColor, self.lineType, true)
            else
                trigger.action.lineToAll(self:getCoalition(), id, lastPoint, point, self.color, self.lineType, true)
            end
        else
-- LOGGING DISABLED WHEN COMPILING("setting firstPoint to [%s]", veaf.p(point))
            trigger.action.markToCoalition(id, self.name, point, self.coalition, true, nil)
            firstPoint = point
        end
        table.insert(self.dcsMarkerIds, id)
        lastPoint = point
    end

    -- finish the polygon
    if firstPoint and lastPoint and #self.points > 2 and not self.isArrow then
-- LOGGING DISABLED WHEN COMPILING("finishing the polygon")
        local id = veaf.getUniqueIdentifier()
-- LOGGING DISABLED WHEN COMPILING("id=[%s]", veaf.p(id))
        if self.isArrow then
            trigger.action.arrowToAll(self:getCoalition(), id, lastPoint, firstPoint, self.color, self.fillColor, self.lineType, true)
        else
            trigger.action.lineToAll(self:getCoalition(), id, lastPoint, firstPoint, self.color, self.lineType, true)
        end
        table.insert(self.dcsMarkerIds, id)
    end

    return self
end

function VeafDrawingOnMap:erase()
-- LOGGING DISABLED WHEN COMPILING("VeafDrawingOnMap[%s]:erase()", veaf.p(self:getName()))
    if self.dcsMarkerIds then
        for _, id in pairs(self.dcsMarkerIds) do
-- LOGGING DISABLED WHEN COMPILING("removing mark id=[%s]", veaf.p(id))
            trigger.action.removeMark(id)
        end
    end

    return self
end

VeafCircleOnMap = VeafDrawingOnMap:new()
function VeafCircleOnMap.init(object)
    -- inheritance
    VeafDrawingOnMap.init(object)

    -- radius in meters
    object.radius = nil
end
function VeafCircleOnMap:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object
    VeafCircleOnMap.init(objectToCreate)

    return objectToCreate
end

function VeafCircleOnMap:setCenter(value)
-- LOGGING DISABLED WHEN COMPILING("VeafCircleOnMap[%s]:setCenter(%s)", veaf.p(self.name), veaf.p(value))
    self.points = { mist.utils.deepCopy(value) }
    return self
end

function VeafCircleOnMap:setRadius(value)
-- LOGGING DISABLED WHEN COMPILING("VeafCircleOnMap[%s]:setRadius(%s)", veaf.p(self.name), veaf.p(value))
    self.radius = value
    return self
end

function VeafCircleOnMap:draw()
-- LOGGING DISABLED WHEN COMPILING("VeafCircleOnMap[%s]:draw()", veaf.p(self:getName()))

    -- start by erasing the drawing if it already is drawn
    self:erase()

    -- then draw it
    local id = veaf.getUniqueIdentifier()
-- LOGGING DISABLED WHEN COMPILING("id=[%s]", veaf.p(id))
    trigger.action.circleToAll(self:getCoalition(), id , self.points[1], self.radius , self.color, self.fillColor, self.lineType, true)
    table.insert(self.dcsMarkerIds, id)

    return self
end

VeafSquareOnMap = VeafDrawingOnMap:new()
function VeafSquareOnMap.init(object)
    -- inheritance
    VeafDrawingOnMap.init(object)

    -- side length in meters
    object.side = nil
    -- center of the square
    object.center = nil
end
function VeafSquareOnMap:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object
    VeafSquareOnMap.init(objectToCreate)

    return objectToCreate
end

function VeafSquareOnMap:setCenter(value)
-- LOGGING DISABLED WHEN COMPILING("VeafSquareOnMap[%s]:setCenter(%s)", veaf.p(self.name), veaf.p(value))
    self.center = mist.utils.deepCopy(value)
    self:compute()
    return self
end

function VeafSquareOnMap:setSide(value)
-- LOGGING DISABLED WHEN COMPILING("VeafSquareOnMap[%s]:setSide(%s)", veaf.p(self.name), veaf.p(value))
    self.side = value
    self:compute()
    return self
end

function VeafSquareOnMap:compute()
-- LOGGING DISABLED WHEN COMPILING("VeafSquareOnMap[%s]:compute()", veaf.p(self.name))
    if self.side and self.center then
-- LOGGING DISABLED WHEN COMPILING("self.center=%s", veaf.p(self.center))
-- LOGGING DISABLED WHEN COMPILING("self.side=%s", veaf.p(self.side))
        local leftDownPoint = { x = self.center.x - self.side / 2, y = self.center.y, z = self.center.z - self.side / 2 }
-- LOGGING DISABLED WHEN COMPILING("leftDownPoint=%s", veaf.p(leftDownPoint))
        local rightUpPoint = { x = self.center.x + self.side / 2, y = self.center.y,z = self.center.z + self.side / 2 }
-- LOGGING DISABLED WHEN COMPILING("rightUpPoint=%s", veaf.p(rightUpPoint))
        self.points = { leftDownPoint, rightUpPoint }
    end
    return self
end

function VeafSquareOnMap:draw()
-- LOGGING DISABLED WHEN COMPILING("VeafSquareOnMap[%s]:draw()", veaf.p(self:getName()))

    -- start by erasing the drawing if it already is drawn
    self:erase()

    -- then draw it
    local id = veaf.getUniqueIdentifier()
-- LOGGING DISABLED WHEN COMPILING("id=[%s]", veaf.p(id))
    trigger.action.rectToAll(self:getCoalition(), id , self.points[1], self.points[2], self.color, self.fillColor, self.lineType, true)
    table.insert(self.dcsMarkerIds, id)

    return self
end


-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- trigger zones management
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veaf._discoverTriggerZones()
    for _, zones in pairs(env.mission.triggers) do
        for _, zoneData in pairs(zones) do
            veaf.triggerZones[zoneData.name] = {
                ["radius"] = zoneData.radius,
                ["zoneId"] = zoneData.zoneId,
                ["color"] =
                {
                    [1] = zoneData.color[1],
                    [2] = zoneData.color[2],
                    [3] = zoneData.color[3],
                    [4] = zoneData.color[4],
                },
                ["properties"] = zoneData.properties,
                ["hidden"] = zoneData.hidden,
                ["y"] = zoneData.y,
                ["x"] = zoneData.x,
                ["name"] = zoneData.name,
                ["type"] = zoneData.type,
            }
            if zoneData.type == 2 then
                veaf.triggerZones[zoneData.name].verticies = zoneData.verticies
            end
        end
    end
end

function veaf.getTriggerZone(zoneName)
    return veaf.triggerZones[zoneName]
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- initialize the random number generator to make it almost random
math.random(); math.random(); math.random()

--- Enable/Disable error boxes displayed on screen.
env.setErrorMessageBoxEnabled(false)

veaf.loggers.get(veaf.Id):info("Loading version %s", veaf.Version)
veaf.loggers.get(veaf.Id):info("veaf.Development=%s", veaf.Development)
veaf.loggers.get(veaf.Id):info("veaf.SecurityDisabled=%s", veaf.SecurityDisabled)
veaf.loggers.get(veaf.Id):info("veaf.LogLevel=%s", veaf.LogLevel)
veaf.loggers.get(veaf.Id):info("veaf.ForcedLogLevel=%s", veaf.ForcedLogLevel)

-- discover trigger zones
veaf._discoverTriggerZones()

--store maximum airbase lifes
veaf.loadAirbasesLife0()

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- changes to CTLD 
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Our CTLD (VEAF version) does not autoinitialize. It's also set to log messages using the VEAF logging functions
-- Instead, we count on the mission makers to call ctld.initialize from the missionConfig.lua file (since v5.0)
-- Here, we're upgrading the vanilla CTLD initialize function so it's smarter

---The VEAF replacement function that wraps up around ctld.initialize
---@param configurationCallback function? a callback that will be called before calling the vanilla ctld.initialize function
function veaf.ctld_initialize_replacement(configurationCallback)
    if ctld then
        veaf.loggers.get(veaf.Id):info(string.format("Setting up CTLD"))

        -- change the init function so we can call it whenever we want
        ctld.skipInitialisation = true

        -- logging change
        ctld.p = veaf.p
        ctld.Id = "CTLD"
        --ctld.LogLevel = "info"
        --ctld.LogLevel = "debug"
        --ctld.LogLevel = "trace"

        ctld.logger = veaf.loggers.new(ctld.Id, ctld.LogLevel)

        -- override the ctld logs with our own methods
        ---@diagnostic disable-next-line: duplicate-set-field
        ctld.logError = function(message, args)
            veaf.loggers.get(ctld.Id):error(message, args)
        end

        -- override the ctld logs with our own methods
        ---@diagnostic disable-next-line: duplicate-set-field
        ctld.logInfo = function(message, args)
            veaf.loggers.get(ctld.Id):info(message, args)
        end

        -- override the ctld logs with our own methods
        ---@diagnostic disable-next-line: duplicate-set-field
        ctld.logDebug = function(message, args)
-- LOGGING DISABLED WHEN COMPILING(message, args)
        end

        -- override the ctld logs with our own methods
        ---@diagnostic disable-next-line: duplicate-set-field
        ctld.logTrace = function(message, args)
-- LOGGING DISABLED WHEN COMPILING(message, args)
        end

        -- global configuration change
        ctld.addPlayerAircraftByType = true
        ctld.loadCrateFromMenu = true -- if set to true, you can load crates with the F10 menu OR hovering, in case of using choppers and planes for example.
        ctld.slingLoad = true -- if false, crates can be used WITHOUT slingloading, by hovering above the crate, simulating slingloading but not the weight...
        ctld.crateWaitTime = 0 -- time in seconds to wait before you can spawn another crate
        
        -- Simulated Sling load configuration
        ctld.minimumHoverHeight = 5.0 -- Lowest allowable height for crate hover
        ctld.maximumHoverHeight = 15.0 -- Highest allowable height for crate hover
        ctld.maxDistanceFromCrate = 8.0 -- Maximum distance from from crate for hover
        ctld.hoverTime = 10 -- Time to hold hover above a crate for loading in seconds

        -- ************** Maximum Units SETUP for UNITS ******************

        ctld.unitLoadLimits["UH-1H"] = 10
        ctld.unitLoadLimits["Mi-24P"] = 10
        ctld.unitLoadLimits["Mi-8MT"] = 20
        ctld.unitLoadLimits["UH-60L"] = 20
        ctld.unitLoadLimits["Yak-52"] = 1
        ctld.unitLoadLimits["SA342L"] = 1
        ctld.unitLoadLimits["SA342M"] = 1
        ctld.unitLoadLimits["SA342Mistral"] = 1
        ctld.unitLoadLimits["SA342Minigun"] = 1
        ctld.unitLoadLimits["CH-47Fbl1"] = 33

        ctld.internalCargoLimits["Mi-8MT"] = 2
        ctld.internalCargoLimits["CH-47Fbl1"] = 4

        -- ************** Allowable actions for UNIT TYPES ******************
        ctld.aircraftTypeTable = {
                "Hercules",
                "UH-60L",
                "Ka-50",
                "Ka-50_3",
                "Mi-8MT",
                "Mi-24P",
                "SA342L",
                "SA342M",
                "SA342Mistral",
                "SA342Minigun",
                "UH-1H",
                "CH-47Fbl1",
                "Yak-52",
        }

        ctld.unitActions["Yak-52"] = {crates=false, troops=true}
        ctld.unitActions["UH-60L"] = {crates=true, troops=true}
        ctld.unitActions["SA342L"] = {crates=false, troops=true}
        ctld.unitActions["SA342M"] = {crates=false, troops=true}
        ctld.unitActions["SA342Mistral"] = {crates=false, troops=true}
        ctld.unitActions["SA342Minigun"] = {crates=false, troops=true}

        -- ************** INFANTRY GROUPS FOR PICKUP ******************

        ctld.autoInitializeAllLogistic = function()
            local LogisticTypeNames = {"LHA_Tarawa", "Stennis", "CVN_71", "KUZNECOW", "FARP Ammo Storage", "FARP Ammo Dump Coating"}
            veaf.loggers.get(ctld.Id):info("autoInitializeAllLogistic()")
            ctld.logisticUnits = {}
            local units = mist.DBs.unitsByName -- local copy for faster execution
            for name, unit in pairs(units) do
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s, unit.type=%s", veaf.p(name), veaf.p(unit.type)))
                if unit then
                    for _, unitTypeName in pairs(LogisticTypeNames) do
                        if unitTypeName:lower() == unit.type:lower() then
                            table.insert(ctld.logisticUnits, unit.unitName)
-- LOGGING DISABLED WHEN COMPILING("Adding CTLD logistic unit %s of group %s", veaf.p(unit.unitName), veaf.p(unit.groupName))
                        end
                    end
                end
            end

            -- generate 20 logistic unit names in the form "logistic #001"
-- LOGGING DISABLED WHEN COMPILING("generate 20 logistic unit names in the form 'logistic #001'")
            for i = 1, 20 do
                table.insert(ctld.logisticUnits, string.format("logistic #%03d",i))
            end

-- LOGGING DISABLED WHEN COMPILING("ctld.logisticUnits=%s", veaf.p(ctld.logisticUnits))
        end

        ctld.autoInitializeAllPickupZones = function()
            local PickupShipNames = {"LHA_Tarawa", "Stennis", "CVN_71", "KUZNECOW"}
            veaf.loggers.get(ctld.Id):info("autoInitializeAllPickupZones()")
            ctld.pickupZones = {}
            -- add all ships to the pickup zones table
            local units = mist.makeUnitTable({"[all][ship]"}) -- get all ships in the mission
-- LOGGING DISABLED WHEN COMPILING("units=%s", veaf.p(units))
            for _, unitName in pairs(units) do
                if unitName then
                    local unitObject = Unit.getByName(unitName)
                    local _unitCoalition = nil
                    if unitObject then
                        _unitCoalition = veaf.getCoalitionForCountry(veaf.getCountryName(unitObject:getCountry()), true)
                    end
                    local zone = {unitName, nil, -1, "yes", _unitCoalition, nil}
                    table.insert(ctld.pickupZones, zone)
-- LOGGING DISABLED WHEN COMPILING("Adding CTLD pickup zone for ship: [%s]", veaf.p(zone))
                end
            end

            -- generate 20 pickup zone names in the form "pickzone #001"
-- LOGGING DISABLED WHEN COMPILING("generate 20 pickup zone names in the form 'pickzone #001'")
            for i = 1, 20 do
                table.insert(ctld.pickupZones, { string.format("pickzone #%03d",i), "none", -1, "yes", 0 })
            end

-- LOGGING DISABLED WHEN COMPILING("ctld.pickupZones=%s", veaf.p(ctld.pickupZones))
        end

        -- automatically add all the carriers and FARPs to ctld.logisticUnits
        ctld.autoInitializeAllLogistic()

        -- automatically generate pickup zones names
        ctld.autoInitializeAllPickupZones()

        -- if a callback is defined, this is the right moment to call it
        if configurationCallback and type(configurationCallback) == "function" then
            -- a configuration callback has been set, call it
            veaf.loggers.get(ctld.Id):info("calling the configuration callback")
            configurationCallback()
            veaf.loggers.get(ctld.Id):info("done calling the configuration callback")
        end

        -- call the actual CTLD.initialize
        veaf.ctld_initialize(true)
        veaf.ctld_initialized = true
        veaf.loggers.get(ctld.Id):info(string.format("Done setting up CTLD"))
    else
        veaf.loggers.get(veaf.Id):error(string.format("CTLD is not loaded"))
    end
end

if ctld then
    veaf.loggers.get(veaf.Id):info(string.format("replacing CTLD.initialize()"))
    veaf.ctld_initialize = ctld.initialize -- used to call the vanilla ctld.initialize from the VEAF replacement
    ctld.initialize = veaf.ctld_initialize_replacement -- replace the ctld.initialize with the VEAF wrapper function
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- changes to CSAR 
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Our CSAR (VEAF version) does not autoinitialize. It's also set to log messages using the VEAF logging functions
-- Instead, we count on the mission makers to call csar.initialize from the missionConfig.lua file (since v5.0)
-- Here, we're upgrading the vanilla CSAR initialize function so it's smarter

---The VEAF replacement function that wraps up around ctld.initialize
---@param configurationCallback function? a callback that will be called before calling the vanilla csar.initialize function
function veaf.csar_initialize_replacement(configurationCallback)
    if csar then
        veaf.loggers.get(veaf.Id):info(string.format("Setting up CSAR"))

        -- change the init function so we can call it whenever we want
        csar.skipInitialisation = true

        -- logging change
        csar.p = veaf.p
        csar.Id = "CSAR"
        --csar.LogLevel = "info"
        --csar.LogLevel = "trace"
        --csar.LogLevel = "debug"

        csar.logger = veaf.loggers.new(csar.Id, csar.LogLevel)

        -- override the csar logs with our own methods
        ---@diagnostic disable-next-line: duplicate-set-field
        csar.logError = function(message)
            veaf.loggers.get(csar.Id):error(message)
        end

        -- override the csar logs with our own methods
        ---@diagnostic disable-next-line: duplicate-set-field
        csar.logInfo = function(message)
            veaf.loggers.get(csar.Id):info(message)
        end

        -- override the csar logs with our own methods
        ---@diagnostic disable-next-line: duplicate-set-field
        csar.logDebug = function(message)
-- LOGGING DISABLED WHEN COMPILING(message)
        end

        -- override the csar logs with our own methods
        ---@diagnostic disable-next-line: duplicate-set-field
        csar.logTrace = function(message)
-- LOGGING DISABLED WHEN COMPILING(message)
        end

        -- global configuration change
        csar.enableAllslots = true
        csar.useprefix = false
        csar.radioSound = "csar-beacon.ogg"
        
        if configurationCallback and type(configurationCallback) == "function" then
            -- a configuration callback has been set, call it
            veaf.loggers.get(csar.Id):info("calling the configuration callback")
            configurationCallback()
            veaf.loggers.get(csar.Id):info("done calling the configuration callback")
        end

        -- call the actual CSAR.initialize
        veaf.csar_initialize(true)
        veaf.csar_initialized = true
        veaf.loggers.get(csar.Id):info(string.format("Done setting up CSAR"))
    else
        veaf.loggers.get(veaf.Id):error(string.format("CSAR is not loaded"))
    end
end

if csar then
    veaf.loggers.get(veaf.Id):info(string.format("replacing CSAR.initialize()"))
    veaf.csar_initialize = csar.initialize -- used to call the vanilla csar.initialize from the VEAF replacement
    csar.initialize = veaf.csar_initialize_replacement -- replace the csar.initialize with the VEAF wrapper function
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- changes to STTS
-------------------------------------------------------------------------------------------------------------------------------------------------------------
if STTS then
    veaf.loggers.get(veaf.Id):info(string.format("Setting up STTS"))

    --- configure SRS Text to Speech
-- LOGGING DISABLED WHEN COMPILING(string.format("STTS - SERVER_CONFIG=%s", veaf.p(SERVER_CONFIG)))
    if SERVER_CONFIG then
        veaf.loggers.get(veaf.Id):info(string.format("Setting up STTS"))
        STTS.DIRECTORY = SERVER_CONFIG.SRS_DIRECTORY
        STTS.SRS_PORT = SERVER_CONFIG.SRS_PORT
        STTS.EXECUTABLE = SERVER_CONFIG.SRS_EXECUTABLE
        STTS.os = SERVER_CONFIG.getModule("os")
        STTS.io = SERVER_CONFIG.getModule("io")
        veaf.loggers.get(veaf.Id):info(string.format("Done setting up STTS"))
    end
end

------------------ END script veaf.lua  ------------------


------------------ START script dcsUnits.lua  ------------------

------------------------------------------------------------------
-- DCS World units database
-- By zip (2018)
--
-- Features:
-- ---------
-- * lists the DCS world units
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

dcsUnits = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the root VEAF constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
dcsUnits.Id = "DCSUNITS - "

--- Version.
dcsUnits.Version = "2024.10.11"

-- trace level, specific to this module
--dcsUnits.LogLevel = "trace"
--dcsUnits.LogLevel = "debug"

dcsUnits.logger = veaf.loggers.new(dcsUnits.Id, dcsUnits.LogLevel)

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--manually filled out list of all Naval statics, perhaps could be automated but did not find any patterns
dcsUnits.NavalStatics = { 
	["offshore WindTurbine"]=true,
	["offshore WindTurbine2"]=true,
	["Oil platform"]=true,
	["Orca"]=true,
	["Gas platform"]=true,
	["Oil rig"]=true,
	["M1 barrage balloon"] = true,
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Raw DCS units database
-------------------------------------------------------------------------------------------------------------------------------------------------------------

dcsUnits.DcsUnitsDatabase =
{
	[1] = 
	{
		["type"] = "1L13 EWR",
		["name"] = "EWR 1L13",
		["category"] = "Air Defence",
		["description"] = "EWR 1L13",
		["vehicle"] = true,
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["Ground vehicles"] = true,
			["Air Defence vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["EWR"] = true,
			[101] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [1]
	[2] = 
	{
		["type"] = "2S6 Tunguska",
		["name"] = "SAM SA-19 Tunguska \"Grison\" ",
		["category"] = "Air Defence",
		["description"] = "SAM SA-19 Tunguska \"Grison\" ",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["AAA"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["AA_missile"] = true,
			["Mobile AAA"] = true,
			["SAM related"] = true,
			["Ground vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Vehicles"] = true,
			["Ground Units"] = true,
			["Air Defence"] = true,
			["All"] = true,
			["SR SAM"] = true,
			["NonAndLightArmoredUnits"] = true,
			[103] = true,
			["SAM TR"] = true,
			["Armed Air Defence"] = true,
			[29] = true,
			["AA_flak"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "SA-19 Tunguska 2S6",
		}, -- end of ["aliases"]
	}, -- end of [2]
	[3] = 
	{
		["type"] = "55G6 EWR",
		["name"] = "EWR 55G6",
		["category"] = "Air Defence",
		["description"] = "EWR 55G6",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence vehicles"] = true,
			["Air Defence"] = true,
			["Ground vehicles"] = true,
			[101] = true,
			["NonAndLightArmoredUnits"] = true,
			["EWR"] = true,
			[2] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [3]
	[4] = 
	{
		["type"] = "5p73 s-125 ln",
		["name"] = "SAM SA-3 S-125 \"Goa\" LN",
		["category"] = "Air Defence",
		["description"] = "SAM SA-3 S-125 \"Goa\" LN",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["AA_missile"] = true,
			[74] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			[27] = true,
			["SAM related"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SAM LL"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [4]
	[5] = 
	{
		["type"] = "Allies_Director",
		["name"] = "Allies Rangefinder (DRT)",
		["category"] = "Air Defence",
		["description"] = "Allies Rangefinder (DRT)",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			[2] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["Armed Air Defence"] = true,
			[330] = true,
			["Air Defence"] = true,
			["NonArmoredUnits"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [5]
	[6] = 
	{
		["type"] = "bofors40",
		["name"] = "AAA Bofors 40mm",
		["category"] = "Air Defence",
		["description"] = "AAA Bofors 40mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["AAA"] = true,
			[16] = true,
			[47] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [6]
	[7] = 
	{
		["type"] = "Dog Ear radar",
		["name"] = "MCC-SR Sborka \"Dog Ear\" SR",
		["category"] = "Air Defence",
		["description"] = "MCC-SR Sborka \"Dog Ear\" SR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["SAM elements"] = true,
			["SAM related"] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["Air Defence"] = true,
			["SAM SR"] = true,
			[101] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [7]
	[8] = 
	{
		["type"] = "flak18",
		["name"] = "AAA 8,8cm Flak 18",
		["category"] = "Air Defence",
		["description"] = "AAA 8,8cm Flak 18",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			[314] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [8]
	[9] = 
	{
		["type"] = "flak30",
		["name"] = "AAA Flak 38 20mm",
		["category"] = "Air Defence",
		["description"] = "AAA Flak 38 20mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			[280] = true,
			["All"] = true,
			[16] = true,
			["AAA"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [9]
	[10] = 
	{
		["type"] = "flak36",
		["name"] = "AAA 8,8cm Flak 36",
		["category"] = "Air Defence",
		["description"] = "AAA 8,8cm Flak 36",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["AAA"] = true,
			[16] = true,
			[47] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [10]
	[11] = 
	{
		["type"] = "flak37",
		["name"] = "AAA 8,8cm Flak 37",
		["category"] = "Air Defence",
		["description"] = "AAA 8,8cm Flak 37",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["AAA"] = true,
			[16] = true,
			[47] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [11]
	[12] = 
	{
		["type"] = "flak38",
		["name"] = "AAA Flak-Vierling 38 Quad 20mm",
		["category"] = "Air Defence",
		["description"] = "AAA Flak-Vierling 38 Quad 20mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			[281] = true,
			[16] = true,
			["AAA"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [12]
	[13] = 
	{
		["type"] = "flak41",
		["name"] = "AAA 8,8cm Flak 41",
		["category"] = "Air Defence",
		["description"] = "AAA 8,8cm Flak 41",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["AAA"] = true,
			[16] = true,
			[47] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [13]
	[14] = 
	{
		["type"] = "Flakscheinwerfer_37",
		["name"] = "SL Flakscheinwerfer 37",
		["category"] = "Air Defence",
		["description"] = "SL Flakscheinwerfer 37",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			[26] = true,
			["Vehicles"] = true,
			["NonArmoredUnits"] = true,
			[2] = true,
			["All"] = true,
			["Air Defence"] = true,
			["AAA"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			[282] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [14]
	[15] = 
	{
		["type"] = "FPS-117",
		["name"] = "EWR AN/FPS-117 Radar",
		["category"] = "Air Defence",
		["description"] = "EWR AN/FPS-117 Radar",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence vehicles"] = true,
			[2] = true,
			["Air Defence"] = true,
			["Ground vehicles"] = true,
			[329] = true,
			["NonAndLightArmoredUnits"] = true,
			["EWR"] = true,
			[101] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [15]
	[16] = 
	{
		["type"] = "FPS-117 Dome",
		["name"] = "EWR AN/FPS-117 Radar (domed)",
		["category"] = "Air Defence",
		["description"] = "EWR AN/FPS-117 Radar (domed)",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence vehicles"] = true,
			[327] = true,
			["Air Defence"] = true,
			["Ground vehicles"] = true,
			[2] = true,
			["NonAndLightArmoredUnits"] = true,
			["EWR"] = true,
			[101] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [16]
	[17] = 
	{
		["type"] = "FPS-117 ECS",
		["name"] = "EWR AN/FPS-117 ECS",
		["category"] = "Air Defence",
		["description"] = "EWR AN/FPS-117 ECS",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			["SAM related"] = true,
			["Air Defence"] = true,
			[16] = true,
			[328] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			[101] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SAM CC"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [17]
	[18] = 
	{
		["type"] = "FuMG-401",
		["name"] = "EWR FuMG-401 Freya LZ",
		["category"] = "Air Defence",
		["description"] = "EWR FuMG-401 Freya LZ",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence vehicles"] = true,
			[2] = true,
			["Air Defence"] = true,
			["Ground vehicles"] = true,
			[284] = true,
			["NonAndLightArmoredUnits"] = true,
			["EWR"] = true,
			[101] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [18]
	[19] = 
	{
		["type"] = "FuSe-65",
		["name"] = "EWR FuSe-65 Würzburg-Riese",
		["category"] = "Air Defence",
		["description"] = "EWR FuSe-65 Würzburg-Riese",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence vehicles"] = true,
			[285] = true,
			["Air Defence"] = true,
			["Ground vehicles"] = true,
			[2] = true,
			["NonAndLightArmoredUnits"] = true,
			["EWR"] = true,
			[101] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [19]
	[20] = 
	{
		["type"] = "generator_5i57",
		["name"] = "Diesel Power Station 5I57A",
		["category"] = "Air Defence",
		["description"] = "Diesel Power Station 5I57A",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			["AD Auxillary Equipment"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			[9] = true,
			["Air Defence vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			[293] = true,
			["All"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [20]
	[21] = 
	{
		["type"] = "Gepard",
		["name"] = "SPAAA Gepard",
		["category"] = "Air Defence",
		["description"] = "SPAAA Gepard",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["AAA"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			[38] = true,
			["Mobile AAA"] = true,
			["NonArmoredUnits"] = true,
			["SAM related"] = true,
			["Ground vehicles"] = true,
			["Air Defence"] = true,
			["Vehicles"] = true,
			["AA_flak"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["SAM TR"] = true,
			["Armed Air Defence"] = true,
			["Ground Units"] = true,
			[105] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [21]
	[22] = 
	{
		["type"] = "Hawk cwar",
		["name"] = "SAM Hawk CWAR AN/MPQ-55",
		["category"] = "Air Defence",
		["description"] = "SAM Hawk CWAR AN/MPQ-55",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			[42] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["Ground Units"] = true,
			["All"] = true,
			["Datalink"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [22]
	[23] = 
	{
		["type"] = "Hawk ln",
		["name"] = "SAM Hawk LN M192",
		["category"] = "Air Defence",
		["description"] = "SAM Hawk LN M192",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["AA_missile"] = true,
			[41] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["SAM related"] = true,
			["NonAndLightArmoredUnits"] = true,
			[27] = true,
			["SAM LL"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "Hawk M192 LN",
		}, -- end of ["aliases"]
	}, -- end of [23]
	[24] = 
	{
		["type"] = "Hawk pcp",
		["name"] = "SAM Hawk Platoon Command Post (PCP)",
		["category"] = "Air Defence",
		["description"] = "SAM Hawk Platoon Command Post (PCP)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			["SAM related"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			[6] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SAM CC"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [24]
	[25] = 
	{
		["type"] = "Hawk sr",
		["name"] = "SAM Hawk SR (AN/MPQ-50)",
		["category"] = "Air Defence",
		["description"] = "SAM Hawk SR (AN/MPQ-50)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["NonAndLightArmoredUnits"] = true,
			[39] = true,
			["Ground Units"] = true,
			["All"] = true,
			["Datalink"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "Hawk AN/MPQ-50 SR",
		}, -- end of ["aliases"]
	}, -- end of [25]
	[26] = 
	{
		["type"] = "Hawk tr",
		["name"] = "SAM Hawk TR (AN/MPQ-46)",
		["category"] = "Air Defence",
		["description"] = "SAM Hawk TR (AN/MPQ-46)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			[40] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["SAM TR"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "Hawk AN/MPQ-46 TR",
		}, -- end of ["aliases"]
	}, -- end of [26]
	[27] = 
	{
		["type"] = "HEMTT_C-RAM_Phalanx",
		["name"] = "LPWS C-RAM",
		["category"] = "Air Defence",
		["description"] = "LPWS C-RAM",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["C-RAM"] = true,
			["Ground vehicles"] = true,
			[105] = true,
			["Mobile AAA"] = true,
			["AAA"] = true,
			["NonArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["SAM TR"] = true,
			["NonAndLightArmoredUnits"] = true,
			["AA_flak"] = true,
			["Ground Units"] = true,
			["All"] = true,
			["Datalink"] = true,
			[342] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [27]
	[28] = 
	{
		["type"] = "HL_ZU-23",
		["name"] = "SPAAA HL with ZU-23",
		["category"] = "Air Defence",
		["description"] = "SPAAA HL with ZU-23",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["Ground Units"] = true,
			["AA_flak"] = true,
			[325] = true,
			["All"] = true,
			[16] = true,
			["AAA"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			[2] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["Mobile AAA"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [28]
	[29] = 
	{
		["type"] = "HQ-7_LN_P",
		["name"] = "HQ-7 LN (Player)",
		["category"] = "Air Defence",
		["description"] = "HQ-7 LN (Player)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["AA_missile"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["NonArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["SAM related"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			[102] = true,
			["SAM LL"] = true,
			[346] = true,
			["SAM TR"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SR SAM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [29]
	[30] = 
	{
		["type"] = "HQ-7_LN_SP",
		["name"] = "HQ-7 Self-Propelled LN",
		["category"] = "Air Defence",
		["description"] = "HQ-7 Self-Propelled LN",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["AA_missile"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["NonArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["SAM related"] = true,
			["Air Defence"] = true,
			["SAM TR"] = true,
			[102] = true,
			["SAM LL"] = true,
			[277] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SR SAM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [30]
	[31] = 
	{
		["type"] = "HQ-7_STR_SP",
		["name"] = "HQ-7 Self-Propelled STR",
		["category"] = "Air Defence",
		["description"] = "HQ-7 Self-Propelled STR",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[278] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["SAM CC"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["SR SAM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM elements"] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [31]
	[32] = 
	{
		["infantry"] = true,
		["type"] = "Igla manpad INS",
		["name"] = "MANPADS SA-18 Igla \"Grouse\" Ins",
		["category"] = "Air Defence",
		["description"] = "MANPADS SA-18 Igla \"Grouse\" Ins",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[62] = true,
			["SAM related"] = true,
			["New infantry"] = true,
			["Armed ground units"] = true,
			["MANPADS"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["Infantry"] = true,
			["Air Defence"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			[27] = true,
			["Armed Air Defence"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [32]
	[33] = 
	{
		["type"] = "KDO_Mod40",
		["name"] = "AAA Kdo.G.40",
		["category"] = "Air Defence",
		["description"] = "AAA Kdo.G.40",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			[316] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [33]
	[34] = 
	{
		["type"] = "KS-19",
		["name"] = "AAA KS-19 100mm",
		["category"] = "Air Defence",
		["description"] = "AAA KS-19 100mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["Ground Units"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonArmoredUnits"] = true,
			["Static AAA"] = true,
			[334] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [34]
	[35] = 
	{
		["type"] = "Kub 1S91 str",
		["name"] = "SAM SA-6 Kub \"Straight Flush\" STR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-6 Kub \"Straight Flush\" STR",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			[21] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["SAM TR"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "SA-6 Kub STR 9S91",
		}, -- end of ["aliases"]
	}, -- end of [35]
	[36] = 
	{
		["type"] = "Kub 2P25 ln",
		["name"] = "SAM SA-6 Kub \"Gainful\" TEL",
		["category"] = "Air Defence",
		["description"] = "SAM SA-6 Kub \"Gainful\" TEL",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Armed Air Defence"] = true,
			["SAM elements"] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["SAM related"] = true,
			["Air Defence"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["NonArmoredUnits"] = true,
			["SAM LL"] = true,
			["NonAndLightArmoredUnits"] = true,
			["AA_missile"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[22] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "SA-6 Kub LN 2P25",
		}, -- end of ["aliases"]
	}, -- end of [36]
	[37] = 
	{
		["type"] = "M1097 Avenger",
		["name"] = "SAM Avenger (Stinger)",
		["category"] = "Air Defence",
		["description"] = "SAM Avenger (Stinger)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["AA_flak"] = true,
			[33] = true,
			["SAM related"] = true,
			["AA_missile"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Air Defence"] = true,
			["Ground Units"] = true,
			["SR SAM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			[104] = true,
			["Armed Air Defence"] = true,
			["Datalink"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [37]
	[38] = 
	{
		["type"] = "M1_37mm",
		["name"] = "AAA M1 37mm",
		["category"] = "Air Defence",
		["description"] = "AAA M1 37mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			[2] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonArmoredUnits"] = true,
			[288] = true,
			["Ground Units"] = true,
			["Static AAA"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [38]
	[39] = 
	{
		["type"] = "M45_Quadmount",
		["name"] = "AAA M45 Quadmount HB 12.7mm",
		["category"] = "Air Defence",
		["description"] = "AAA M45 Quadmount HB 12.7mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air Defence"] = true,
			[287] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [39]
	[40] = 
	{
		["type"] = "M48 Chaparral",
		["name"] = "SAM Chaparral M48",
		["category"] = "Air Defence",
		["description"] = "SAM Chaparral M48",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[16] = true,
			["SAM related"] = true,
			["AA_missile"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Air Defence"] = true,
			["Ground Units"] = true,
			["SR SAM"] = true,
			["NonAndLightArmoredUnits"] = true,
			[50] = true,
			[27] = true,
			["Armed Air Defence"] = true,
			["Datalink"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [40]
	[41] = 
	{
		["type"] = "M6 Linebacker",
		["name"] = "SAM Linebacker - Bradley M6",
		["category"] = "Air Defence",
		["description"] = "SAM Linebacker - Bradley M6",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["AA_flak"] = true,
			["SAM related"] = true,
			["AA_missile"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Ground Units"] = true,
			["Air Defence"] = true,
			[51] = true,
			["SR SAM"] = true,
			[104] = true,
			["All"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Datalink"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [41]
	[42] = 
	{
		["type"] = "Maschinensatz_33",
		["name"] = "Maschinensatz 33 Gen",
		["category"] = "Air Defence",
		["description"] = "Maschinensatz 33 Gen",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			["AD Auxillary Equipment"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			[9] = true,
			["Air Defence vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			[283] = true,
			["All"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [42]
	[43] = 
	{
		["type"] = "NASAMS_Command_Post",
		["name"] = "SAM NASAMS C2",
		["category"] = "Air Defence",
		["description"] = "SAM NASAMS C2",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["SAM CC"] = true,
			["Ground Units Non Airdefence"] = true,
			["NonArmoredUnits"] = true,
			[306] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			[25] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Unarmed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [43]
	[44] = 
	{
		["type"] = "NASAMS_LN_B",
		["name"] = "SAM NASAMS LN AIM-120B",
		["category"] = "Air Defence",
		["description"] = "SAM NASAMS LN AIM-120B",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["AA_missile"] = true,
			["NonArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			[307] = true,
			["SAM LL"] = true,
			[27] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [44]
	[45] = 
	{
		["type"] = "NASAMS_LN_C",
		["name"] = "SAM NASAMS LN AIM-120C",
		["category"] = "Air Defence",
		["description"] = "SAM NASAMS LN AIM-120C",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["AA_missile"] = true,
			["NonArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			[308] = true,
			[27] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM LL"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [45]
	[46] = 
	{
		["type"] = "NASAMS_Radar_MPQ64F1",
		["name"] = "SAM NASAMS SR MPQ64F1",
		["category"] = "Air Defence",
		["description"] = "SAM NASAMS SR MPQ64F1",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			[305] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			[101] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [46]
	[47] = 
	{
		["type"] = "Osa 9A33 ln",
		["name"] = "SAM SA-8 Osa \"Gecko\" TEL",
		["category"] = "Air Defence",
		["description"] = "SAM SA-8 Osa \"Gecko\" TEL",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["AA_missile"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			[23] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			[102] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["SAM TR"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SR SAM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [47]
	[48] = 
	{
		["type"] = "p-19 s-125 sr",
		["name"] = "SAM SA-2/3/5 P19 \"Flat Face\" SR ",
		["category"] = "Air Defence",
		["description"] = "SAM SA-2/3/5 P19 \"Flat Face\" SR ",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			[75] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [48]
	[49] = 
	{
		["type"] = "Patriot AMG",
		["name"] = "SAM Patriot CR (AMG AN/MRC-137)",
		["category"] = "Air Defence",
		["description"] = "SAM Patriot CR (AMG AN/MRC-137)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[17] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[36] = true,
			["SAM CC"] = true,
			["Ground Units Non Airdefence"] = true,
			["NonArmoredUnits"] = true,
			[25] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Unarmed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [49]
	[50] = 
	{
		["type"] = "Patriot cp",
		["name"] = "SAM Patriot C2 ICC",
		["category"] = "Air Defence",
		["description"] = "SAM Patriot C2 ICC",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[17] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[36] = true,
			["SAM CC"] = true,
			["Ground Units Non Airdefence"] = true,
			["NonArmoredUnits"] = true,
			[25] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Unarmed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [50]
	[51] = 
	{
		["type"] = "Patriot ECS",
		["name"] = "SAM Patriot ECS",
		["category"] = "Air Defence",
		["description"] = "SAM Patriot ECS",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[36] = true,
			["SAM CC"] = true,
			["Ground Units Non Airdefence"] = true,
			["NonArmoredUnits"] = true,
			[25] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Unarmed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [51]
	[52] = 
	{
		["type"] = "Patriot EPP",
		["name"] = "SAM Patriot EPP-III",
		["category"] = "Air Defence",
		["description"] = "SAM Patriot EPP-III",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[17] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[36] = true,
			["SAM CC"] = true,
			["Ground Units Non Airdefence"] = true,
			["NonArmoredUnits"] = true,
			[25] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Unarmed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [52]
	[53] = 
	{
		["type"] = "Patriot ln",
		["name"] = "SAM Patriot LN",
		["category"] = "Air Defence",
		["description"] = "SAM Patriot LN",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["AA_missile"] = true,
			["NonArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			[37] = true,
			[27] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM LL"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [53]
	[54] = 
	{
		["type"] = "Patriot str",
		["name"] = "SAM Patriot STR",
		["category"] = "Air Defence",
		["description"] = "SAM Patriot STR",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			[34] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["LR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			[101] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [54]
	[55] = 
	{
		["type"] = "QF_37_AA",
		["name"] = "AAA QF 3.7\"",
		["category"] = "Air Defence",
		["description"] = "AAA QF 3.7\"",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			[286] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [55]
	[56] = 
	{
		["type"] = "rapier_fsa_blindfire_radar",
		["name"] = "SAM Rapier Blindfire TR",
		["category"] = "Air Defence",
		["description"] = "SAM Rapier Blindfire TR",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["SR SAM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			[262] = true,
			["All"] = true,
			["Ground Units"] = true,
			[27] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [56]
	[57] = 
	{
		["type"] = "rapier_fsa_launcher",
		["name"] = "SAM Rapier LN",
		["category"] = "Air Defence",
		["description"] = "SAM Rapier LN",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["AA_missile"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["NonArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["SAM related"] = true,
			["Air Defence"] = true,
			[260] = true,
			["SR SAM"] = true,
			["SAM LL"] = true,
			["SAM TR"] = true,
			[27] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [57]
	[58] = 
	{
		["type"] = "rapier_fsa_optical_tracker_unit",
		["name"] = "SAM Rapier Tracker",
		["category"] = "Air Defence",
		["description"] = "SAM Rapier Tracker",
		["vehicle"] = true,
		["attribute"] = 
		{
			[261] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["SAM elements"] = true,
			["SAM SR"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["SR SAM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["Air Defence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [58]
	[59] = 
	{
		["type"] = "RD_75",
		["name"] = "SAM SA-2 S-75 RD-75 Amazonka RF",
		["category"] = "Air Defence",
		["description"] = "SAM SA-2 S-75 RD-75 Amazonka RF",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			[337] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["SAM TR"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [59]
	[60] = 
	{
		["type"] = "RLS_19J6",
		["name"] = "SAM SA-5 S-200 ST-68U \"Tin Shield\" SR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-5 S-200 ST-68U \"Tin Shield\" SR",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[309] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["LR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM elements"] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [60]
	[61] = 
	{
		["type"] = "Roland ADS",
		["name"] = "SAM Roland ADS",
		["category"] = "Air Defence",
		["description"] = "SAM Roland ADS",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[31] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["AA_missile"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["NonArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["SAM elements"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			[102] = true,
			["SAM LL"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SR SAM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [61]
	[62] = 
	{
		["type"] = "Roland Radar",
		["name"] = "SAM Roland EWR",
		["category"] = "Air Defence",
		["description"] = "SAM Roland EWR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Air Defence"] = true,
			["SAM related"] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			["SAM SR"] = true,
			[101] = true,
			[32] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [62]
	[63] = 
	{
		["type"] = "RPC_5N62V",
		["name"] = "SAM SA-5 S-200 \"Square Pair\" TR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-5 S-200 \"Square Pair\" TR",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["LR SAM"] = true,
			[313] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [63]
	[64] = 
	{
		["type"] = "S-200_Launcher",
		["name"] = "SAM SA-5 S-200 \"Gammon\" LN",
		["category"] = "Air Defence",
		["description"] = "SAM SA-5 S-200 \"Gammon\" LN",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["AA_missile"] = true,
			[74] = true,
			["LR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			[27] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SAM LL"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [64]
	[65] = 
	{
		["type"] = "S-300PS 40B6M tr",
		["name"] = "SAM SA-10 S-300 \"Grumble\" Flap Lid-A TR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-10 S-300 \"Grumble\" Flap Lid-A TR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["LR SAM"] = true,
			["SAM TR"] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			[4] = true,
			["SAM related"] = true,
			[101] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [65]
	[66] = 
	{
		["type"] = "S-300PS 40B6MD sr",
		["name"] = "SAM SA-10 S-300 \"Grumble\" Clam Shell SR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-10 S-300 \"Grumble\" Clam Shell SR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["LR SAM"] = true,
			["SAM related"] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			["Air Defence"] = true,
			["SAM SR"] = true,
			[101] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			[5] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [66]
	[67] = 
	{
		["type"] = "S-300PS 40B6MD sr_19J6",
		["name"] = "SAM SA-10 S-300 \"Grumble\" Tin Shield SR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-10 S-300 \"Grumble\" Tin Shield SR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["LR SAM"] = true,
			["SAM related"] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			["Air Defence"] = true,
			["SAM SR"] = true,
			[101] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			[345] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [67]
	[68] = 
	{
		["type"] = "S-300PS 54K6 cp",
		["name"] = "SAM SA-10 S-300 \"Grumble\" C2",
		["category"] = "Air Defence",
		["description"] = "SAM SA-10 S-300 \"Grumble\" C2",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			["SAM related"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			[6] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SAM CC"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [68]
	[69] = 
	{
		["type"] = "S-300PS 5H63C 30H6_tr",
		["name"] = "SAM SA-10 S-300 \"Grumble\" Flap Lid-B TR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-10 S-300 \"Grumble\" Flap Lid-B TR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["LR SAM"] = true,
			["SAM TR"] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			["SAM related"] = true,
			["Air Defence"] = true,
			[101] = true,
			["Ground vehicles"] = true,
			[344] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [69]
	[70] = 
	{
		["type"] = "S-300PS 5P85C ln",
		["name"] = "SAM SA-10 S-300 \"Grumble\" TEL C",
		["category"] = "Air Defence",
		["description"] = "SAM SA-10 S-300 \"Grumble\" TEL C",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Armed Air Defence"] = true,
			["SAM elements"] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["SAM related"] = true,
			[8] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["Air Defence"] = true,
			["AA_missile"] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SAM LL"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [70]
	[71] = 
	{
		["type"] = "S-300PS 5P85D ln",
		["name"] = "SAM SA-10 S-300 \"Grumble\" TEL D",
		["category"] = "Air Defence",
		["description"] = "SAM SA-10 S-300 \"Grumble\" TEL D",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Armed Air Defence"] = true,
			["SAM elements"] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["SAM related"] = true,
			["Air Defence"] = true,
			[16] = true,
			[9] = true,
			["NonArmoredUnits"] = true,
			["SAM LL"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["AA_missile"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [71]
	[72] = 
	{
		["type"] = "S-300PS 64H6E sr",
		["name"] = "SAM SA-10 S-300 \"Grumble\" Big Bird SR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-10 S-300 \"Grumble\" Big Bird SR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["LR SAM"] = true,
			["SAM related"] = true,
			[7] = true,
			["SAM elements"] = true,
			[2] = true,
			["Air Defence"] = true,
			["SAM SR"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [72]
	[73] = 
	{
		["type"] = "S-60_Type59_Artillery",
		["name"] = "AAA S-60 57mm",
		["category"] = "Air Defence",
		["description"] = "AAA S-60 57mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["Ground Units"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air Defence"] = true,
			["NonArmoredUnits"] = true,
			["Static AAA"] = true,
			[259] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [73]
	[74] = 
	{
		["type"] = "S_75M_Volhov",
		["name"] = "SAM SA-2 S-75 \"Guideline\" LN",
		["category"] = "Air Defence",
		["description"] = "SAM SA-2 S-75 \"Guideline\" LN",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["AA_missile"] = true,
			[74] = true,
			["LR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			[27] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SAM LL"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [74]
	[75] = 
	{
		["type"] = "SA-11 Buk CC 9S470M1",
		["name"] = "SAM SA-11 Buk \"Gadfly\" C2 ",
		["category"] = "Air Defence",
		["description"] = "SAM SA-11 Buk \"Gadfly\" C2 ",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[2] = true,
			["SAM related"] = true,
			[16] = true,
			[17] = true,
			["Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SAM CC"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [75]
	[76] = 
	{
		["type"] = "SA-11 Buk LN 9A310M1",
		["name"] = "SAM SA-11 Buk \"Gadfly\" Fire Dome TEL",
		["category"] = "Air Defence",
		["description"] = "SAM SA-11 Buk \"Gadfly\" Fire Dome TEL",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["AA_missile"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			[102] = true,
			["SAM LL"] = true,
			["SAM TR"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[19] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [76]
	[77] = 
	{
		["type"] = "SA-11 Buk SR 9S18M1",
		["name"] = "SAM SA-11 Buk \"Gadfly\" Snow Drift SR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-11 Buk \"Gadfly\" Snow Drift SR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["SAM elements"] = true,
			["SAM related"] = true,
			["Vehicles"] = true,
			["MR SAM"] = true,
			[2] = true,
			["Air Defence"] = true,
			["SAM SR"] = true,
			[101] = true,
			["Ground vehicles"] = true,
			[18] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [77]
	[78] = 
	{
		["description"] = "MANPADS SA-18 Igla \"Grouse\" C2",
		["type"] = "SA-18 Igla comm",
		["name"] = "MANPADS SA-18 Igla \"Grouse\" C2",
		["category"] = "Air Defence",
		["infantry"] = true,
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["MANPADS AUX"] = true,
			["Infantry"] = true,
			["NonArmoredUnits"] = true,
			["SAM AUX"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["Ground Units Non Airdefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[55] = true,
			["All"] = true,
			["Ground Units"] = true,
			[27] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [78]
	[79] = 
	{
		["infantry"] = true,
		["type"] = "SA-18 Igla manpad",
		["name"] = "MANPADS SA-18 Igla \"Grouse\"",
		["category"] = "Air Defence",
		["description"] = "MANPADS SA-18 Igla \"Grouse\"",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[16] = true,
			["SAM related"] = true,
			["New infantry"] = true,
			["Armed ground units"] = true,
			["MANPADS"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["Infantry"] = true,
			["Air Defence"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			[54] = true,
			[27] = true,
			["Armed Air Defence"] = true,
			["Ground Units"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [79]
	[80] = 
	{
		["description"] = "MANPADS SA-18 Igla-S \"Grouse\" C2",
		["type"] = "SA-18 Igla-S comm",
		["name"] = "MANPADS SA-18 Igla-S \"Grouse\" C2",
		["category"] = "Air Defence",
		["infantry"] = true,
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["MANPADS AUX"] = true,
			["Infantry"] = true,
			["NonArmoredUnits"] = true,
			["SAM AUX"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["Ground Units Non Airdefence"] = true,
			[53] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[27] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [80]
	[81] = 
	{
		["infantry"] = true,
		["type"] = "SA-18 Igla-S manpad",
		["name"] = "MANPADS SA-18 Igla-S \"Grouse\"",
		["category"] = "Air Defence",
		["description"] = "MANPADS SA-18 Igla-S \"Grouse\"",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[16] = true,
			["SAM related"] = true,
			["New infantry"] = true,
			["Armed ground units"] = true,
			["MANPADS"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["Infantry"] = true,
			["Air Defence"] = true,
			["Ground Units Non Airdefence"] = true,
			[52] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Armed Air Defence"] = true,
			["Ground Units"] = true,
			[27] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [81]
	[82] = 
	{
		["type"] = "snr s-125 tr",
		["name"] = "SAM SA-3 S-125 \"Low Blow\" TR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-3 S-125 \"Low Blow\" TR",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			[73] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			["SAM related"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[101] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [82]
	[83] = 
	{
		["type"] = "SNR_75V",
		["name"] = "SAM SA-2 S-75 \"Fan Song\" TR",
		["category"] = "Air Defence",
		["description"] = "SAM SA-2 S-75 \"Fan Song\" TR",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			[256] = true,
			[101] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["MR SAM"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [83]
	[84] = 
	{
		["infantry"] = true,
		["type"] = "Soldier stinger",
		["name"] = "MANPADS Stinger",
		["category"] = "Air Defence",
		["description"] = "MANPADS Stinger",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[16] = true,
			["SAM related"] = true,
			["New infantry"] = true,
			["Armed ground units"] = true,
			["MANPADS"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["Infantry"] = true,
			["Air Defence"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			[56] = true,
			[27] = true,
			["Armed Air Defence"] = true,
			["Ground Units"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [84]
	[85] = 
	{
		["type"] = "SON_9",
		["name"] = "AAA Fire Can SON-9",
		["category"] = "Air Defence",
		["description"] = "AAA Fire Can SON-9",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			[335] = true,
			["NonArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["SAM related"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			[101] = true,
			["All"] = true,
			["Ground Units"] = true,
			["AAA"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [85]
	[86] = 
	{
		["description"] = "MANPADS Stinger C2",
		["type"] = "Stinger comm",
		["name"] = "MANPADS Stinger C2",
		["category"] = "Air Defence",
		["infantry"] = true,
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["MANPADS AUX"] = true,
			["Infantry"] = true,
			["NonArmoredUnits"] = true,
			["SAM AUX"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["Ground Units Non Airdefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[27] = true,
			["All"] = true,
			[57] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [86]
	[87] = 
	{
		["description"] = "MANPADS Stinger C2 Desert",
		["type"] = "Stinger comm dsr",
		["name"] = "MANPADS Stinger C2 Desert",
		["category"] = "Air Defence",
		["infantry"] = true,
		["vehicle"] = true,
		["attribute"] = 
		{
			[59] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["MANPADS AUX"] = true,
			["Infantry"] = true,
			["NonArmoredUnits"] = true,
			["SAM AUX"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			["Ground Units Non Airdefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[27] = true,
			["All"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [87]
	[88] = 
	{
		["type"] = "Strela-1 9P31",
		["name"] = "SAM SA-9 Strela 1 \"Gaskin\" TEL",
		["category"] = "Air Defence",
		["description"] = "SAM SA-9 Strela 1 \"Gaskin\" TEL",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[16] = true,
			["SAM related"] = true,
			["AA_missile"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			[25] = true,
			["Air Defence"] = true,
			["SR SAM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			[27] = true,
			["Armed Air Defence"] = true,
			["Ground Units"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "SA-9 Strela-1 9P31",
		}, -- end of ["aliases"]
	}, -- end of [88]
	[89] = 
	{
		["type"] = "Strela-10M3",
		["name"] = "SAM SA-13 Strela 10M3 \"Gopher\" TEL",
		["category"] = "Air Defence",
		["description"] = "SAM SA-13 Strela 10M3 \"Gopher\" TEL",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM related"] = true,
			["AA_missile"] = true,
			["IR Guided SAM"] = true,
			["SAM"] = true,
			["NonArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Air Defence"] = true,
			[26] = true,
			["SR SAM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			[104] = true,
			["Armed Air Defence"] = true,
			["Ground Units"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "SA-13 Strela-10M3 9A35M3",
		}, -- end of ["aliases"]
	}, -- end of [89]
	[90] = 
	{
		["type"] = "Tor 9A331",
		["name"] = "SAM SA-15 Tor \"Gauntlet\"",
		["category"] = "Air Defence",
		["description"] = "SAM SA-15 Tor \"Gauntlet\"",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["SAM SR"] = true,
			["Ground vehicles"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["AA_missile"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["SAM related"] = true,
			[102] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			[28] = true,
			["All"] = true,
			["Ground Units"] = true,
			["SR SAM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [90]
	[91] = 
	{
		["type"] = "tt_ZU-23",
		["name"] = "SPAAA LC with ZU-23",
		["category"] = "Air Defence",
		["description"] = "SPAAA LC with ZU-23",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["Ground Units"] = true,
			["AA_flak"] = true,
			["All"] = true,
			[326] = true,
			[16] = true,
			["AAA"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["Mobile AAA"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [91]
	[92] = 
	{
		["type"] = "Ural-375 ZU-23",
		["name"] = "AAA ZU-23 on Ural-4320",
		["category"] = "Air Defence",
		["description"] = "AAA ZU-23 on Ural-4320",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["Ground Units"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			[49] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["Mobile AAA"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [92]
	[93] = 
	{
		["type"] = "Ural-375 ZU-23 Insurgent",
		["name"] = "AAA ZU-23 on Ural-4320 Insurgent",
		["category"] = "Air Defence",
		["description"] = "AAA ZU-23 on Ural-4320 Insurgent",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["Ground Units"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[72] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["Mobile AAA"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [93]
	[94] = 
	{
		["type"] = "Vulcan",
		["name"] = "SPAAA Vulcan M163",
		["category"] = "Air Defence",
		["description"] = "SPAAA Vulcan M163",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["SAM elements"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			[105] = true,
			["Mobile AAA"] = true,
			[46] = true,
			["NonArmoredUnits"] = true,
			["AAA"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["Air Defence"] = true,
			["Armed Air Defence"] = true,
			["SAM TR"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM related"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "M163 Vulcan",
		}, -- end of ["aliases"]
	}, -- end of [94]
	[95] = 
	{
		["type"] = "ZSU-23-4 Shilka",
		["name"] = "SPAAA ZSU-23-4 Shilka \"Gun Dish\"",
		["category"] = "Air Defence",
		["description"] = "SPAAA ZSU-23-4 Shilka \"Gun Dish\"",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["SAM elements"] = true,
			[16] = true,
			["AAA"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			[105] = true,
			["Mobile AAA"] = true,
			["NonArmoredUnits"] = true,
			["SAM related"] = true,
			["Ground vehicles"] = true,
			["Air Defence"] = true,
			["Vehicles"] = true,
			["AA_flak"] = true,
			["NonAndLightArmoredUnits"] = true,
			["SAM TR"] = true,
			["All"] = true,
			["Armed Air Defence"] = true,
			["Ground Units"] = true,
			[30] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [95]
	[96] = 
	{
		["type"] = "ZSU_57_2",
		["name"] = "SPAAA ZSU-57-2",
		["category"] = "Air Defence",
		["description"] = "SPAAA ZSU-57-2",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["Ground Units"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air Defence"] = true,
			[257] = true,
			["Armed Air Defence"] = true,
			["Mobile AAA"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [96]
	[97] = 
	{
		["type"] = "ZU-23 Closed Insurgent",
		["name"] = "AAA ZU-23 Insurgent Closed Emplacement",
		["category"] = "Air Defence",
		["description"] = "AAA ZU-23 Insurgent Closed Emplacement",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			[2] = true,
			["AA_flak"] = true,
			["All"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			[16] = true,
			["AAA"] = true,
			["Armed Air Defence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air Defence"] = true,
			[71] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [97]
	[98] = 
	{
		["type"] = "ZU-23 Emplacement",
		["name"] = "AAA ZU-23 Emplacement",
		["category"] = "Air Defence",
		["description"] = "AAA ZU-23 Emplacement",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			["NonArmoredUnits"] = true,
			["AA_flak"] = true,
			["All"] = true,
			["AAA"] = true,
			[16] = true,
			[47] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [98]
	[99] = 
	{
		["type"] = "ZU-23 Emplacement Closed",
		["name"] = "AAA ZU-23 Closed Emplacement",
		["category"] = "Air Defence",
		["description"] = "AAA ZU-23 Closed Emplacement",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			[48] = true,
			[26] = true,
			["Vehicles"] = true,
			["AA_flak"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[16] = true,
			["AAA"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [99]
	[100] = 
	{
		["type"] = "ZU-23 Insurgent",
		["name"] = "AAA ZU-23 Insurgent Emplacement",
		["category"] = "Air Defence",
		["description"] = "AAA ZU-23 Insurgent Emplacement",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			[26] = true,
			[70] = true,
			["AA_flak"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[16] = true,
			["AAA"] = true,
			["Rocket Attack Valid AirDefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Air Defence"] = true,
			["Static AAA"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [100]
	[101] = 
	{
		["type"] = "Cow",
		["name"] = "Cow",
		["category"] = "Animal",
		["description"] = "Cow",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
			[100] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [101]
	[102] = 
	{
		["type"] = "AAV7",
		["name"] = "APC AAV-7 Amphibious",
		["category"] = "Armor",
		["description"] = "APC AAV-7 Amphibious",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [102]
	[103] = 
	{
		["type"] = "BMD-1",
		["name"] = "IFV BMD-1",
		["category"] = "Armor",
		["description"] = "IFV BMD-1",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[7] = true,
			[104] = true,
			["Armed vehicles"] = true,
			["IFV"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["ATGM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [103]
	[104] = 
	{
		["type"] = "BMP-1",
		["name"] = "IFV BMP-1",
		["category"] = "Armor",
		["description"] = "IFV BMP-1",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[7] = true,
			[104] = true,
			["Armed vehicles"] = true,
			["IFV"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["ATGM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [104]
	[105] = 
	{
		["type"] = "BMP-2",
		["name"] = "IFV BMP-2",
		["category"] = "Armor",
		["description"] = "IFV BMP-2",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[7] = true,
			[104] = true,
			["Armed vehicles"] = true,
			["IFV"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["ATGM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [105]
	[106] = 
	{
		["type"] = "BMP-3",
		["name"] = "IFV BMP-3",
		["category"] = "Armor",
		["description"] = "IFV BMP-3",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[7] = true,
			[104] = true,
			["Armed vehicles"] = true,
			["IFV"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["ATGM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [106]
	[107] = 
	{
		["type"] = "BRDM-2",
		["name"] = "Scout BRDM-2",
		["category"] = "Armor",
		["description"] = "Scout BRDM-2",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [107]
	[108] = 
	{
		["type"] = "BTR-80",
		["name"] = "APC BTR-80",
		["category"] = "Armor",
		["description"] = "APC BTR-80",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [108]
	[109] = 
	{
		["type"] = "BTR-82A",
		["name"] = "IFV BTR-82A",
		["category"] = "Armor",
		["description"] = "IFV BTR-82A",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			[258] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [109]
	[110] = 
	{
		["type"] = "BTR_D",
		["name"] = "APC BTR-RD",
		["category"] = "Armor",
		["description"] = "APC BTR-RD",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["ATGM"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[104] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [110]
	[111] = 
	{
		["type"] = "Centaur_IV",
		["name"] = "Tk Centaur IV CS",
		["category"] = "Armor",
		["description"] = "Tk Centaur IV CS",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [111]
	[112] = 
	{
		["type"] = "Challenger2",
		["name"] = "MBT Challenger II",
		["category"] = "Armor",
		["description"] = "MBT Challenger II",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			[16] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [112]
	[113] = 
	{
		["type"] = "Chieftain_mk3",
		["name"] = "MBT Chieftain Mk.3",
		["category"] = "Armor",
		["description"] = "MBT Chieftain Mk.3",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[297] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [113]
	[114] = 
	{
		["type"] = "Churchill_VII",
		["name"] = "Tk Churchill VII",
		["category"] = "Armor",
		["description"] = "Tk Churchill VII",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [114]
	[115] = 
	{
		["type"] = "Cobra",
		["name"] = "Scout Cobra",
		["category"] = "Armor",
		["description"] = "Scout Cobra",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [115]
	[116] = 
	{
		["type"] = "Cromwell_IV",
		["name"] = "Tk Cromwell IV",
		["category"] = "Armor",
		["description"] = "Tk Cromwell IV",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [116]
	[117] = 
	{
		["type"] = "Daimler_AC",
		["name"] = "Car Daimler Armored",
		["category"] = "Armor",
		["description"] = "Car Daimler Armored",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["IFV"] = true,
			[7] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [117]
	[118] = 
	{
		["type"] = "Elefant_SdKfz_184",
		["name"] = "SPG Elefant TD",
		["category"] = "Armor",
		["description"] = "SPG Elefant TD",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [118]
	[119] = 
	{
		["type"] = "HL_DSHK",
		["name"] = "Scout HL with DSHK 12.7mm",
		["category"] = "Armor",
		["description"] = "Scout HL with DSHK 12.7mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			[321] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [119]
	[120] = 
	{
		["type"] = "HL_KORD",
		["name"] = "Scout HL with KORD 12.7mm",
		["category"] = "Armor",
		["description"] = "Scout HL with KORD 12.7mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			[322] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [120]
	[121] = 
	{
		["type"] = "Jagdpanther_G1",
		["name"] = "SPG Jagdpanther TD",
		["category"] = "Armor",
		["description"] = "SPG Jagdpanther TD",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [121]
	[122] = 
	{
		["type"] = "JagdPz_IV",
		["name"] = "SPG Jagdpanzer IV TD",
		["category"] = "Armor",
		["description"] = "SPG Jagdpanzer IV TD",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [122]
	[123] = 
	{
		["type"] = "LAV-25",
		["name"] = "IFV LAV-25",
		["category"] = "Armor",
		["description"] = "IFV LAV-25",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			[7] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [123]
	[124] = 
	{
		["type"] = "Leclerc",
		["name"] = "MBT Leclerc",
		["category"] = "Armor",
		["description"] = "MBT Leclerc",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			[16] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [124]
	[125] = 
	{
		["type"] = "Leopard-2",
		["name"] = "MBT Leopard-2A6M",
		["category"] = "Armor",
		["description"] = "MBT Leopard-2A6M",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			["Ground vehicles"] = true,
			[17] = true,
			[299] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [125]
	[126] = 
	{
		["type"] = "leopard-2A4",
		["name"] = "MBT Leopard-2A4",
		["category"] = "Armor",
		["description"] = "MBT Leopard-2A4",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			["Ground vehicles"] = true,
			[17] = true,
			["Vehicles"] = true,
			[300] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [126]
	[127] = 
	{
		["type"] = "leopard-2A4_trs",
		["name"] = "MBT Leopard-2A4 Trs",
		["category"] = "Armor",
		["description"] = "MBT Leopard-2A4 Trs",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			["Ground vehicles"] = true,
			[17] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			[301] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [127]
	[128] = 
	{
		["type"] = "Leopard-2A5",
		["name"] = "MBT Leopard-2A5",
		["category"] = "Armor",
		["description"] = "MBT Leopard-2A5",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			["Ground vehicles"] = true,
			[298] = true,
			["Vehicles"] = true,
			[17] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [128]
	[129] = 
	{
		["type"] = "Leopard1A3",
		["name"] = "MBT Leopard 1A3",
		["category"] = "Armor",
		["description"] = "MBT Leopard 1A3",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "LEO1A3",
		}, -- end of ["aliases"]
	}, -- end of [129]
	[130] = 
	{
		["type"] = "M-1 Abrams",
		["name"] = "MBT M1A2 Abrams",
		["category"] = "Armor",
		["description"] = "MBT M1A2 Abrams",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["Modern Tanks"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			[16] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			["Tanks"] = true,
			["Armed vehicles"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [130]
	[131] = 
	{
		["type"] = "M-113",
		["name"] = "APC M113",
		["category"] = "Armor",
		["description"] = "APC M113",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [131]
	[132] = 
	{
		["type"] = "M-2 Bradley",
		["name"] = "IFV M2A2 Bradley",
		["category"] = "Armor",
		["description"] = "IFV M2A2 Bradley",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["IFV"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			[7] = true,
			[104] = true,
			["Infantry carriers"] = true,
			["Ground Units"] = true,
			["All"] = true,
			["Datalink"] = true,
			["ATGM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "M2A2 Bradley",
		}, -- end of ["aliases"]
	}, -- end of [132]
	[133] = 
	{
		["type"] = "M-60",
		["name"] = "MBT M60A3 Patton",
		["category"] = "Armor",
		["description"] = "MBT M60A3 Patton",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [133]
	[134] = 
	{
		["type"] = "M1043 HMMWV Armament",
		["name"] = "Scout HMMWV",
		["category"] = "Armor",
		["description"] = "Scout HMMWV",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[14] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [134]
	[135] = 
	{
		["type"] = "M1045 HMMWV TOW",
		["name"] = "ATGM HMMWV",
		["category"] = "Armor",
		["description"] = "ATGM HMMWV",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			["ATGM"] = true,
			[14] = true,
			[104] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [135]
	[136] = 
	{
		["type"] = "M10_GMC",
		["name"] = "SPG M10 GMC TD",
		["category"] = "Armor",
		["description"] = "SPG M10 GMC TD",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [136]
	[137] = 
	{
		["type"] = "M1126 Stryker ICV",
		["name"] = "IFV M1126 Stryker ICV",
		["category"] = "Armor",
		["description"] = "IFV M1126 Stryker ICV",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			[80] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [137]
	[138] = 
	{
		["type"] = "M1128 Stryker MGS",
		["name"] = "SPG Stryker MGS",
		["category"] = "Armor",
		["description"] = "SPG Stryker MGS",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["Modern Tanks"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			[80] = true,
			["Ground Units Non Airdefence"] = true,
			["IFV"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Tanks"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [138]
	[139] = 
	{
		["type"] = "M1134 Stryker ATGM",
		["name"] = "ATGM Stryker",
		["category"] = "Armor",
		["description"] = "ATGM Stryker",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			[80] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			[104] = true,
			["IFV"] = true,
			["Ground Units"] = true,
			["All"] = true,
			["Datalink"] = true,
			["ATGM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [139]
	[140] = 
	{
		["type"] = "M1A2C_SEP_V3",
		["name"] = "MBT M1A2C SEP v3 Abrams",
		["category"] = "Armor",
		["description"] = "MBT M1A2C SEP v3 Abrams",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["Modern Tanks"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			[16] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			["Tanks"] = true,
			["Armed vehicles"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [140]
	[141] = 
	{
		["type"] = "M2A1_halftrack",
		["name"] = "APC M2A1 Halftrack",
		["category"] = "Armor",
		["description"] = "APC M2A1 Halftrack",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [141]
	[142] = 
	{
		["type"] = "M4_Sherman",
		["name"] = "Tk M4 Sherman",
		["category"] = "Armor",
		["description"] = "Tk M4 Sherman",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [142]
	[143] = 
	{
		["type"] = "M4_Tractor",
		["name"] = "Tractor M4 High Speed",
		["category"] = "Armor",
		["description"] = "Tractor M4 High Speed",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["IFV"] = true,
			[7] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [143]
	[144] = 
	{
		["type"] = "M4A4_Sherman_FF",
		["name"] = "Tk M4A4 Sherman Firefly",
		["category"] = "Armor",
		["description"] = "Tk M4A4 Sherman Firefly",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [144]
	[145] = 
	{
		["type"] = "M8_Greyhound",
		["name"] = "Scout M8 Greyhound AC",
		["category"] = "Armor",
		["description"] = "Scout M8 Greyhound AC",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["IFV"] = true,
			[7] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [145]
	[146] = 
	{
		["type"] = "Marder",
		["name"] = "IFV Marder",
		["category"] = "Armor",
		["description"] = "IFV Marder",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["ATGM"] = true,
			[7] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["IFV"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [146]
	[147] = 
	{
		["type"] = "MaxxPro_MRAP",
		["name"] = "APC MRAP MaxxPro",
		["category"] = "Armor",
		["description"] = "APC MRAP MaxxPro",
		["vehicle"] = true,
		["attribute"] = 
		{
			[347] = true,
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [147]
	[148] = 
	{
		["type"] = "MCV-80",
		["name"] = "IFV Warrior ",
		["category"] = "Armor",
		["description"] = "IFV Warrior ",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [148]
	[149] = 
	{
		["type"] = "Merkava_Mk4",
		["name"] = "MBT Merkava IV",
		["category"] = "Armor",
		["description"] = "MBT Merkava IV",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["Modern Tanks"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			[16] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			["Tanks"] = true,
			["Armed vehicles"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [149]
	[150] = 
	{
		["type"] = "MTLB",
		["name"] = "APC MTLB",
		["category"] = "Armor",
		["description"] = "APC MTLB",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [150]
	[151] = 
	{
		["type"] = "PT_76",
		["name"] = "LT PT-76",
		["category"] = "Armor",
		["description"] = "LT PT-76",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Tanks"] = true,
			[26] = true,
			["HeavyArmoredUnits"] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[296] = true,
			["Armed vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [151]
	[152] = 
	{
		["type"] = "Pz_IV_H",
		["name"] = "Tk PzIV H",
		["category"] = "Armor",
		["description"] = "Tk PzIV H",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [152]
	[153] = 
	{
		["type"] = "Pz_V_Panther_G",
		["name"] = "Tk Panther G (Pz V)",
		["category"] = "Armor",
		["description"] = "Tk Panther G (Pz V)",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [153]
	[154] = 
	{
		["type"] = "Sd_Kfz_234_2_Puma",
		["name"] = "Scout Puma AC",
		["category"] = "Armor",
		["description"] = "Scout Puma AC",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["IFV"] = true,
			[7] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [154]
	[155] = 
	{
		["type"] = "Sd_Kfz_251",
		["name"] = "APC Sd.Kfz.251 Halftrack",
		["category"] = "Armor",
		["description"] = "APC Sd.Kfz.251 Halftrack",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [155]
	[156] = 
	{
		["type"] = "Stug_III",
		["name"] = "SPG StuG III G AG",
		["category"] = "Armor",
		["description"] = "SPG StuG III G AG",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [156]
	[157] = 
	{
		["type"] = "Stug_IV",
		["name"] = "SPG StuG IV AG",
		["category"] = "Armor",
		["description"] = "SPG StuG IV AG",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [157]
	[158] = 
	{
		["type"] = "SturmPzIV",
		["name"] = "SPG Brummbaer AG",
		["category"] = "Armor",
		["description"] = "SPG Brummbaer AG",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			[16] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [158]
	[159] = 
	{
		["type"] = "T-55",
		["name"] = "MBT T-55",
		["category"] = "Armor",
		["description"] = "MBT T-55",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [159]
	[160] = 
	{
		["type"] = "T-72B",
		["name"] = "MBT T-72B",
		["category"] = "Armor",
		["description"] = "MBT T-72B",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			[16] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [160]
	[161] = 
	{
		["type"] = "T-72B3",
		["name"] = "MBT T-72B3",
		["category"] = "Armor",
		["description"] = "MBT T-72B3",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			[16] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [161]
	[162] = 
	{
		["type"] = "T-80UD",
		["name"] = "MBT T-80U",
		["category"] = "Armor",
		["description"] = "MBT T-80U",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			[16] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [162]
	[163] = 
	{
		["type"] = "T-90",
		["name"] = "MBT T-90",
		["category"] = "Armor",
		["description"] = "MBT T-90",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["AntiAir Armed Vehicles"] = true,
			[2] = true,
			["Modern Tanks"] = true,
			["Armed vehicles"] = true,
			[16] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [163]
	[164] = 
	{
		["type"] = "Tetrarch",
		["name"] = "Tk Tetrach",
		["category"] = "Armor",
		["description"] = "Tk Tetrach",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["IFV"] = true,
			[7] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [164]
	[165] = 
	{
		["type"] = "Tiger_I",
		["name"] = "Tk Tiger 1",
		["category"] = "Armor",
		["description"] = "Tk Tiger 1",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [165]
	[166] = 
	{
		["type"] = "Tiger_II_H",
		["name"] = "Tk Tiger II",
		["category"] = "Armor",
		["description"] = "Tk Tiger II",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [166]
	[167] = 
	{
		["type"] = "TPZ",
		["name"] = "APC TPz Fuchs ",
		["category"] = "Armor",
		["description"] = "APC TPz Fuchs ",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [167]
	[168] = 
	{
		["type"] = "tt_DSHK",
		["name"] = "Scout LC with DSHK 12.7mm",
		["category"] = "Armor",
		["description"] = "Scout LC with DSHK 12.7mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armed vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			[323] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armored vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [168]
	[169] = 
	{
		["type"] = "tt_KORD",
		["name"] = "Scout LC with KORD 12.7mm",
		["category"] = "Armor",
		["description"] = "Scout LC with KORD 12.7mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[324] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [169]
	[170] = 
	{
		["type"] = "TYPE-59",
		["name"] = "MT Type 59",
		["category"] = "Armor",
		["description"] = "MT Type 59",
		["vehicle"] = true,
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			["Tanks"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Old Tanks"] = true,
			[17] = true,
			["Vehicles"] = true,
			[2] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [170]
	[171] = 
	{
		["type"] = "VAB_Mephisto",
		["name"] = "ATGM VAB Mephisto",
		["category"] = "Armor",
		["description"] = "ATGM VAB Mephisto",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			[80] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			[104] = true,
			["IFV"] = true,
			["Ground Units"] = true,
			["All"] = true,
			["Datalink"] = true,
			["ATGM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [171]
	[172] = 
	{
		["type"] = "ZBD04A",
		["name"] = "ZBD-04A",
		["category"] = "Armor",
		["description"] = "ZBD-04A",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			["IFV"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			[276] = true,
			[104] = true,
			["Infantry carriers"] = true,
			["Ground Units"] = true,
			["All"] = true,
			["Datalink"] = true,
			["ATGM"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [172]
	[173] = 
	{
		["type"] = "ZTZ96B",
		["name"] = "ZTZ-96B",
		["category"] = "Armor",
		["description"] = "ZTZ-96B",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["Modern Tanks"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Ground Units Non Airdefence"] = true,
			[275] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["Tanks"] = true,
			["AntiAir Armed Vehicles"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [173]
	[174] = 
	{
		["type"] = "2B11 mortar",
		["name"] = "Mortar 2B11 120mm",
		["category"] = "Artillery",
		["description"] = "Mortar 2B11 120mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [174]
	[175] = 
	{
		["type"] = "Grad-URAL",
		["name"] = "MLRS BM-21 Grad 122mm",
		["category"] = "Artillery",
		["description"] = "MLRS BM-21 Grad 122mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			[63] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["MLRS"] = true,
			["Ground Units"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "MLRS BM-21 Grad",
		}, -- end of ["aliases"]
	}, -- end of [175]
	[176] = 
	{
		["type"] = "Grad_FDDM",
		["name"] = "Grad MRL FDDM (FC)",
		["category"] = "Artillery",
		["description"] = "Grad MRL FDDM (FC)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "Boman",
		}, -- end of ["aliases"]
	}, -- end of [176]
	[177] = 
	{
		["type"] = "HL_B8M1",
		["name"] = "MLRS HL with B8M1 80mm",
		["category"] = "Artillery",
		["description"] = "MLRS HL with B8M1 80mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["</WSTYPE>"] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["NonAndLightArmoredUnits"] = true,
			[63] = true,
			["Indirect fire"] = true,
			["MLRS"] = true,
			["Ground Units"] = true,
			[27] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [177]
	[178] = 
	{
		["type"] = "L118_Unit",
		["name"] = "L118 Light Artillery Gun",
		["category"] = "Artillery",
		["description"] = "L118 Light Artillery Gun",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[348] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [178]
	[179] = 
	{
		["type"] = "LeFH_18-40-105",
		["name"] = "FH LeFH-18 105mm",
		["category"] = "Artillery",
		["description"] = "FH LeFH-18 105mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[319] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [179]
	[180] = 
	{
		["type"] = "M-109",
		["name"] = "SPH M109 Paladin 155mm",
		["category"] = "Artillery",
		["description"] = "SPH M109 Paladin 155mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "M109",
		}, -- end of ["aliases"]
	}, -- end of [180]
	[181] = 
	{
		["type"] = "M12_GMC",
		["name"] = "SPH M12 GMC 155mm",
		["category"] = "Artillery",
		["description"] = "SPH M12 GMC 155mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			[16] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [181]
	[182] = 
	{
		["type"] = "M2A1-105",
		["name"] = "FH M2A1 105mm",
		["category"] = "Artillery",
		["description"] = "FH M2A1 105mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[320] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Artillery"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [182]
	[183] = 
	{
		["type"] = "MLRS",
		["name"] = "MLRS M270 227mm",
		["category"] = "Artillery",
		["description"] = "MLRS M270 227mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground Units"] = true,
			[63] = true,
			["NonAndLightArmoredUnits"] = true,
			[27] = true,
			["Indirect fire"] = true,
			["All"] = true,
			["Datalink"] = true,
			["MLRS"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "M270 MLRS",
		}, -- end of ["aliases"]
	}, -- end of [183]
	[184] = 
	{
		["type"] = "MLRS FDDM",
		["name"] = "MRLS FDDM (FC)",
		["category"] = "Artillery",
		["description"] = "MRLS FDDM (FC)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[14] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [184]
	[185] = 
	{
		["type"] = "Pak40",
		["name"] = "FH Pak 40 75mm",
		["category"] = "Artillery",
		["description"] = "FH Pak 40 75mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[318] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [185]
	[186] = 
	{
		["type"] = "PLZ05",
		["name"] = "PLZ-05",
		["category"] = "Artillery",
		["description"] = "PLZ-05",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[279] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [186]
	[187] = 
	{
		["type"] = "SAU 2-C9",
		["name"] = "SPM 2S9 Nona 120mm M",
		["category"] = "Artillery",
		["description"] = "SPM 2S9 Nona 120mm M",
		["vehicle"] = true,
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [187]
	[188] = 
	{
		["type"] = "SAU Akatsia",
		["name"] = "SPH 2S3 Akatsia 152mm",
		["category"] = "Artillery",
		["description"] = "SPH 2S3 Akatsia 152mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "2S3 Akatsia",
		}, -- end of ["aliases"]
	}, -- end of [188]
	[189] = 
	{
		["type"] = "SAU Gvozdika",
		["name"] = "SPH 2S1 Gvozdika 122mm",
		["category"] = "Artillery",
		["description"] = "SPH 2S1 Gvozdika 122mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [189]
	[190] = 
	{
		["type"] = "SAU Msta",
		["name"] = "SPH 2S19 Msta 152mm",
		["category"] = "Artillery",
		["description"] = "SPH 2S19 Msta 152mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "2S19 Msta",
		}, -- end of ["aliases"]
	}, -- end of [190]
	[191] = 
	{
		["type"] = "Smerch",
		["name"] = "MLRS 9A52 Smerch CM 300mm",
		["category"] = "Artillery",
		["description"] = "MLRS 9A52 Smerch CM 300mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			[63] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["MLRS"] = true,
			["Ground Units"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [191]
	[192] = 
	{
		["type"] = "Smerch_HE",
		["name"] = "MLRS 9A52 Smerch HE 300mm",
		["category"] = "Artillery",
		["description"] = "MLRS 9A52 Smerch HE 300mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			[63] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["MLRS"] = true,
			["Ground Units"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [192]
	[193] = 
	{
		["type"] = "SpGH_Dana",
		["name"] = "SPH Dana vz77 152mm",
		["category"] = "Artillery",
		["description"] = "SPH Dana vz77 152mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [193]
	[194] = 
	{
		["type"] = "T155_Firtina",
		["name"] = "SPH T155 Firtina 155mm",
		["category"] = "Artillery",
		["description"] = "SPH T155 Firtina 155mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			["Vehicles"] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Indirect fire"] = true,
			["Armed vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Armed ground units"] = true,
			[302] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [194]
	[195] = 
	{
		["type"] = "tt_B8M1",
		["name"] = "MLRS LC with B8M1 80mm",
		["category"] = "Artillery",
		["description"] = "MLRS LC with B8M1 80mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["</WSTYPE>"] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["NonAndLightArmoredUnits"] = true,
			[63] = true,
			["Indirect fire"] = true,
			["MLRS"] = true,
			["Ground Units"] = true,
			[27] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [195]
	[196] = 
	{
		["type"] = "Uragan_BM-27",
		["name"] = "MLRS 9K57 Uragan BM-27 220mm",
		["category"] = "Artillery",
		["description"] = "MLRS 9K57 Uragan BM-27 220mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			[63] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["MLRS"] = true,
			["Ground Units"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [196]
	[197] = 
	{
		["type"] = "Wespe124",
		["name"] = "SPH Sd.Kfz.124 Wespe 105mm",
		["category"] = "Artillery",
		["description"] = "SPH Sd.Kfz.124 Wespe 105mm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Vehicles"] = true,
			[317] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed ground units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [197]
	[198] = 
	{
		["desc"] = 
		{
			["maxMass"] = 2000,
			["minMass"] = 1000,
		}, -- end of ["desc"]
		["type"] = "ammo_cargo",
		["name"] = "Ammo",
		["category"] = "Cargo",
		["defaultMass"] = 1500,
		["description"] = "Ammo",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [198]
	[199] = 
	{
		["desc"] = 
		{
			["maxMass"] = 480,
			["minMass"] = 100,
		}, -- end of ["desc"]
		["type"] = "barrels_cargo",
		["name"] = "Barrels",
		["category"] = "Cargo",
		["defaultMass"] = 480,
		["description"] = "Barrels",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [199]
	[200] = 
	{
		["desc"] = 
		{
			["maxMass"] = 4000,
			["minMass"] = 100,
		}, -- end of ["desc"]
		["type"] = "container_cargo",
		["name"] = "Container",
		["category"] = "Cargo",
		["defaultMass"] = 1200,
		["description"] = "Container",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [200]
	[201] = 
	{
		["desc"] = 
		{
			["maxMass"] = 823,
			["minMass"] = 823,
		}, -- end of ["desc"]
		["type"] = "f_bar_cargo",
		["name"] = "F-shape barrier",
		["category"] = "Cargo",
		["defaultMass"] = 823,
		["description"] = "F-shape barrier",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [201]
	[202] = 
	{
		["desc"] = 
		{
			["maxMass"] = 5000,
			["minMass"] = 800,
		}, -- end of ["desc"]
		["type"] = "fueltank_cargo",
		["name"] = "Fueltank",
		["category"] = "Cargo",
		["defaultMass"] = 2400,
		["description"] = "Fueltank",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [202]
	[203] = 
	{
		["desc"] = 
		{
			["maxMass"] = 10000,
			["minMass"] = 3800,
		}, -- end of ["desc"]
		["type"] = "iso_container",
		["name"] = "ISO container",
		["category"] = "Cargo",
		["defaultMass"] = 4500,
		["description"] = "ISO container",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [203]
	[204] = 
	{
		["desc"] = 
		{
			["maxMass"] = 10000,
			["minMass"] = 2200,
		}, -- end of ["desc"]
		["type"] = "iso_container_small",
		["name"] = "ISO container small",
		["category"] = "Cargo",
		["defaultMass"] = 3200,
		["description"] = "ISO container small",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [204]
	[205] = 
	{
		["desc"] = 
		{
			["maxMass"] = 8100,
			["minMass"] = 100,
		}, -- end of ["desc"]
		["type"] = "l118",
		["name"] = "L118 Light Artillery",
		["category"] = "Cargo",
		["defaultMass"] = 2100,
		["description"] = "L118 Light Artillery",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [205]
	[206] = 
	{
		["desc"] = 
		{
			["maxMass"] = 840,
			["minMass"] = 840,
		}, -- end of ["desc"]
		["type"] = "m117_cargo",
		["name"] = "M117 bombs",
		["category"] = "Cargo",
		["defaultMass"] = 840,
		["description"] = "M117 bombs",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [206]
	[207] = 
	{
		["desc"] = 
		{
			["maxMass"] = 5000,
			["minMass"] = 700,
		}, -- end of ["desc"]
		["type"] = "oiltank_cargo",
		["name"] = "Oiltank",
		["category"] = "Cargo",
		["defaultMass"] = 2300,
		["description"] = "Oiltank",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [207]
	[208] = 
	{
		["desc"] = 
		{
			["maxMass"] = 4815,
			["minMass"] = 4815,
		}, -- end of ["desc"]
		["type"] = "pipes_big_cargo",
		["name"] = "Pipes big",
		["category"] = "Cargo",
		["defaultMass"] = 4815,
		["description"] = "Pipes big",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [208]
	[209] = 
	{
		["desc"] = 
		{
			["maxMass"] = 4350,
			["minMass"] = 4350,
		}, -- end of ["desc"]
		["type"] = "pipes_small_cargo",
		["name"] = "Pipes small",
		["category"] = "Cargo",
		["defaultMass"] = 4350,
		["description"] = "Pipes small",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [209]
	[210] = 
	{
		["desc"] = 
		{
			["maxMass"] = 5000,
			["minMass"] = 5000,
		}, -- end of ["desc"]
		["type"] = "tetrapod_cargo",
		["name"] = "Tetrapod",
		["category"] = "Cargo",
		["defaultMass"] = 5000,
		["description"] = "Tetrapod",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [210]
	[211] = 
	{
		["desc"] = 
		{
			["maxMass"] = 4747,
			["minMass"] = 4747,
		}, -- end of ["desc"]
		["type"] = "trunks_long_cargo",
		["name"] = "Trunks long",
		["category"] = "Cargo",
		["defaultMass"] = 4747,
		["description"] = "Trunks long",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [211]
	[212] = 
	{
		["desc"] = 
		{
			["maxMass"] = 5000,
			["minMass"] = 5000,
		}, -- end of ["desc"]
		["type"] = "trunks_small_cargo",
		["name"] = "Trunks short",
		["category"] = "Cargo",
		["defaultMass"] = 5000,
		["description"] = "Trunks short",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [212]
	[213] = 
	{
		["desc"] = 
		{
			["maxMass"] = 10000,
			["minMass"] = 100,
		}, -- end of ["desc"]
		["type"] = "uh1h_cargo",
		["name"] = "UH-1H cargo",
		["category"] = "Cargo",
		["defaultMass"] = 1000,
		["description"] = "UH-1H cargo",
		["attribute"] = 
		{
			["Cargos"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [213]
	[214] = 
	{
		["type"] = "Boxcartrinity",
		["name"] = "Flatcar",
		["category"] = "Carriage",
		["description"] = "Flatcar",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[51] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [214]
	[215] = 
	{
		["type"] = "Coach a passenger",
		["name"] = "Passenger Car",
		["category"] = "Carriage",
		["description"] = "Passenger Car",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[54] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [215]
	[216] = 
	{
		["type"] = "Coach a platform",
		["name"] = "Coach Platform",
		["category"] = "Carriage",
		["description"] = "Coach Platform",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			[53] = true,
			["Unarmed vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [216]
	[217] = 
	{
		["type"] = "Coach a tank blue",
		["name"] = "Tank Car blue",
		["category"] = "Carriage",
		["description"] = "Tank Car blue",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[50] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [217]
	[218] = 
	{
		["type"] = "Coach a tank yellow",
		["name"] = "Tank Car yellow",
		["category"] = "Carriage",
		["description"] = "Tank Car yellow",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[98] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [218]
	[219] = 
	{
		["type"] = "Coach cargo",
		["name"] = "Freight Van",
		["category"] = "Carriage",
		["description"] = "Freight Van",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[51] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [219]
	[220] = 
	{
		["type"] = "Coach cargo open",
		["name"] = "Open Wagon",
		["category"] = "Carriage",
		["description"] = "Open Wagon",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[51] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [220]
	[221] = 
	{
		["type"] = "DR_50Ton_Flat_Wagon",
		["name"] = "DR 50-ton flat wagon",
		["category"] = "Carriage",
		["description"] = "DR 50-ton flat wagon",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			[53] = true,
			["Unarmed vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [221]
	[222] = 
	{
		["type"] = "German_covered_wagon_G10",
		["name"] = "Wagon G10 (Germany)",
		["category"] = "Carriage",
		["description"] = "Wagon G10 (Germany)",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[51] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [222]
	[223] = 
	{
		["type"] = "German_tank_wagon",
		["name"] = "Tank Car (Germany)",
		["category"] = "Carriage",
		["description"] = "Tank Car (Germany)",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Unarmed vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [223]
	[224] = 
	{
		["type"] = "Tankcartrinity",
		["name"] = "Tank Cartrinity",
		["category"] = "Carriage",
		["description"] = "Tank Cartrinity",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[51] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [224]
	[225] = 
	{
		["type"] = "Wellcarnsc",
		["name"] = "Well Car",
		["category"] = "Carriage",
		["description"] = "Well Car",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			[51] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [225]
	[226] = 
	{
		["type"] = "big_smoke",
		["name"] = "Big smoke",
		["category"] = "Effect",
		["description"] = "Big smoke",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [226]
	[227] = 
	{
		["type"] = ".Command Center",
		["name"] = "Command Center",
		["category"] = "Fortification",
		["description"] = "Command Center",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [227]
	[228] = 
	{
		["type"] = "345 Excavator",
		["name"] = "Excavator",
		["category"] = "Fortification",
		["description"] = "Excavator",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [228]
	[229] = 
	{
		["type"] = "af_hq",
		["name"] = "HQ Building",
		["category"] = "Fortification",
		["description"] = "HQ Building",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [229]
	[230] = 
	{
		["type"] = "Airshow_Cone",
		["name"] = "Airshow cone",
		["category"] = "Fortification",
		["description"] = "Airshow cone",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [230]
	[231] = 
	{
		["type"] = "Airshow_Crowd",
		["name"] = "Airshow Crowd",
		["category"] = "Fortification",
		["description"] = "Airshow Crowd",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [231]
	[232] = 
	{
		["type"] = "AM32a-60_01",
		["name"] = "M92 AM32a-60-01",
		["category"] = "Fortification",
		["description"] = "M92 AM32a-60-01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [232]
	[233] = 
	{
		["type"] = "AM32a-60_02",
		["name"] = "M92 AM32a-60-02",
		["category"] = "Fortification",
		["description"] = "M92 AM32a-60-02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [233]
	[234] = 
	{
		["type"] = "APFC fuel",
		["name"] = "M92 APFC fuel",
		["category"] = "Fortification",
		["description"] = "M92 APFC fuel",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [234]
	[235] = 
	{
		["type"] = "B600",
		["name"] = "M92 B600",
		["category"] = "Fortification",
		["description"] = "M92 B600",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [235]
	[236] = 
	{
		["type"] = "Barracks 2",
		["name"] = "Barracks 2",
		["category"] = "Fortification",
		["description"] = "Barracks 2",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [236]
	[237] = 
	{
		["type"] = "Barrier A",
		["name"] = "M92 Barrier A",
		["category"] = "Fortification",
		["description"] = "M92 Barrier A",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [237]
	[238] = 
	{
		["type"] = "Barrier B",
		["name"] = "M92 Barrier B",
		["category"] = "Fortification",
		["description"] = "M92 Barrier B",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [238]
	[239] = 
	{
		["type"] = "Barrier C",
		["name"] = "M92 Barrier C",
		["category"] = "Fortification",
		["description"] = "M92 Barrier C",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [239]
	[240] = 
	{
		["type"] = "Barrier D",
		["name"] = "M92 Barrier D",
		["category"] = "Fortification",
		["description"] = "M92 Barrier D",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [240]
	[241] = 
	{
		["type"] = "Beer Bomb",
		["name"] = "Barrel",
		["category"] = "Fortification",
		["description"] = "Barrel",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [241]
	[242] = 
	{
		["type"] = "Belgian gate",
		["name"] = "Belgian gate",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Belgian gate",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [242]
	[243] = 
	{
		["type"] = "billboard_motorized",
		["name"] = "Billboard Motorized",
		["category"] = "Fortification",
		["description"] = "Billboard Motorized",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [243]
	[244] = 
	{
		["type"] = "Black_Tyre",
		["name"] = "Mark Tyre Black",
		["category"] = "Fortification",
		["description"] = "Mark Tyre Black",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [244]
	[245] = 
	{
		["type"] = "Black_Tyre_RF",
		["name"] = "Mark Tyre with Red Flag",
		["category"] = "Fortification",
		["description"] = "Mark Tyre with Red Flag",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [245]
	[246] = 
	{
		["type"] = "Black_Tyre_WF",
		["name"] = "Mark Tyre with White Flag",
		["category"] = "Fortification",
		["description"] = "Mark Tyre with White Flag",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [246]
	[247] = 
	{
		["type"] = "Boiler-house A",
		["name"] = "Boiler-house A",
		["category"] = "Fortification",
		["description"] = "Boiler-house A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [247]
	[248] = 
	{
		["type"] = "BoomBarrier_closed",
		["name"] = "M92 Boom Barrier closed",
		["category"] = "Fortification",
		["description"] = "M92 Boom Barrier closed",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [248]
	[249] = 
	{
		["type"] = "BoomBarrier_open",
		["name"] = "M92 Boom Barrier open",
		["category"] = "Fortification",
		["description"] = "M92 Boom Barrier open",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [249]
	[250] = 
	{
		["type"] = "Building01_PBR",
		["name"] = "M92 Building01 PBR",
		["category"] = "Fortification",
		["description"] = "M92 Building01 PBR",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [250]
	[251] = 
	{
		["type"] = "Building02_PBR",
		["name"] = "M92 Building02 PBR",
		["category"] = "Fortification",
		["description"] = "M92 Building02 PBR",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [251]
	[252] = 
	{
		["type"] = "Building03_PBR",
		["name"] = "M92 Building03 PBR",
		["category"] = "Fortification",
		["description"] = "M92 Building03 PBR",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [252]
	[253] = 
	{
		["type"] = "Building04_PBR",
		["name"] = "M92 Building04 PBR",
		["category"] = "Fortification",
		["description"] = "M92 Building04 PBR",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [253]
	[254] = 
	{
		["type"] = "Building05_PBR",
		["name"] = "M92 Building05 PBR",
		["category"] = "Fortification",
		["description"] = "M92 Building05 PBR",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [254]
	[255] = 
	{
		["type"] = "Building06_PBR",
		["name"] = "M92 Building06 PBR",
		["category"] = "Fortification",
		["description"] = "M92 Building06 PBR",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [255]
	[256] = 
	{
		["type"] = "Building07_PBR",
		["name"] = "M92 Building07 PBR",
		["category"] = "Fortification",
		["description"] = "M92 Building07 PBR",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [256]
	[257] = 
	{
		["type"] = "Building08_PBR",
		["name"] = "M92 Building08 PBR",
		["category"] = "Fortification",
		["description"] = "M92 Building08 PBR",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [257]
	[258] = 
	{
		["type"] = "Bunker",
		["name"] = "Bunker 2",
		["category"] = "Fortification",
		["description"] = "Bunker 2",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [258]
	[259] = 
	{
		["type"] = "Cafe",
		["name"] = "Cafe",
		["category"] = "Fortification",
		["description"] = "Cafe",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [259]
	[260] = 
	{
		["type"] = "Camouflage01",
		["name"] = "M92 Camouflage 01",
		["category"] = "Fortification",
		["description"] = "M92 Camouflage 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [260]
	[261] = 
	{
		["type"] = "Camouflage02",
		["name"] = "M92 Camouflage 02",
		["category"] = "Fortification",
		["description"] = "M92 Camouflage 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [261]
	[262] = 
	{
		["type"] = "Camouflage03",
		["name"] = "M92 Camouflage 03",
		["category"] = "Fortification",
		["description"] = "M92 Camouflage 03",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [262]
	[263] = 
	{
		["type"] = "Camouflage04",
		["name"] = "M92 Camouflage 04",
		["category"] = "Fortification",
		["description"] = "M92 Camouflage 04",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [263]
	[264] = 
	{
		["type"] = "Camouflage05",
		["name"] = "M92 Camouflage 05",
		["category"] = "Fortification",
		["description"] = "M92 Camouflage 05",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [264]
	[265] = 
	{
		["type"] = "Camouflage06",
		["name"] = "M92 Camouflage 06",
		["category"] = "Fortification",
		["description"] = "M92 Camouflage 06",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [265]
	[266] = 
	{
		["type"] = "Camouflage07",
		["name"] = "M92 Camouflage 07",
		["category"] = "Fortification",
		["description"] = "M92 Camouflage 07",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [266]
	[267] = 
	{
		["type"] = "Cargo01",
		["name"] = "M92 Cargo 01",
		["category"] = "Fortification",
		["description"] = "M92 Cargo 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [267]
	[268] = 
	{
		["type"] = "Cargo02",
		["name"] = "M92 Cargo 02",
		["category"] = "Fortification",
		["description"] = "M92 Cargo 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [268]
	[269] = 
	{
		["type"] = "Cargo03",
		["name"] = "M92 Cargo 03",
		["category"] = "Fortification",
		["description"] = "M92 Cargo 03",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [269]
	[270] = 
	{
		["type"] = "Cargo04",
		["name"] = "M92 Cargo 04",
		["category"] = "Fortification",
		["description"] = "M92 Cargo 04",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [270]
	[271] = 
	{
		["type"] = "Cargo05",
		["name"] = "M92 Cargo 05",
		["category"] = "Fortification",
		["description"] = "M92 Cargo 05",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [271]
	[272] = 
	{
		["type"] = "Cargo06",
		["name"] = "M92 Cargo 06",
		["category"] = "Fortification",
		["description"] = "M92 Cargo 06",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [272]
	[273] = 
	{
		["type"] = "Chemical tank A",
		["name"] = "Chemical tank A",
		["category"] = "Fortification",
		["description"] = "Chemical tank A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [273]
	[274] = 
	{
		["type"] = "Comms tower M",
		["name"] = "Comms tower M",
		["category"] = "Fortification",
		["description"] = "Comms tower M",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [274]
	[275] = 
	{
		["type"] = "Concertina wire",
		["name"] = "Concertina wire",
		["category"] = "Fortification",
		["description"] = "Concertina wire",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [275]
	[276] = 
	{
		["type"] = "Cone01",
		["name"] = "M92 Cone 01",
		["category"] = "Fortification",
		["description"] = "M92 Cone 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [276]
	[277] = 
	{
		["type"] = "Cone02",
		["name"] = "M92 Cone 02",
		["category"] = "Fortification",
		["description"] = "M92 Cone 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [277]
	[278] = 
	{
		["type"] = "Container brown",
		["name"] = "Container brown",
		["category"] = "Fortification",
		["description"] = "Container brown",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [278]
	[279] = 
	{
		["type"] = "Container red 1",
		["name"] = "Container red 1",
		["category"] = "Fortification",
		["description"] = "Container red 1",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [279]
	[280] = 
	{
		["type"] = "Container red 2",
		["name"] = "Container red 2",
		["category"] = "Fortification",
		["description"] = "Container red 2",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [280]
	[281] = 
	{
		["type"] = "Container red 3",
		["name"] = "Container red 3",
		["category"] = "Fortification",
		["description"] = "Container red 3",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [281]
	[282] = 
	{
		["type"] = "Container white",
		["name"] = "Container white",
		["category"] = "Fortification",
		["description"] = "Container white",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [282]
	[283] = 
	{
		["type"] = "Container_10ft",
		["name"] = "M92 Container 10ft",
		["category"] = "Fortification",
		["description"] = "M92 Container 10ft",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [283]
	[284] = 
	{
		["type"] = "Container_20ft",
		["name"] = "M92 Container 20ft",
		["category"] = "Fortification",
		["description"] = "M92 Container 20ft",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [284]
	[285] = 
	{
		["type"] = "container_20ft",
		["name"] = "Container 20ft",
		["category"] = "Fortification",
		["description"] = "Container 20ft",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [285]
	[286] = 
	{
		["type"] = "container_40ft",
		["name"] = "Container 40ft",
		["category"] = "Fortification",
		["description"] = "Container 40ft",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [286]
	[287] = 
	{
		["type"] = "Container_40ft",
		["name"] = "M92 Container 40ft",
		["category"] = "Fortification",
		["description"] = "M92 Container 40ft",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [287]
	[288] = 
	{
		["type"] = "Container_generator",
		["name"] = "M92 Container generator",
		["category"] = "Fortification",
		["description"] = "M92 Container generator",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [288]
	[289] = 
	{
		["type"] = "Container_office",
		["name"] = "M92 Container office",
		["category"] = "Fortification",
		["description"] = "M92 Container office",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [289]
	[290] = 
	{
		["type"] = "Container_watchtower",
		["name"] = "M92 Container watchtower",
		["category"] = "Fortification",
		["description"] = "M92 Container watchtower",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [290]
	[291] = 
	{
		["type"] = "Container_watchtower_lights",
		["name"] = "M92 Container watchtower lights",
		["category"] = "Fortification",
		["description"] = "M92 Container watchtower lights",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [291]
	[292] = 
	{
		["type"] = "Czech hedgehogs 1",
		["name"] = "Czech hedgehogs 1",
		["category"] = "Fortification",
		["description"] = "Czech hedgehogs 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [292]
	[293] = 
	{
		["type"] = "Czech hedgehogs 2",
		["name"] = "Czech hedgehogs 2",
		["category"] = "Fortification",
		["description"] = "Czech hedgehogs 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [293]
	[294] = 
	{
		["type"] = "Dragonteeth 1",
		["name"] = "Dragonteeth 1",
		["category"] = "Fortification",
		["description"] = "Dragonteeth 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [294]
	[295] = 
	{
		["type"] = "Dragonteeth 2",
		["name"] = "Dragonteeth 2",
		["category"] = "Fortification",
		["description"] = "Dragonteeth 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [295]
	[296] = 
	{
		["type"] = "Dragonteeth 3",
		["name"] = "Dragonteeth 3",
		["category"] = "Fortification",
		["description"] = "Dragonteeth 3",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [296]
	[297] = 
	{
		["type"] = "Dragonteeth 4",
		["name"] = "Dragonteeth 4",
		["category"] = "Fortification",
		["description"] = "Dragonteeth 4",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [297]
	[298] = 
	{
		["type"] = "Dragonteeth 5",
		["name"] = "Dragonteeth 5",
		["category"] = "Fortification",
		["description"] = "Dragonteeth 5",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [298]
	[299] = 
	{
		["type"] = "Electric power box",
		["name"] = "Electric power box",
		["category"] = "Fortification",
		["description"] = "Electric power box",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [299]
	[300] = 
	{
		["type"] = "ElevatedPlatform_down",
		["name"] = "M92 Elevated Platform down",
		["category"] = "Fortification",
		["description"] = "M92 Elevated Platform down",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [300]
	[301] = 
	{
		["type"] = "ElevatedPlatform_up",
		["name"] = "M92 Elevated Platform up",
		["category"] = "Fortification",
		["description"] = "M92 Elevated Platform up",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [301]
	[302] = 
	{
		["type"] = "Farm A",
		["name"] = "Farm A",
		["category"] = "Fortification",
		["description"] = "Farm A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [302]
	[303] = 
	{
		["type"] = "Farm B",
		["name"] = "Farm B",
		["category"] = "Fortification",
		["description"] = "Farm B",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [303]
	[304] = 
	{
		["type"] = "FARP Ammo Dump Coating",
		["name"] = "FARP Ammo Storage",
		["category"] = "Fortification",
		["description"] = "FARP Ammo Storage",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [304]
	[305] = 
	{
		["type"] = "FARP CP Blindage",
		["name"] = "FARP Command Post",
		["category"] = "Fortification",
		["description"] = "FARP Command Post",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [305]
	[306] = 
	{
		["type"] = "FARP Fuel Depot",
		["name"] = "FARP Fuel Depot",
		["category"] = "Fortification",
		["description"] = "FARP Fuel Depot",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [306]
	[307] = 
	{
		["type"] = "FARP Tent",
		["name"] = "FARP Tent",
		["category"] = "Fortification",
		["description"] = "FARP Tent",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [307]
	[308] = 
	{
		["type"] = "FarpHide_Dmed",
		["name"] = "FARP Hide Double Med",
		["category"] = "Fortification",
		["description"] = "FARP Hide Double Med",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [308]
	[309] = 
	{
		["type"] = "FarpHide_Dsmall",
		["name"] = "FARP Hide Double Small",
		["category"] = "Fortification",
		["description"] = "FARP Hide Double Small",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [309]
	[310] = 
	{
		["type"] = "FarpHide_Med",
		["name"] = "FARP Hide Single Med",
		["category"] = "Fortification",
		["description"] = "FARP Hide Single Med",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [310]
	[311] = 
	{
		["type"] = "FarpHide_small",
		["name"] = "FARP Hide Single Small",
		["category"] = "Fortification",
		["description"] = "FARP Hide Single Small",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [311]
	[312] = 
	{
		["type"] = "Fire Control Bunker",
		["name"] = "Fire control bunker",
		["category"] = "Fortification",
		["description"] = "Fire control bunker",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [312]
	[313] = 
	{
		["type"] = "fire_control",
		["name"] = "Bunker with Fire Control Center",
		["category"] = "Fortification",
		["description"] = "Bunker with Fire Control Center",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [313]
	[314] = 
	{
		["type"] = "FireExtinguisher01",
		["name"] = "M92 Fire Extinguisher 01",
		["category"] = "Fortification",
		["description"] = "M92 Fire Extinguisher 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [314]
	[315] = 
	{
		["type"] = "FireExtinguisher02",
		["name"] = "M92 Fire Extinguisher 02",
		["category"] = "Fortification",
		["description"] = "M92 Fire Extinguisher 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [315]
	[316] = 
	{
		["type"] = "FireExtinguisher03",
		["name"] = "M92 Fire Extinguisher 03",
		["category"] = "Fortification",
		["description"] = "M92 Fire Extinguisher 03",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [316]
	[317] = 
	{
		["type"] = "FlagPole",
		["name"] = "Flag Pole",
		["category"] = "Fortification",
		["description"] = "Flag Pole",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [317]
	[318] = 
	{
		["type"] = "Freya_Shelter_Brick",
		["name"] = "Freya Shelter Brick",
		["category"] = "Fortification",
		["description"] = "Freya Shelter Brick",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [318]
	[319] = 
	{
		["type"] = "Freya_Shelter_Concrete",
		["name"] = "Freya Shelter Concrete",
		["category"] = "Fortification",
		["description"] = "Freya Shelter Concrete",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [319]
	[320] = 
	{
		["type"] = "Fuel tank",
		["name"] = "Fuel tank",
		["category"] = "Fortification",
		["description"] = "Fuel tank",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [320]
	[321] = 
	{
		["type"] = "Garage A",
		["name"] = "Garage A",
		["category"] = "Fortification",
		["description"] = "Garage A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [321]
	[322] = 
	{
		["type"] = "Garage B",
		["name"] = "Garage B",
		["category"] = "Fortification",
		["description"] = "Garage B",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [322]
	[323] = 
	{
		["type"] = "Garage small A",
		["name"] = "Garage small A",
		["category"] = "Fortification",
		["description"] = "Garage small A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [323]
	[324] = 
	{
		["type"] = "Garage small B",
		["name"] = "Garage small B",
		["category"] = "Fortification",
		["description"] = "Garage small B",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [324]
	[325] = 
	{
		["type"] = "GeneratorF",
		["name"] = "GeneratorF",
		["category"] = "Fortification",
		["description"] = "GeneratorF",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [325]
	[326] = 
	{
		["type"] = "Hangar A",
		["name"] = "Hangar A",
		["category"] = "Fortification",
		["description"] = "Hangar A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [326]
	[327] = 
	{
		["type"] = "Hangar B",
		["name"] = "Hangar B",
		["category"] = "Fortification",
		["description"] = "Hangar B",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [327]
	[328] = 
	{
		["type"] = "Haystack 1",
		["name"] = "Haystack 1",
		["category"] = "Fortification",
		["description"] = "Haystack 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [328]
	[329] = 
	{
		["type"] = "Haystack 2",
		["name"] = "Haystack 2",
		["category"] = "Fortification",
		["description"] = "Haystack 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [329]
	[330] = 
	{
		["type"] = "Haystack 3",
		["name"] = "Haystack 3",
		["category"] = "Fortification",
		["description"] = "Haystack 3",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [330]
	[331] = 
	{
		["type"] = "Haystack 4",
		["name"] = "Haystack 4",
		["category"] = "Fortification",
		["description"] = "Haystack 4",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [331]
	[332] = 
	{
		["type"] = "Hemmkurvenhindernis",
		["name"] = "Hemmkurvenhindernis",
		["category"] = "Fortification",
		["description"] = "Hemmkurvenhindernis",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [332]
	[333] = 
	{
		["type"] = "HESCO_generator",
		["name"] = "M92 HESCO generator",
		["category"] = "Fortification",
		["description"] = "M92 HESCO generator",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [333]
	[334] = 
	{
		["type"] = "HESCO_post_1",
		["name"] = "M92 HESCO post 1",
		["category"] = "Fortification",
		["description"] = "M92 HESCO post 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [334]
	[335] = 
	{
		["type"] = "HESCO_wallperimeter_1",
		["name"] = "M92 HESCO wallperimeter 1",
		["category"] = "Fortification",
		["description"] = "M92 HESCO wallperimeter 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [335]
	[336] = 
	{
		["type"] = "HESCO_wallperimeter_2",
		["name"] = "M92 HESCO wallperimeter 2",
		["category"] = "Fortification",
		["description"] = "M92 HESCO wallperimeter 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [336]
	[337] = 
	{
		["type"] = "HESCO_wallperimeter_3",
		["name"] = "M92 HESCO wallperimeter 3",
		["category"] = "Fortification",
		["description"] = "M92 HESCO wallperimeter 3",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [337]
	[338] = 
	{
		["type"] = "HESCO_wallperimeter_4",
		["name"] = "M92 HESCO wallperimeter 4",
		["category"] = "Fortification",
		["description"] = "M92 HESCO wallperimeter 4",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [338]
	[339] = 
	{
		["type"] = "HESCO_wallperimeter_5",
		["name"] = "M92 HESCO wallperimeter 5",
		["category"] = "Fortification",
		["description"] = "M92 HESCO wallperimeter 5",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [339]
	[340] = 
	{
		["type"] = "HESCO_watchtower_1",
		["name"] = "M92 HESCO watchtower 1",
		["category"] = "Fortification",
		["description"] = "M92 HESCO watchtower 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [340]
	[341] = 
	{
		["type"] = "HESCO_watchtower_2",
		["name"] = "M92 HESCO watchtower 2",
		["category"] = "Fortification",
		["description"] = "M92 HESCO watchtower 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [341]
	[342] = 
	{
		["type"] = "HESCO_watchtower_3",
		["name"] = "M92 HESCO watchtower 3",
		["category"] = "Fortification",
		["description"] = "M92 HESCO watchtower 3",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [342]
	[343] = 
	{
		["type"] = "house1arm",
		["name"] = "Barracks armed",
		["category"] = "Fortification",
		["description"] = "Barracks armed",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [343]
	[344] = 
	{
		["type"] = "house2arm",
		["name"] = "Watch tower armed",
		["category"] = "Fortification",
		["description"] = "Watch tower armed",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [344]
	[345] = 
	{
		["type"] = "houseA_arm",
		["name"] = "Building armed",
		["category"] = "Fortification",
		["description"] = "Building armed",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [345]
	[346] = 
	{
		["type"] = "ip_tower",
		["name"] = "IP Tower",
		["category"] = "Fortification",
		["description"] = "IP Tower",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [346]
	[347] = 
	{
		["type"] = "Jerrycan",
		["name"] = "M92 Jerrycan",
		["category"] = "Fortification",
		["description"] = "M92 Jerrycan",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [347]
	[348] = 
	{
		["type"] = "Ladder",
		["name"] = "M92 Ladder",
		["category"] = "Fortification",
		["description"] = "M92 Ladder",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [348]
	[349] = 
	{
		["type"] = "Landmine",
		["name"] = "Landmine",
		["category"] = "Fortification",
		["description"] = "Landmine",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [349]
	[350] = 
	{
		["type"] = "LHD_LHA",
		["name"] = "M92 LHD LHA",
		["category"] = "Fortification",
		["description"] = "M92 LHD LHA",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [350]
	[351] = 
	{
		["type"] = "Log posts 1",
		["name"] = "Log posts 1",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Log posts 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [351]
	[352] = 
	{
		["type"] = "Log posts 2",
		["name"] = "Log posts 2",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Log posts 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [352]
	[353] = 
	{
		["type"] = "Log posts 3",
		["name"] = "Log posts 3",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Log posts 3",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [353]
	[354] = 
	{
		["type"] = "Log ramps 1",
		["name"] = "Log ramps 1",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Log ramps 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [354]
	[355] = 
	{
		["type"] = "Log ramps 2",
		["name"] = "Log ramps 2",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Log ramps 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [355]
	[356] = 
	{
		["type"] = "Log ramps 3",
		["name"] = "Log ramps 3",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Log ramps 3",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [356]
	[357] = 
	{
		["type"] = "M32-10C_01",
		["name"] = "M92 M32-10C 01",
		["category"] = "Fortification",
		["description"] = "M92 M32-10C 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [357]
	[358] = 
	{
		["type"] = "M32-10C_02",
		["name"] = "M92 M32-10C 02",
		["category"] = "Fortification",
		["description"] = "M92 M32-10C 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [358]
	[359] = 
	{
		["type"] = "M32-10C_03",
		["name"] = "M92 M32-10C 03",
		["category"] = "Fortification",
		["description"] = "M92 M32-10C 03",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [359]
	[360] = 
	{
		["type"] = "M32-10C_04",
		["name"] = "M92 M32-10C 04",
		["category"] = "Fortification",
		["description"] = "M92 M32-10C 04",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [360]
	[361] = 
	{
		["type"] = "Military staff",
		["name"] = "Military staff",
		["category"] = "Fortification",
		["description"] = "Military staff",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [361]
	[362] = 
	{
		["type"] = "MJ-1_01",
		["name"] = "M92 MJ-1 01",
		["category"] = "Fortification",
		["description"] = "M92 MJ-1 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [362]
	[363] = 
	{
		["type"] = "MJ-1_02",
		["name"] = "M92 MJ-1 02",
		["category"] = "Fortification",
		["description"] = "M92 MJ-1 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [363]
	[364] = 
	{
		["type"] = "NF-2_LightOff01",
		["name"] = "M92 NF-2 LightOff 01",
		["category"] = "Fortification",
		["description"] = "M92 NF-2 LightOff 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [364]
	[365] = 
	{
		["type"] = "NF-2_LightOff02",
		["name"] = "M92 NF-2 LightOff 02",
		["category"] = "Fortification",
		["description"] = "M92 NF-2 LightOff 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [365]
	[366] = 
	{
		["type"] = "NF-2_LightOn",
		["name"] = "M92 NF-2 LightOn",
		["category"] = "Fortification",
		["description"] = "M92 NF-2 LightOn",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [366]
	[367] = 
	{
		["type"] = "Nodding_Donkey_Pump",
		["name"] = "Nodding Donkey Pump",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Nodding Donkey Pump",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [367]
	[368] = 
	{
		["type"] = "offshore WindTurbine",
		["name"] = "Offshore Wind Turbine",
		["category"] = "Fortification",
		["description"] = "Offshore Wind Turbine",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [368]
	[369] = 
	{
		["type"] = "offshore WindTurbine2",
		["name"] = "Offshore Wind Turbine 2",
		["category"] = "Fortification",
		["description"] = "Offshore Wind Turbine 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [369]
	[370] = 
	{
		["type"] = "Oil Barrel",
		["name"] = "M92 Oil barrel",
		["category"] = "Fortification",
		["description"] = "M92 Oil barrel",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [370]
	[371] = 
	{
		["type"] = "Oil derrick",
		["name"] = "Oil derrick",
		["category"] = "Fortification",
		["description"] = "Oil derrick",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [371]
	[372] = 
	{
		["type"] = "Oil platform",
		["name"] = "Oil platform",
		["category"] = "Fortification",
		["description"] = "Oil platform",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [372]
	[373] = 
	{
		["type"] = "Orca",
		["name"] = "Orca Whale",
		["category"] = "Fortification",
		["description"] = "Orca Whale",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [373]
	[374] = 
	{
		["type"] = "outpost",
		["name"] = "Outpost",
		["category"] = "Fortification",
		["description"] = "Outpost",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [374]
	[375] = 
	{
		["type"] = "outpost_road",
		["name"] = "Road outpost",
		["category"] = "Fortification",
		["description"] = "Road outpost",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [375]
	[376] = 
	{
		["type"] = "outpost_road_l",
		["name"] = "Road outpost_L",
		["category"] = "Fortification",
		["description"] = "Road outpost_L",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [376]
	[377] = 
	{
		["type"] = "outpost_road_r",
		["name"] = "Road outpost-R",
		["category"] = "Fortification",
		["description"] = "Road outpost-R",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [377]
	[378] = 
	{
		["type"] = "P20_01",
		["name"] = "M92 P20 01",
		["category"] = "Fortification",
		["description"] = "M92 P20 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [378]
	[379] = 
	{
		["type"] = "Pile of Woods",
		["name"] = "M92 Pile of woods",
		["category"] = "Fortification",
		["description"] = "M92 Pile of woods",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [379]
	[380] = 
	{
		["type"] = "Pump station",
		["name"] = "Pump station",
		["category"] = "Fortification",
		["description"] = "Pump station",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [380]
	[381] = 
	{
		["type"] = "r11_volvo",
		["name"] = "M92 R11 Volvo",
		["category"] = "Fortification",
		["description"] = "M92 R11 Volvo",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [381]
	[382] = 
	{
		["type"] = "Railway crossing A",
		["name"] = "Railway crossing A",
		["category"] = "Fortification",
		["description"] = "Railway crossing A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [382]
	[383] = 
	{
		["type"] = "Railway crossing B",
		["name"] = "Railway crossing B",
		["category"] = "Fortification",
		["description"] = "Railway crossing B",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [383]
	[384] = 
	{
		["type"] = "Railway station",
		["name"] = "Railway station",
		["category"] = "Fortification",
		["description"] = "Railway station",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [384]
	[385] = 
	{
		["type"] = "Red_Flag",
		["name"] = "Mark Flag Red",
		["category"] = "Fortification",
		["description"] = "Mark Flag Red",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [385]
	[386] = 
	{
		["type"] = "Repair workshop",
		["name"] = "Repair workshop",
		["category"] = "Fortification",
		["description"] = "Repair workshop",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [386]
	[387] = 
	{
		["type"] = "Restaurant 1",
		["name"] = "Restaurant 1",
		["category"] = "Fortification",
		["description"] = "Restaurant 1",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [387]
	[388] = 
	{
		["type"] = "Revetment_x4",
		["name"] = "M92 Revetment x4",
		["category"] = "Fortification",
		["description"] = "M92 Revetment x4",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [388]
	[389] = 
	{
		["type"] = "Revetment_x8",
		["name"] = "M92 Revetment x8",
		["category"] = "Fortification",
		["description"] = "M92 Revetment x8",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [389]
	[390] = 
	{
		["type"] = "Sandbag_01",
		["name"] = "M92 Sandbag 01",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [390]
	[391] = 
	{
		["type"] = "Sandbag_02",
		["name"] = "M92 Sandbag 02",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [391]
	[392] = 
	{
		["type"] = "Sandbag_03",
		["name"] = "M92 Sandbag 03",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 03",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [392]
	[393] = 
	{
		["type"] = "Sandbag_04",
		["name"] = "M92 Sandbag 04",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 04",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [393]
	[394] = 
	{
		["type"] = "Sandbag_05",
		["name"] = "M92 Sandbag 05",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 05",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [394]
	[395] = 
	{
		["type"] = "Sandbag_06",
		["name"] = "M92 Sandbag 06",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 06",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [395]
	[396] = 
	{
		["type"] = "Sandbag_07",
		["name"] = "M92 Sandbag 07",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 07",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [396]
	[397] = 
	{
		["type"] = "Sandbag_08",
		["name"] = "M92 Sandbag 08",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 08",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [397]
	[398] = 
	{
		["type"] = "Sandbag_09",
		["name"] = "M92 Sandbag 09",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 09",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [398]
	[399] = 
	{
		["type"] = "Sandbag_10",
		["name"] = "M92 Sandbag 10",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 10",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [399]
	[400] = 
	{
		["type"] = "Sandbag_11",
		["name"] = "M92 Sandbag 11",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 11",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [400]
	[401] = 
	{
		["type"] = "Sandbag_12",
		["name"] = "M92 Sandbag 12",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 12",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [401]
	[402] = 
	{
		["type"] = "Sandbag_13",
		["name"] = "M92 Sandbag 13",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 13",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [402]
	[403] = 
	{
		["type"] = "Sandbag_15",
		["name"] = "M92 Sandbag 15",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 15",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [403]
	[404] = 
	{
		["type"] = "Sandbag_16",
		["name"] = "M92 Sandbag 16",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 16",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [404]
	[405] = 
	{
		["type"] = "Sandbag_17",
		["name"] = "M92 Sandbag 17",
		["category"] = "Fortification",
		["description"] = "M92 Sandbag 17",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [405]
	[406] = 
	{
		["type"] = "Sandbox",
		["name"] = "Bunker 1",
		["category"] = "Fortification",
		["description"] = "Bunker 1",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [406]
	[407] = 
	{
		["type"] = "Shelter",
		["name"] = "Shelter",
		["category"] = "Fortification",
		["description"] = "Shelter",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [407]
	[408] = 
	{
		["type"] = "Shelter B",
		["name"] = "Shelter B",
		["category"] = "Fortification",
		["description"] = "Shelter B",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [408]
	[409] = 
	{
		["type"] = "Shelter01",
		["name"] = "M92 Shelter 01",
		["category"] = "Fortification",
		["description"] = "M92 Shelter 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [409]
	[410] = 
	{
		["type"] = "Shelter02",
		["name"] = "M92 Shelter 02",
		["category"] = "Fortification",
		["description"] = "M92 Shelter 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [410]
	[411] = 
	{
		["type"] = "Shop",
		["name"] = "Shop",
		["category"] = "Fortification",
		["description"] = "Shop",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [411]
	[412] = 
	{
		["type"] = "Siegfried Line",
		["name"] = "Siegfried line",
		["category"] = "Fortification",
		["description"] = "Siegfried line",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [412]
	[413] = 
	{
		["type"] = "SK_C_28_naval_gun",
		["name"] = "Gun 15cm SK C/28 Naval in Bunker",
		["category"] = "Fortification",
		["description"] = "Gun 15cm SK C/28 Naval in Bunker",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armed vehicles"] = true,
			["LightArmoredUnits"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["AntiAir Armed Vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			[26] = true,
			["Indirect fire"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [413]
	[414] = 
	{
		["type"] = "Small house 1A",
		["name"] = "Small house 1A",
		["category"] = "Fortification",
		["description"] = "Small house 1A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [414]
	[415] = 
	{
		["type"] = "Small house 1A area",
		["name"] = "Small house 1A area",
		["category"] = "Fortification",
		["description"] = "Small house 1A area",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [415]
	[416] = 
	{
		["type"] = "Small house 1B",
		["name"] = "Small house 1B",
		["category"] = "Fortification",
		["description"] = "Small house 1B",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [416]
	[417] = 
	{
		["type"] = "Small house 1B area",
		["name"] = "Small house 1B area",
		["category"] = "Fortification",
		["description"] = "Small house 1B area",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [417]
	[418] = 
	{
		["type"] = "Small house 1C area",
		["name"] = "Small house 1C area",
		["category"] = "Fortification",
		["description"] = "Small house 1C area",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [418]
	[419] = 
	{
		["type"] = "Small house 2C",
		["name"] = "Small house 2C",
		["category"] = "Fortification",
		["description"] = "Small house 2C",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [419]
	[420] = 
	{
		["type"] = "Small werehouse 1",
		["name"] = "Small warehouse 1",
		["category"] = "Fortification",
		["description"] = "Small warehouse 1",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [420]
	[421] = 
	{
		["type"] = "Small werehouse 2",
		["name"] = "Small warehouse 2",
		["category"] = "Fortification",
		["description"] = "Small warehouse 2",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [421]
	[422] = 
	{
		["type"] = "Small werehouse 3",
		["name"] = "Small warehouse 3",
		["category"] = "Fortification",
		["description"] = "Small warehouse 3",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [422]
	[423] = 
	{
		["type"] = "Small werehouse 4",
		["name"] = "Small warehouse 4",
		["category"] = "Fortification",
		["description"] = "Small warehouse 4",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [423]
	[424] = 
	{
		["type"] = "Small_LightHouse",
		["name"] = "Small_LightHouse",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Small_LightHouse",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [424]
	[425] = 
	{
		["type"] = "Stanley_LightHouse",
		["name"] = "Stanley LightHouse",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "Stanley LightHouse",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [425]
	[426] = 
	{
		["type"] = "Subsidiary structure 1",
		["name"] = "Subsidiary structure 1",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure 1",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [426]
	[427] = 
	{
		["type"] = "Subsidiary structure 2",
		["name"] = "Subsidiary structure 2",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure 2",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [427]
	[428] = 
	{
		["type"] = "Subsidiary structure 3",
		["name"] = "Subsidiary structure 3",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure 3",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [428]
	[429] = 
	{
		["type"] = "Subsidiary structure A",
		["name"] = "Subsidiary structure A",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [429]
	[430] = 
	{
		["type"] = "Subsidiary structure B",
		["name"] = "Subsidiary structure B",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure B",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [430]
	[431] = 
	{
		["type"] = "Subsidiary structure C",
		["name"] = "Subsidiary structure C",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure C",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [431]
	[432] = 
	{
		["type"] = "Subsidiary structure D",
		["name"] = "Subsidiary structure D",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure D",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [432]
	[433] = 
	{
		["type"] = "Subsidiary structure E",
		["name"] = "Subsidiary structure E",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure E",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [433]
	[434] = 
	{
		["type"] = "Subsidiary structure F",
		["name"] = "Subsidiary structure F",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure F",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [434]
	[435] = 
	{
		["type"] = "Subsidiary structure G",
		["name"] = "Subsidiary structure G",
		["category"] = "Fortification",
		["description"] = "Subsidiary structure G",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [435]
	[436] = 
	{
		["type"] = "Supermarket A",
		["name"] = "Supermarket A",
		["category"] = "Fortification",
		["description"] = "Supermarket A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [436]
	[437] = 
	{
		["type"] = "TACAN_beacon",
		["name"] = "Beacon TACAN Portable TTS 3030",
		["category"] = "Fortification",
		["description"] = "Beacon TACAN Portable TTS 3030",
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			[26] = true,
			[2] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["HeavyArmoredUnits"] = true,
			["Fortifications"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[96] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [437]
	[438] = 
	{
		["type"] = "Tech combine",
		["name"] = "Tech combine",
		["category"] = "Fortification",
		["description"] = "Tech combine",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [438]
	[439] = 
	{
		["type"] = "Tech hangar A",
		["name"] = "Tech hangar A",
		["category"] = "Fortification",
		["description"] = "Tech hangar A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [439]
	[440] = 
	{
		["type"] = "Tent01",
		["name"] = "M92 Tent 01",
		["category"] = "Fortification",
		["description"] = "M92 Tent 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [440]
	[441] = 
	{
		["type"] = "Tent02",
		["name"] = "M92 Tent 02",
		["category"] = "Fortification",
		["description"] = "M92 Tent 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [441]
	[442] = 
	{
		["type"] = "Tent03",
		["name"] = "M92 Tent 03",
		["category"] = "Fortification",
		["description"] = "M92 Tent 03",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [442]
	[443] = 
	{
		["type"] = "Tent04",
		["name"] = "M92 Tent 04",
		["category"] = "Fortification",
		["description"] = "M92 Tent 04",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [443]
	[444] = 
	{
		["type"] = "Tent05",
		["name"] = "M92 Tent 05",
		["category"] = "Fortification",
		["description"] = "M92 Tent 05",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [444]
	[445] = 
	{
		["type"] = "Tetrahydra",
		["name"] = "Tetrahydra",
		["category"] = "Fortification",
		["description"] = "Tetrahydra",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [445]
	[446] = 
	{
		["type"] = "Toolbox01",
		["name"] = "M92 Toolbox 01",
		["category"] = "Fortification",
		["description"] = "M92 Toolbox 01",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [446]
	[447] = 
	{
		["type"] = "Toolbox02",
		["name"] = "M92 Toolbox 02",
		["category"] = "Fortification",
		["description"] = "M92 Toolbox 02",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [447]
	[448] = 
	{
		["type"] = "Tower Crane",
		["name"] = "TowerCrane",
		["category"] = "Fortification",
		["isPutToWater"] = true,
		["description"] = "TowerCrane",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [448]
	[449] = 
	{
		["type"] = "TugHarlan",
		["name"] = "M92 Tug Harlan",
		["category"] = "Fortification",
		["description"] = "M92 Tug Harlan",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [449]
	[450] = 
	{
		["type"] = "TV tower",
		["name"] = "TV tower",
		["category"] = "Fortification",
		["description"] = "TV tower",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [450]
	[451] = 
	{
		["type"] = "Twall_x1",
		["name"] = "M92 Twall x1",
		["category"] = "Fortification",
		["description"] = "M92 Twall x1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [451]
	[452] = 
	{
		["type"] = "Twall_x6",
		["name"] = "M92 Twall x6",
		["category"] = "Fortification",
		["description"] = "M92 Twall x6",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [452]
	[453] = 
	{
		["type"] = "Twall_x6_3mts",
		["name"] = "M92 Twall x6 3mts",
		["category"] = "Fortification",
		["description"] = "M92 Twall x6 3mts",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [453]
	[454] = 
	{
		["type"] = "warning_board_a",
		["name"] = "Warning Board: Spy Cannot Escape!",
		["category"] = "Fortification",
		["description"] = "Warning Board: Spy Cannot Escape!",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [454]
	[455] = 
	{
		["type"] = "warning_board_b",
		["name"] = "Warning Board: Catch Spy!",
		["category"] = "Fortification",
		["description"] = "Warning Board: Catch Spy!",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [455]
	[456] = 
	{
		["type"] = "Water tower A",
		["name"] = "Water tower A",
		["category"] = "Fortification",
		["description"] = "Water tower A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [456]
	[457] = 
	{
		["type"] = "WC",
		["name"] = "WC",
		["category"] = "Fortification",
		["description"] = "WC",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [457]
	[458] = 
	{
		["type"] = "White_Flag",
		["name"] = "Mark Flag White",
		["category"] = "Fortification",
		["description"] = "Mark Flag White",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [458]
	[459] = 
	{
		["type"] = "White_Tyre",
		["name"] = "Mark Tyre White",
		["category"] = "Fortification",
		["description"] = "Mark Tyre White",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [459]
	[460] = 
	{
		["type"] = "Windsock",
		["name"] = "Windsock",
		["category"] = "Fortification",
		["description"] = "Windsock",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [460]
	[461] = 
	{
		["type"] = "WindTurbine",
		["name"] = "Wind Turbine",
		["category"] = "Fortification",
		["description"] = "Wind Turbine",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [461]
	[462] = 
	{
		["type"] = "WindTurbine_11",
		["name"] = "Wind Turbine 2",
		["category"] = "Fortification",
		["description"] = "Wind Turbine 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [462]
	[463] = 
	{
		["type"] = "Workshop A",
		["name"] = "Workshop A",
		["category"] = "Fortification",
		["description"] = "Workshop A",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [463]
	[464] = 
	{
		["type"] = "GrassAirfield",
		["name"] = "Grass Airfield",
		["category"] = "GrassAirfield",
		["description"] = "Grass Airfield",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [464]
	[465] = 
	{
		["type"] = "Bridge",
		["name"] = "Bridge",
		["category"] = "GroundObject",
		["description"] = "Bridge",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [465]
	[466] = 
	{
		["type"] = "Building",
		["name"] = "Building",
		["category"] = "GroundObject",
		["description"] = "Building",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [466]
	[467] = 
	{
		["type"] = "Train",
		["name"] = "Train",
		["category"] = "GroundObject",
		["description"] = "Train",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [467]
	[468] = 
	{
		["type"] = "Transport",
		["name"] = "Transport",
		["category"] = "GroundObject",
		["description"] = "Transport",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [468]
	[469] = 
	{
		["air"] = true,
		["type"] = "AH-1W",
		["name"] = "AH-1W",
		["category"] = "Helicopter",
		["description"] = "AH-1W",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			[163] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [469]
	[470] = 
	{
		["air"] = true,
		["type"] = "AH-64A",
		["name"] = "AH-64A",
		["category"] = "Helicopter",
		["description"] = "AH-64A",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			["Helicopters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[157] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [470]
	[471] = 
	{
		["air"] = true,
		["type"] = "AH-64D",
		["name"] = "AH-64D",
		["category"] = "Helicopter",
		["description"] = "AH-64D",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			[158] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [471]
	[472] = 
	{
		["air"] = true,
		["type"] = "AH-64D_BLK_II",
		["name"] = "AH-64D BLK.II",
		["category"] = "Helicopter",
		["description"] = "AH-64D BLK.II",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["AFAPD"] = true,
			["Refuelable"] = true,
			["Helicopters"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			[300] = true,
			[6] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [472]
	[473] = 
	{
		["air"] = true,
		["type"] = "CH-47D",
		["name"] = "CH-47D",
		["category"] = "Helicopter",
		["description"] = "CH-47D",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			[159] = true,
			["NonAndLightArmoredUnits"] = true,
			[25] = true,
			["NonArmoredUnits"] = true,
			["Transport helicopters"] = true,
			["Helicopters"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [473]
	[474] = 
	{
		["air"] = true,
		["type"] = "CH-47Fbl1",
		["name"] = "CH-47F",
		["category"] = "Helicopter",
		["description"] = "CH-47F",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["Helicopters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			[329] = true,
			["All"] = true,
			[5] = true,
			["Transport helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [474]
	[475] = 
	{
		["air"] = true,
		["type"] = "CH-53E",
		["name"] = "CH-53E",
		["category"] = "Helicopter",
		["description"] = "CH-53E",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["All"] = true,
			["NonAndLightArmoredUnits"] = true,
			[25] = true,
			["NonArmoredUnits"] = true,
			["Transport helicopters"] = true,
			["Helicopters"] = true,
			[160] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [475]
	[476] = 
	{
		["air"] = true,
		["type"] = "Ka-27",
		["name"] = "Ka-27",
		["category"] = "Helicopter",
		["description"] = "Ka-27",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[154] = true,
			["Air"] = true,
			["NonAndLightArmoredUnits"] = true,
			[25] = true,
			["NonArmoredUnits"] = true,
			["Transport helicopters"] = true,
			["Helicopters"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [476]
	[477] = 
	{
		["air"] = true,
		["type"] = "Ka-50",
		["name"] = "Ka-50",
		["category"] = "Helicopter",
		["description"] = "Ka-50",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			["NonAndLightArmoredUnits"] = true,
			[155] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [477]
	[478] = 
	{
		["air"] = true,
		["type"] = "Ka-50_3",
		["name"] = "Ka-50 III",
		["category"] = "Helicopter",
		["description"] = "Ka-50 III",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			[326] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [478]
	[479] = 
	{
		["air"] = true,
		["type"] = "Mi-24P",
		["name"] = "Mi-24P",
		["category"] = "Helicopter",
		["description"] = "Mi-24P",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			[296] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Helicopters"] = true,
			["Transports"] = true,
			["All"] = true,
			[6] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [479]
	[480] = 
	{
		["air"] = true,
		["type"] = "Mi-24V",
		["name"] = "Mi-24V",
		["category"] = "Helicopter",
		["description"] = "Mi-24V",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			["Air"] = true,
			["NonAndLightArmoredUnits"] = true,
			[152] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [480]
	[481] = 
	{
		["air"] = true,
		["type"] = "Mi-26",
		["name"] = "Mi-26",
		["category"] = "Helicopter",
		["description"] = "Mi-26",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[153] = true,
			[25] = true,
			["NonArmoredUnits"] = true,
			["Transport helicopters"] = true,
			["Helicopters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Transports"] = true,
			["All"] = true,
			["Planes"] = true,
			["Air"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [481]
	[482] = 
	{
		["air"] = true,
		["type"] = "Mi-28N",
		["name"] = "Mi-28N",
		["category"] = "Helicopter",
		["description"] = "Mi-28N",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			["All"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			[167] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [482]
	[483] = 
	{
		["air"] = true,
		["type"] = "Mi-8MT",
		["name"] = "Mi-8MTV2",
		["category"] = "Helicopter",
		["description"] = "Mi-8MTV2",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Helicopters"] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[151] = true,
			["Transports"] = true,
			["All"] = true,
			[6] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [483]
	[484] = 
	{
		["air"] = true,
		["type"] = "OH-58D",
		["name"] = "OH-58D",
		["category"] = "Helicopter",
		["description"] = "OH-58D",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			[168] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [484]
	[485] = 
	{
		["air"] = true,
		["type"] = "OH58D",
		["name"] = "OH-58D(R)",
		["category"] = "Helicopter",
		["description"] = "OH-58D(R)",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			[336] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [485]
	[486] = 
	{
		["air"] = true,
		["type"] = "SA342L",
		["name"] = "SA342L",
		["category"] = "Helicopter",
		["description"] = "SA342L",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			[290] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [486]
	[487] = 
	{
		["air"] = true,
		["type"] = "SA342M",
		["name"] = "SA342M",
		["category"] = "Helicopter",
		["description"] = "SA342M",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			["Helicopters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[289] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [487]
	[488] = 
	{
		["air"] = true,
		["type"] = "SA342Minigun",
		["name"] = "SA342Minigun",
		["category"] = "Helicopter",
		["description"] = "SA342Minigun",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			["NonAndLightArmoredUnits"] = true,
			[292] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [488]
	[489] = 
	{
		["air"] = true,
		["type"] = "SA342Mistral",
		["name"] = "SA342Mistral",
		["category"] = "Helicopter",
		["description"] = "SA342Mistral",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			[291] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [489]
	[490] = 
	{
		["air"] = true,
		["type"] = "SH-3W",
		["name"] = "SH-3W",
		["category"] = "Helicopter",
		["description"] = "SH-3W",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["Helicopters"] = true,
			["NonAndLightArmoredUnits"] = true,
			[25] = true,
			["NonArmoredUnits"] = true,
			["Transport helicopters"] = true,
			[164] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [490]
	[491] = 
	{
		["air"] = true,
		["type"] = "SH-60B",
		["name"] = "SH-60B",
		["category"] = "Helicopter",
		["description"] = "SH-60B",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			[6] = true,
			[161] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Helicopters"] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [491]
	[492] = 
	{
		["air"] = true,
		["type"] = "UH-1H",
		["name"] = "UH-1H",
		["category"] = "Helicopter",
		["description"] = "UH-1H",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Helicopters"] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[166] = true,
			["Transports"] = true,
			["All"] = true,
			[6] = true,
			["Attack helicopters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [492]
	[493] = 
	{
		["air"] = true,
		["type"] = "UH-60A",
		["name"] = "UH-60A",
		["category"] = "Helicopter",
		["description"] = "UH-60A",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["NonAndLightArmoredUnits"] = true,
			[162] = true,
			[25] = true,
			["NonArmoredUnits"] = true,
			["Transport helicopters"] = true,
			["Helicopters"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [493]
	[494] = 
	{
		["type"] = "FARP",
		["name"] = "FARP",
		["category"] = "Heliport",
		["isPutToWater"] = true,
		["description"] = "FARP",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [494]
	[495] = 
	{
		["type"] = "FARP_SINGLE_01",
		["name"] = "PAD Single",
		["category"] = "Heliport",
		["isPutToWater"] = true,
		["description"] = "PAD Single",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [495]
	[496] = 
	{
		["type"] = "Gas platform",
		["name"] = "Gas platform",
		["category"] = "Heliport",
		["isPutToWater"] = true,
		["description"] = "Gas platform",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [496]
	[497] = 
	{
		["type"] = "Invisible FARP",
		["name"] = "Invisible FARP",
		["category"] = "Heliport",
		["description"] = "Invisible FARP",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [497]
	[498] = 
	{
		["type"] = "Oil rig",
		["name"] = "Oil rig",
		["category"] = "Heliport",
		["isPutToWater"] = true,
		["description"] = "Oil rig",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [498]
	[499] = 
	{
		["type"] = "SINGLE_HELIPAD",
		["name"] = "Helipad Single",
		["category"] = "Heliport",
		["isPutToWater"] = true,
		["description"] = "Helipad Single",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [499]
	[500] = 
	{
		["type"] = "Infantry AK",
		["name"] = "Infantry AK-74 Rus ver1",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry AK-74 Rus ver1",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			["Skeleton_type_A"] = true,
			[90] = true,
			["New infantry"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [500]
	[501] = 
	{
		["type"] = "Infantry AK Ins",
		["name"] = "Insurgent AKM",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Insurgent AKM",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			["Skeleton_type_A"] = true,
			[90] = true,
			["New infantry"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [501]
	[502] = 
	{
		["type"] = "Infantry AK ver2",
		["name"] = "Infantry AK-74 Rus ver2",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry AK-74 Rus ver2",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			["Skeleton_type_A"] = true,
			[90] = true,
			["New infantry"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [502]
	[503] = 
	{
		["type"] = "Infantry AK ver3",
		["name"] = "Infantry AK-74 Rus ver3",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry AK-74 Rus ver3",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			["Skeleton_type_A"] = true,
			[90] = true,
			["New infantry"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [503]
	[504] = 
	{
		["type"] = "JTAC",
		["name"] = "JTAC",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "JTAC",
		["attribute"] = 
		{
			["Infantry"] = true,
			[27] = true,
			[2] = true,
			[16] = true,
			[90] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground Units Non Airdefence"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [504]
	[505] = 
	{
		["type"] = "Paratrooper AKS-74",
		["name"] = "Paratrooper AKS",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Paratrooper AKS",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			[90] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [505]
	[506] = 
	{
		["type"] = "Paratrooper RPG-16",
		["name"] = "Paratrooper RPG-16",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Paratrooper RPG-16",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			[90] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [506]
	[507] = 
	{
		["type"] = "Soldier AK",
		["name"] = "Infantry AK-74",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry AK-74",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			[90] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [507]
	[508] = 
	{
		["type"] = "Soldier M249",
		["name"] = "Infantry M249",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry M249",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["Prone"] = true,
			[90] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [508]
	[509] = 
	{
		["type"] = "Soldier M4",
		["name"] = "Infantry M4",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry M4",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			[90] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [509]
	[510] = 
	{
		["type"] = "Soldier M4 GRG",
		["name"] = "Infantry M4 Georgia",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry M4 Georgia",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			[90] = true,
			["New infantry"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [510]
	[511] = 
	{
		["type"] = "Soldier RPG",
		["name"] = "Infantry RPG",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry RPG",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			[90] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [511]
	[512] = 
	{
		["type"] = "soldier_mauser98",
		["name"] = "Infantry Mauser 98",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry Mauser 98",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			["Skeleton_type_A"] = true,
			[90] = true,
			["New infantry"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [512]
	[513] = 
	{
		["type"] = "soldier_wwii_br_01",
		["name"] = "Infantry SMLE No.4 Mk-1",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry SMLE No.4 Mk-1",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			["Skeleton_type_A"] = true,
			[90] = true,
			["New infantry"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [513]
	[514] = 
	{
		["type"] = "soldier_wwii_us",
		["name"] = "Infantry M1 Garand",
		["category"] = "Infantry",
		["infantry"] = true,
		["description"] = "Infantry M1 Garand",
		["attribute"] = 
		{
			["Infantry"] = true,
			[26] = true,
			[2] = true,
			["NonArmoredUnits"] = true,
			["Skeleton_type_A"] = true,
			[90] = true,
			["New infantry"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [514]
	[515] = 
	{
		["type"] = "DRG_Class_86",
		["name"] = "Loco DRG Class 86",
		["category"] = "Locomotive",
		["description"] = "Loco DRG Class 86",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[99] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [515]
	[516] = 
	{
		["type"] = "Electric locomotive",
		["name"] = "Loco VL80 Electric",
		["category"] = "Locomotive",
		["description"] = "Loco VL80 Electric",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Unarmed vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [516]
	[517] = 
	{
		["type"] = "ES44AH",
		["name"] = "Loco ES44AH",
		["category"] = "Locomotive",
		["description"] = "Loco ES44AH",
		["vehicle"] = true,
		["attribute"] = 
		{
			[48] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground Units Non Airdefence"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [517]
	[518] = 
	{
		["type"] = "Locomotive",
		["name"] = "Loco CHME3T",
		["category"] = "Locomotive",
		["description"] = "Loco CHME3T",
		["vehicle"] = true,
		["attribute"] = 
		{
			[48] = true,
			["Vehicles"] = true,
			[100] = true,
			[2] = true,
			[8] = true,
			["Trucks"] = true,
			["Ground vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground Units Non Airdefence"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [518]
	[519] = 
	{
		["type"] = "hy_launcher",
		["name"] = "AShM SS-N-2 Silkworm",
		["category"] = "MissilesSS",
		["description"] = "AShM SS-N-2 Silkworm",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[16] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			["SS_missile"] = true,
			["Armed vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[339] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [519]
	[520] = 
	{
		["type"] = "Scud_B",
		["name"] = "SSM SS-1C Scud-B",
		["category"] = "MissilesSS",
		["description"] = "SSM SS-1C Scud-B",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armed vehicles"] = true,
			[63] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["SS_missile"] = true,
			["Armed ground units"] = true,
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[27] = true,
			["Indirect fire"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Datalink"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [520]
	[521] = 
	{
		["type"] = "Silkworm_SR",
		["name"] = "AShM Silkworm SR",
		["category"] = "MissilesSS",
		["description"] = "AShM Silkworm SR",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			["Ground Units Non Airdefence"] = true,
			["Vehicles"] = true,
			[263] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["Armed vehicles"] = true,
			[101] = true,
			["Ground vehicles"] = true,
			["Indirect fire"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[16] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [521]
	[522] = 
	{
		["type"] = "v1_launcher",
		["name"] = "V-1 Launch Ramp",
		["category"] = "MissilesSS",
		["description"] = "V-1 Launch Ramp",
		["vehicle"] = true,
		["attribute"] = 
		{
			["Artillery"] = true,
			[63] = true,
			["Vehicles"] = true,
			[27] = true,
			[2] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Indirect fire"] = true,
			[17] = true,
			["Armed vehicles"] = true,
			["SS_missile"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground Units Non Airdefence"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [522]
	[523] = 
	{
		["type"] = "Carrier Airboss",
		["name"] = "Carrier Airboss",
		["category"] = "Personnel",
		["description"] = "Carrier Airboss",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [523]
	[524] = 
	{
		["type"] = "Carrier LSO Personell",
		["name"] = "Carrier LSO 1",
		["category"] = "Personnel",
		["description"] = "Carrier LSO 1",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [524]
	[525] = 
	{
		["type"] = "Carrier LSO Personell 1",
		["name"] = "Carrier LSO 2",
		["category"] = "Personnel",
		["description"] = "Carrier LSO 2",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [525]
	[526] = 
	{
		["type"] = "Carrier LSO Personell 2",
		["name"] = "Carrier LSO 3",
		["category"] = "Personnel",
		["description"] = "Carrier LSO 3",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [526]
	[527] = 
	{
		["type"] = "Carrier LSO Personell 3",
		["name"] = "Carrier LSO 4",
		["category"] = "Personnel",
		["description"] = "Carrier LSO 4",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [527]
	[528] = 
	{
		["type"] = "Carrier LSO Personell 4",
		["name"] = "Carrier LSO 5",
		["category"] = "Personnel",
		["description"] = "Carrier LSO 5",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [528]
	[529] = 
	{
		["type"] = "Carrier LSO Personell 5",
		["name"] = "Carrier LSO 6",
		["category"] = "Personnel",
		["description"] = "Carrier LSO 6",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [529]
	[530] = 
	{
		["type"] = "Carrier Seaman",
		["name"] = "Carrier Seaman",
		["category"] = "Personnel",
		["description"] = "Carrier Seaman",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [530]
	[531] = 
	{
		["type"] = "us carrier shooter",
		["name"] = "Carrier Shooter",
		["category"] = "Personnel",
		["description"] = "Carrier Shooter",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [531]
	[532] = 
	{
		["type"] = "us carrier tech",
		["name"] = "Carrier Technician",
		["category"] = "Personnel",
		["description"] = "Carrier Technician",
		["attribute"] = 
		{
			[5] = true,
			[9] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [532]
	[533] = 
	{
		["air"] = true,
		["type"] = "A-10A",
		["name"] = "A-10A",
		["category"] = "Plane",
		["description"] = "A-10A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			[17] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			[6] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [533]
	[534] = 
	{
		["air"] = true,
		["type"] = "A-10C",
		["name"] = "A-10C",
		["category"] = "Plane",
		["description"] = "A-10C",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Planes"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			[6] = true,
			["All"] = true,
			["Datalink"] = true,
			[58] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [534]
	[535] = 
	{
		["air"] = true,
		["type"] = "A-10C_2",
		["name"] = "A-10C II",
		["category"] = "Plane",
		["description"] = "A-10C II",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Planes"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			[6] = true,
			["All"] = true,
			["Datalink"] = true,
			[264] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [535]
	[536] = 
	{
		["air"] = true,
		["type"] = "A-20G",
		["name"] = "A-20G",
		["category"] = "Plane",
		["description"] = "A-20G",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[263] = true,
			["NonArmoredUnits"] = true,
			[4] = true,
			["Strategic bombers"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Bombers"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [536]
	[537] = 
	{
		["air"] = true,
		["type"] = "A-50",
		["name"] = "A-50",
		["category"] = "Plane",
		["description"] = "A-50",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[26] = true,
			["Refuelable"] = true,
			["AWACS"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [537]
	[538] = 
	{
		["air"] = true,
		["type"] = "AJS37",
		["name"] = "AJS37",
		["category"] = "Plane",
		["description"] = "AJS37",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			[265] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [538]
	[539] = 
	{
		["air"] = true,
		["type"] = "An-26B",
		["name"] = "An-26B",
		["category"] = "Plane",
		["description"] = "An-26B",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[39] = true,
			["Transports"] = true,
			["All"] = true,
			[5] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [539]
	[540] = 
	{
		["air"] = true,
		["type"] = "An-30M",
		["name"] = "An-30M",
		["category"] = "Plane",
		["description"] = "An-30M",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Transports"] = true,
			["All"] = true,
			[5] = true,
			[40] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [540]
	[541] = 
	{
		["air"] = true,
		["type"] = "AV8BNA",
		["name"] = "AV-8B N/A",
		["category"] = "Plane",
		["description"] = "AV-8B N/A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			[266] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Bombers"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [541]
	[542] = 
	{
		["air"] = true,
		["type"] = "B-17G",
		["name"] = "B-17G",
		["category"] = "Plane",
		["description"] = "B-17G",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[294] = true,
			[4] = true,
			["Strategic bombers"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["NonArmoredUnits"] = true,
			["Bombers"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [542]
	[543] = 
	{
		["air"] = true,
		["type"] = "B-1B",
		["name"] = "B-1B",
		["category"] = "Plane",
		["description"] = "B-1B",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Strategic bombers"] = true,
			["Bombers"] = true,
			["Planes"] = true,
			["Battle airplanes"] = true,
			[19] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Link16"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [543]
	[544] = 
	{
		["air"] = true,
		["type"] = "B-52H",
		["name"] = "B-52H",
		["category"] = "Plane",
		["description"] = "B-52H",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			[4] = true,
			["Strategic bombers"] = true,
			["Bombers"] = true,
			["Planes"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Link16"] = true,
			["All"] = true,
			[23] = true,
			["Datalink"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [544]
	[545] = 
	{
		["air"] = true,
		["type"] = "Bf-109K-4",
		["name"] = "Bf 109 K-4",
		["category"] = "Plane",
		["description"] = "Bf 109 K-4",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			[257] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [545]
	[546] = 
	{
		["air"] = true,
		["type"] = "C-101CC",
		["name"] = "C-101CC",
		["category"] = "Plane",
		["description"] = "C-101CC",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[6] = true,
			["Planes"] = true,
			[270] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [546]
	[547] = 
	{
		["air"] = true,
		["type"] = "C-101EB",
		["name"] = "C-101EB",
		["category"] = "Plane",
		["description"] = "C-101EB",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			[269] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["All"] = true,
			[6] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [547]
	[548] = 
	{
		["air"] = true,
		["type"] = "C-130",
		["name"] = "C-130",
		["category"] = "Plane",
		["description"] = "C-130",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[31] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["Transports"] = true,
			["All"] = true,
			[5] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [548]
	[549] = 
	{
		["air"] = true,
		["type"] = "C-17A",
		["name"] = "C-17A",
		["category"] = "Plane",
		["description"] = "C-17A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			[47] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["Transports"] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [549]
	[550] = 
	{
		["air"] = true,
		["type"] = "C-47",
		["name"] = "C-47",
		["category"] = "Plane",
		["description"] = "C-47",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Planes"] = true,
			[299] = true,
			["NonArmoredUnits"] = true,
			["Transports"] = true,
			["All"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [550]
	[551] = 
	{
		["air"] = true,
		["type"] = "Christen Eagle II",
		["name"] = "Christen Eagle II",
		["category"] = "Plane",
		["description"] = "Christen Eagle II",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			[274] = true,
			["Planes"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [551]
	[552] = 
	{
		["air"] = true,
		["type"] = "E-2C",
		["name"] = "E-2D",
		["category"] = "Plane",
		["description"] = "E-2D",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			[41] = true,
			["Link16"] = true,
			["AWACS"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [552]
	[553] = 
	{
		["air"] = true,
		["type"] = "E-3A",
		["name"] = "E-3A",
		["category"] = "Plane",
		["description"] = "E-3A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[27] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["AWACS"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [553]
	[554] = 
	{
		["air"] = true,
		["type"] = "F-117A",
		["name"] = "F-117A",
		["category"] = "Plane",
		["description"] = "F-117A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[37] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			[6] = true,
			["All"] = true,
			["Bombers"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [554]
	[555] = 
	{
		["air"] = true,
		["type"] = "F-14A",
		["name"] = "F-14A",
		["category"] = "Plane",
		["description"] = "F-14A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonArmoredUnits"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["All"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [555]
	[556] = 
	{
		["air"] = true,
		["type"] = "F-14A-135-GR",
		["name"] = "F-14A-135-GR",
		["category"] = "Plane",
		["description"] = "F-14A-135-GR",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			[279] = true,
			["All"] = true,
			["Datalink"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [556]
	[557] = 
	{
		["air"] = true,
		["type"] = "F-14B",
		["name"] = "F-14B",
		["category"] = "Plane",
		["description"] = "F-14B",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			[278] = true,
			["Refuelable"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [557]
	[558] = 
	{
		["air"] = true,
		["type"] = "F-15C",
		["name"] = "F-15C",
		["category"] = "Plane",
		["description"] = "F-15C",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			[6] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [558]
	[559] = 
	{
		["air"] = true,
		["type"] = "F-15E",
		["name"] = "F-15E",
		["category"] = "Plane",
		["description"] = "F-15E",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			[59] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [559]
	[560] = 
	{
		["air"] = true,
		["type"] = "F-15ESE",
		["name"] = "F-15E S4+",
		["category"] = "Plane",
		["description"] = "F-15E S4+",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			[327] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [560]
	[561] = 
	{
		["air"] = true,
		["type"] = "F-16A",
		["name"] = "F-16A",
		["category"] = "Plane",
		["description"] = "F-16A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[52] = true,
			["Refuelable"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [561]
	[562] = 
	{
		["air"] = true,
		["type"] = "F-16A MLU",
		["name"] = "F-16A MLU",
		["category"] = "Plane",
		["description"] = "F-16A MLU",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[52] = true,
			["Refuelable"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [562]
	[563] = 
	{
		["air"] = true,
		["type"] = "F-16C bl.50",
		["name"] = "F-16C bl.50",
		["category"] = "Plane",
		["description"] = "F-16C bl.50",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[7] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [563]
	[564] = 
	{
		["air"] = true,
		["type"] = "F-16C bl.52d",
		["name"] = "F-16C bl.52d",
		["category"] = "Plane",
		["description"] = "F-16C bl.52d",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[7] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [564]
	[565] = 
	{
		["air"] = true,
		["type"] = "F-16C_50",
		["name"] = "F-16CM bl.50",
		["category"] = "Plane",
		["description"] = "F-16CM bl.50",
		["attribute"] = 
		{
			[1] = true,
			[275] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["Air"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [565]
	[566] = 
	{
		["air"] = true,
		["type"] = "F-4E",
		["name"] = "F-4E",
		["category"] = "Plane",
		["description"] = "F-4E",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[45] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [566]
	[567] = 
	{
		["air"] = true,
		["type"] = "F-4E-45MC",
		["name"] = "F-4E-45MC",
		["category"] = "Plane",
		["description"] = "F-4E-45MC",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonArmoredUnits"] = true,
			[328] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [567]
	[568] = 
	{
		["air"] = true,
		["type"] = "F-5E",
		["name"] = "F-5E",
		["category"] = "Plane",
		["description"] = "F-5E",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			[46] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Battle airplanes"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [568]
	[569] = 
	{
		["air"] = true,
		["type"] = "F-5E-3",
		["name"] = "F-5E-3",
		["category"] = "Plane",
		["description"] = "F-5E-3",
		["attribute"] = 
		{
			[1] = true,
			[276] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Air"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [569]
	[570] = 
	{
		["air"] = true,
		["type"] = "F-5E-3_FC",
		["name"] = "F-5E FC",
		["category"] = "Plane",
		["description"] = "F-5E FC",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[331] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [570]
	[571] = 
	{
		["air"] = true,
		["type"] = "F-86F Sabre",
		["name"] = "F-86F",
		["category"] = "Plane",
		["description"] = "F-86F",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[277] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Battle airplanes"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [571]
	[572] = 
	{
		["air"] = true,
		["type"] = "F-86F_FC",
		["name"] = "F-86F FC",
		["category"] = "Plane",
		["description"] = "F-86F FC",
		["attribute"] = 
		{
			[1] = true,
			[332] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Air"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [572]
	[573] = 
	{
		["air"] = true,
		["type"] = "F/A-18A",
		["name"] = "F/A-18A",
		["category"] = "Plane",
		["description"] = "F/A-18A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[14] = true,
			["Refuelable"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [573]
	[574] = 
	{
		["air"] = true,
		["type"] = "F/A-18C",
		["name"] = "F/A-18C",
		["category"] = "Plane",
		["description"] = "F/A-18C",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[53] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [574]
	[575] = 
	{
		["air"] = true,
		["type"] = "FA-18C_hornet",
		["name"] = "F/A-18C Lot 20",
		["category"] = "Plane",
		["description"] = "F/A-18C Lot 20",
		["attribute"] = 
		{
			[1] = true,
			["Link4"] = true,
			["Refuelable"] = true,
			["ACLS"] = true,
			["Link16"] = true,
			["Multirole fighters"] = true,
			["Planes"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			[280] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [575]
	[576] = 
	{
		["air"] = true,
		["type"] = "Falcon_Gyrocopter",
		["name"] = "Falcon Assault Gyrocopter",
		["category"] = "Plane",
		["description"] = "Falcon Assault Gyrocopter",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			[330] = true,
			["Planes"] = true,
			["Transports"] = true,
			["All"] = true,
			[6] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [576]
	[577] = 
	{
		["air"] = true,
		["type"] = "FW-190A8",
		["name"] = "Fw 190 A-8",
		["category"] = "Plane",
		["description"] = "Fw 190 A-8",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[256] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Battle airplanes"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [577]
	[578] = 
	{
		["air"] = true,
		["type"] = "FW-190D9",
		["name"] = "Fw 190 D-9",
		["category"] = "Plane",
		["description"] = "Fw 190 D-9",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[255] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [578]
	[579] = 
	{
		["air"] = true,
		["type"] = "H-6J",
		["name"] = "H-6J",
		["category"] = "Plane",
		["description"] = "H-6J",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Strategic bombers"] = true,
			[298] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Bombers"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [579]
	[580] = 
	{
		["air"] = true,
		["type"] = "Hawk",
		["name"] = "Hawk",
		["category"] = "Plane",
		["description"] = "Hawk",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			[281] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [580]
	[581] = 
	{
		["air"] = true,
		["type"] = "I-16",
		["name"] = "I-16",
		["category"] = "Plane",
		["description"] = "I-16",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[282] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [581]
	[582] = 
	{
		["air"] = true,
		["type"] = "IL-76MD",
		["name"] = "IL-76MD",
		["category"] = "Plane",
		["description"] = "IL-76MD",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Transports"] = true,
			["All"] = true,
			[5] = true,
			[30] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [582]
	[583] = 
	{
		["air"] = true,
		["type"] = "IL-78M",
		["name"] = "IL-78M",
		["category"] = "Plane",
		["description"] = "IL-78M",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Tankers"] = true,
			[28] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [583]
	[584] = 
	{
		["air"] = true,
		["type"] = "J-11A",
		["name"] = "J-11A",
		["category"] = "Plane",
		["description"] = "J-11A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[66] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [584]
	[585] = 
	{
		["air"] = true,
		["type"] = "JF-17",
		["name"] = "JF-17",
		["category"] = "Plane",
		["description"] = "JF-17",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[271] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [585]
	[586] = 
	{
		["air"] = true,
		["type"] = "Ju-88A4",
		["name"] = "Ju 88 A-4",
		["category"] = "Plane",
		["description"] = "Ju 88 A-4",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			[295] = true,
			["Strategic bombers"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["Bombers"] = true,
			[4] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [586]
	[587] = 
	{
		["air"] = true,
		["type"] = "KC-135",
		["name"] = "KC-135",
		["category"] = "Plane",
		["description"] = "KC-135",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Tankers"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			[60] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [587]
	[588] = 
	{
		["air"] = true,
		["type"] = "KC130",
		["name"] = "KC-130",
		["category"] = "Plane",
		["description"] = "KC-130",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Tankers"] = true,
			["Refuelable"] = true,
			[267] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [588]
	[589] = 
	{
		["air"] = true,
		["type"] = "KC135MPRS",
		["name"] = "KC-135MPRS",
		["category"] = "Plane",
		["description"] = "KC-135MPRS",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Tankers"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			[268] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [589]
	[590] = 
	{
		["air"] = true,
		["type"] = "KJ-2000",
		["name"] = "KJ-2000",
		["category"] = "Plane",
		["description"] = "KJ-2000",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Link16"] = true,
			["AWACS"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			[272] = true,
			["All"] = true,
			["Datalink"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [590]
	[591] = 
	{
		["air"] = true,
		["type"] = "L-39C",
		["name"] = "L-39C",
		["category"] = "Plane",
		["description"] = "L-39C",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			[283] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[6] = true,
			["Planes"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [591]
	[592] = 
	{
		["air"] = true,
		["type"] = "L-39ZA",
		["name"] = "L-39ZA",
		["category"] = "Plane",
		["description"] = "L-39ZA",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[6] = true,
			["All"] = true,
			["Planes"] = true,
			[61] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [592]
	[593] = 
	{
		["air"] = true,
		["type"] = "M-2000C",
		["name"] = "M-2000C",
		["category"] = "Plane",
		["description"] = "M-2000C",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
			[284] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["All"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [593]
	[594] = 
	{
		["air"] = true,
		["type"] = "MB-339A",
		["name"] = "MB-339A",
		["category"] = "Plane",
		["description"] = "MB-339A",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[324] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [594]
	[595] = 
	{
		["air"] = true,
		["type"] = "MB-339APAN",
		["name"] = "MB-339A/PAN",
		["category"] = "Plane",
		["description"] = "MB-339A/PAN",
		["attribute"] = 
		{
			[1] = true,
			[325] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Air"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [595]
	[596] = 
	{
		["air"] = true,
		["type"] = "MiG-15bis",
		["name"] = "MiG-15bis",
		["category"] = "Plane",
		["description"] = "MiG-15bis",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			[286] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [596]
	[597] = 
	{
		["air"] = true,
		["type"] = "MiG-15bis_FC",
		["name"] = "MiG-15bis FC",
		["category"] = "Plane",
		["description"] = "MiG-15bis FC",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[333] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Battle airplanes"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [597]
	[598] = 
	{
		["air"] = true,
		["type"] = "MiG-19P",
		["name"] = "MiG-19P",
		["category"] = "Plane",
		["description"] = "MiG-19P",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			[287] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [598]
	[599] = 
	{
		["air"] = true,
		["type"] = "MiG-21Bis",
		["name"] = "MiG-21Bis",
		["category"] = "Plane",
		["description"] = "MiG-21Bis",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			[288] = true,
			["Planes"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [599]
	[600] = 
	{
		["air"] = true,
		["type"] = "MiG-23MLD",
		["name"] = "MiG-23MLD",
		["category"] = "Plane",
		["description"] = "MiG-23MLD",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Battle airplanes"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [600]
	[601] = 
	{
		["air"] = true,
		["type"] = "MiG-25PD",
		["name"] = "MiG-25PD",
		["category"] = "Plane",
		["description"] = "MiG-25PD",
		["attribute"] = 
		{
			[1] = true,
			[24] = true,
			["Interceptors"] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[3] = true,
			["Planes"] = true,
			["Air"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [601]
	[602] = 
	{
		["air"] = true,
		["type"] = "MiG-25RBT",
		["name"] = "MiG-25RBT",
		["category"] = "Plane",
		["description"] = "MiG-25RBT",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Planes"] = true,
			[8] = true,
			[3] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Aux"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [602]
	[603] = 
	{
		["air"] = true,
		["type"] = "MiG-27K",
		["name"] = "MiG-27K",
		["category"] = "Plane",
		["description"] = "MiG-27K",
		["attribute"] = 
		{
			[1] = true,
			[11] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Air"] = true,
			["Bombers"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [603]
	[604] = 
	{
		["air"] = true,
		["type"] = "MiG-29A",
		["name"] = "MiG-29A",
		["category"] = "Plane",
		["description"] = "MiG-29A",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Battle airplanes"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [604]
	[605] = 
	{
		["air"] = true,
		["type"] = "MiG-29G",
		["name"] = "MiG-29G",
		["category"] = "Plane",
		["description"] = "MiG-29G",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[49] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [605]
	[606] = 
	{
		["air"] = true,
		["type"] = "MiG-29S",
		["name"] = "MiG-29S",
		["category"] = "Plane",
		["description"] = "MiG-29S",
		["attribute"] = 
		{
			[1] = true,
			[50] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Air"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [606]
	[607] = 
	{
		["air"] = true,
		["type"] = "MiG-31",
		["name"] = "MiG-31",
		["category"] = "Plane",
		["description"] = "MiG-31",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Interceptors"] = true,
			["Refuelable"] = true,
			[9] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[3] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [607]
	[608] = 
	{
		["air"] = true,
		["type"] = "Mirage 2000-5",
		["name"] = "Mirage 2000-5",
		["category"] = "Plane",
		["description"] = "Mirage 2000-5",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Multirole fighters"] = true,
			[34] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["Planes"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [608]
	[609] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1AD",
		["name"] = "Mirage F1AD",
		["category"] = "Plane",
		["description"] = "Mirage F1AD",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			[334] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [609]
	[610] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1AZ",
		["name"] = "Mirage F1AZ",
		["category"] = "Plane",
		["description"] = "Mirage F1AZ",
		["attribute"] = 
		{
			[1] = true,
			[335] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["All"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [610]
	[611] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1B",
		["name"] = "Mirage F1B",
		["category"] = "Plane",
		["description"] = "Mirage F1B",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[319] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [611]
	[612] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1BD",
		["name"] = "Mirage F1BD",
		["category"] = "Plane",
		["description"] = "Mirage F1BD",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			[322] = true,
			["All"] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [612]
	[613] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1BE",
		["name"] = "Mirage F1BE",
		["category"] = "Plane",
		["description"] = "Mirage F1BE",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[320] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [613]
	[614] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1BQ",
		["name"] = "Mirage F1BQ",
		["category"] = "Plane",
		["description"] = "Mirage F1BQ",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[321] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [614]
	[615] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1C",
		["name"] = "Mirage F1C",
		["category"] = "Plane",
		["description"] = "Mirage F1C",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			[301] = true,
			["All"] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [615]
	[616] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1C-200",
		["name"] = "Mirage F1C-200",
		["category"] = "Plane",
		["description"] = "Mirage F1C-200",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[306] = true,
			["Refuelable"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [616]
	[617] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1CE",
		["name"] = "Mirage F1CE",
		["category"] = "Plane",
		["description"] = "Mirage F1CE",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[302] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [617]
	[618] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1CG",
		["name"] = "Mirage F1CG",
		["category"] = "Plane",
		["description"] = "Mirage F1CG",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[310] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [618]
	[619] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1CH",
		["name"] = "Mirage F1CH",
		["category"] = "Plane",
		["description"] = "Mirage F1CH",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			[308] = true,
			["All"] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [619]
	[620] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1CJ",
		["name"] = "Mirage F1CJ",
		["category"] = "Plane",
		["description"] = "Mirage F1CJ",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[312] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [620]
	[621] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1CK",
		["name"] = "Mirage F1CK",
		["category"] = "Plane",
		["description"] = "Mirage F1CK",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			[313] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [621]
	[622] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1CR",
		["name"] = "Mirage F1CR",
		["category"] = "Plane",
		["description"] = "Mirage F1CR",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[317] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [622]
	[623] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1CT",
		["name"] = "Mirage F1CT",
		["category"] = "Plane",
		["description"] = "Mirage F1CT",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[318] = true,
			["All"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [623]
	[624] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1CZ",
		["name"] = "Mirage F1CZ",
		["category"] = "Plane",
		["description"] = "Mirage F1CZ",
		["attribute"] = 
		{
			[1] = true,
			[311] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Air"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [624]
	[625] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1DDA",
		["name"] = "Mirage F1DDA",
		["category"] = "Plane",
		["description"] = "Mirage F1DDA",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[323] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [625]
	[626] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1ED",
		["name"] = "Mirage F1ED",
		["category"] = "Plane",
		["description"] = "Mirage F1ED",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			[315] = true,
			["All"] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [626]
	[627] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1EDA",
		["name"] = "Mirage F1EDA",
		["category"] = "Plane",
		["description"] = "Mirage F1EDA",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			[316] = true,
			["All"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [627]
	[628] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1EE",
		["name"] = "Mirage F1EE",
		["category"] = "Plane",
		["description"] = "Mirage F1EE",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			[303] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [628]
	[629] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1EH",
		["name"] = "Mirage F1EH",
		["category"] = "Plane",
		["description"] = "Mirage F1EH",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[307] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [629]
	[630] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1EQ",
		["name"] = "Mirage F1EQ",
		["category"] = "Plane",
		["description"] = "Mirage F1EQ",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
			[314] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["All"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [630]
	[631] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1JA",
		["name"] = "Mirage F1JA",
		["category"] = "Plane",
		["description"] = "Mirage F1JA",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			[309] = true,
			["All"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [631]
	[632] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1M-CE",
		["name"] = "Mirage F1M (C.14 1-25/32-51)",
		["category"] = "Plane",
		["description"] = "Mirage F1M (C.14 1-25/32-51)",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[305] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [632]
	[633] = 
	{
		["air"] = true,
		["type"] = "Mirage-F1M-EE",
		["name"] = "Mirage F1M (C.14 52-73)",
		["category"] = "Plane",
		["description"] = "Mirage F1M (C.14 52-73)",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Multirole fighters"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			[304] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [633]
	[634] = 
	{
		["air"] = true,
		["type"] = "MosquitoFBMkVI",
		["name"] = "Mosquito FB Mk. VI",
		["category"] = "Plane",
		["description"] = "Mosquito FB Mk. VI",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			[4] = true,
			[297] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [634]
	[635] = 
	{
		["air"] = true,
		["type"] = "MQ-9 Reaper",
		["name"] = "MQ-9 Reaper",
		["category"] = "Plane",
		["description"] = "MQ-9 Reaper",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["UAVs"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			[285] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [635]
	[636] = 
	{
		["air"] = true,
		["type"] = "P-47D-30",
		["name"] = "P-47D-30",
		["category"] = "Plane",
		["description"] = "P-47D-30",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			[260] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [636]
	[637] = 
	{
		["air"] = true,
		["type"] = "P-47D-30bl1",
		["name"] = "P-47D-30 (Early)",
		["category"] = "Plane",
		["description"] = "P-47D-30 (Early)",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[261] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [637]
	[638] = 
	{
		["air"] = true,
		["type"] = "P-47D-40",
		["name"] = "P-47D-40",
		["category"] = "Plane",
		["description"] = "P-47D-40",
		["attribute"] = 
		{
			[1] = true,
			[262] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Air"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [638]
	[639] = 
	{
		["air"] = true,
		["type"] = "P-51D",
		["name"] = "P-51D-25-NA",
		["category"] = "Plane",
		["description"] = "P-51D-25-NA",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[63] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [639]
	[640] = 
	{
		["air"] = true,
		["type"] = "P-51D-30-NA",
		["name"] = "P-51D-30-NA",
		["category"] = "Plane",
		["description"] = "P-51D-30-NA",
		["attribute"] = 
		{
			[1] = true,
			[64] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Air"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [640]
	[641] = 
	{
		["air"] = true,
		["type"] = "RQ-1A Predator",
		["name"] = "MQ-1A Predator",
		["category"] = "Plane",
		["description"] = "MQ-1A Predator",
		["attribute"] = 
		{
			[1] = true,
			["UAVs"] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[55] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [641]
	[642] = 
	{
		["air"] = true,
		["type"] = "S-3B",
		["name"] = "S-3B",
		["category"] = "Plane",
		["description"] = "S-3B",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Aux"] = true,
			["Link16"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			[42] = true,
			["Datalink"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [642]
	[643] = 
	{
		["air"] = true,
		["type"] = "S-3B Tanker",
		["name"] = "S-3B Tanker",
		["category"] = "Plane",
		["description"] = "S-3B Tanker",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[33] = true,
			["Refuelable"] = true,
			["Aux"] = true,
			["Link16"] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Tankers"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [643]
	[644] = 
	{
		["air"] = true,
		["type"] = "SpitfireLFMkIX",
		["name"] = "Spitfire LF Mk. IX",
		["category"] = "Plane",
		["description"] = "Spitfire LF Mk. IX",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			[258] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [644]
	[645] = 
	{
		["air"] = true,
		["type"] = "SpitfireLFMkIXCW",
		["name"] = "Spitfire LF Mk. IX CW",
		["category"] = "Plane",
		["description"] = "Spitfire LF Mk. IX CW",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			[259] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [645]
	[646] = 
	{
		["air"] = true,
		["type"] = "Su-17M4",
		["name"] = "Su-17M4",
		["category"] = "Plane",
		["description"] = "Su-17M4",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["Bombers"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["NonAndLightArmoredUnits"] = true,
			[48] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [646]
	[647] = 
	{
		["air"] = true,
		["type"] = "Su-24M",
		["name"] = "Su-24M",
		["category"] = "Plane",
		["description"] = "Su-24M",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["Bombers"] = true,
			["All"] = true,
			[12] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [647]
	[648] = 
	{
		["air"] = true,
		["type"] = "Su-24MR",
		["name"] = "Su-24MR",
		["category"] = "Plane",
		["description"] = "Su-24MR",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[51] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
			["All"] = true,
			["NonArmoredUnits"] = true,
			["Aux"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [648]
	[649] = 
	{
		["air"] = true,
		["type"] = "Su-25",
		["name"] = "Su-25",
		["category"] = "Plane",
		["description"] = "Su-25",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			[16] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			[6] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [649]
	[650] = 
	{
		["air"] = true,
		["type"] = "Su-25T",
		["name"] = "Su-25T",
		["category"] = "Plane",
		["description"] = "Su-25T",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[6] = true,
			["All"] = true,
			["Planes"] = true,
			[54] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [650]
	[651] = 
	{
		["air"] = true,
		["type"] = "Su-25TM",
		["name"] = "Su-25TM",
		["category"] = "Plane",
		["description"] = "Su-25TM",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[38] = true,
			["All"] = true,
			[6] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [651]
	[652] = 
	{
		["air"] = true,
		["type"] = "Su-27",
		["name"] = "Su-27",
		["category"] = "Plane",
		["description"] = "Su-27",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonAndLightArmoredUnits"] = true,
			[3] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Battle airplanes"] = true,
			["Planes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [652]
	[653] = 
	{
		["air"] = true,
		["type"] = "Su-30",
		["name"] = "Su-30",
		["category"] = "Plane",
		["description"] = "Su-30",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			[13] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Multirole fighters"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [653]
	[654] = 
	{
		["air"] = true,
		["type"] = "Su-33",
		["name"] = "Su-33",
		["category"] = "Plane",
		["description"] = "Su-33",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Fighters"] = true,
			["NonArmoredUnits"] = true,
			[4] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [654]
	[655] = 
	{
		["air"] = true,
		["type"] = "Su-34",
		["name"] = "Su-34",
		["category"] = "Plane",
		["description"] = "Su-34",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[20] = true,
			["Planes"] = true,
			["All"] = true,
			["Bombers"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [655]
	[656] = 
	{
		["air"] = true,
		["type"] = "TF-51D",
		["name"] = "TF-51D",
		["category"] = "Plane",
		["description"] = "TF-51D",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Battleplanes"] = true,
			[65] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [656]
	[657] = 
	{
		["air"] = true,
		["type"] = "Tornado GR4",
		["name"] = "Tornado GR4",
		["category"] = "Plane",
		["description"] = "Tornado GR4",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			["Link16"] = true,
			["Planes"] = true,
			["Battle airplanes"] = true,
			[10] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Bombers"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "Tornado GR3",
		}, -- end of ["aliases"]
	}, -- end of [657]
	[658] = 
	{
		["air"] = true,
		["type"] = "Tornado IDS",
		["name"] = "Tornado IDS",
		["category"] = "Plane",
		["description"] = "Tornado IDS",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			[56] = true,
			["Link16"] = true,
			["Battle airplanes"] = true,
			["Bombers"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Datalink"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [658]
	[659] = 
	{
		["air"] = true,
		["type"] = "Tu-142",
		["name"] = "Tu-142",
		["category"] = "Plane",
		["description"] = "Tu-142",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			[4] = true,
			["Strategic bombers"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Bombers"] = true,
			["Planes"] = true,
			[22] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [659]
	[660] = 
	{
		["air"] = true,
		["type"] = "Tu-160",
		["name"] = "Tu-160",
		["category"] = "Plane",
		["description"] = "Tu-160",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["NonArmoredUnits"] = true,
			["Strategic bombers"] = true,
			[18] = true,
			["NonAndLightArmoredUnits"] = true,
			["Bombers"] = true,
			["Battle airplanes"] = true,
			["All"] = true,
			["Planes"] = true,
			["Refuelable"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [660]
	[661] = 
	{
		["air"] = true,
		["type"] = "Tu-22M3",
		["name"] = "Tu-22M3",
		["category"] = "Plane",
		["description"] = "Tu-22M3",
		["attribute"] = 
		{
			[1] = true,
			[2] = true,
			["Air"] = true,
			["Battle airplanes"] = true,
			["Bombers"] = true,
			[25] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [661]
	[662] = 
	{
		["air"] = true,
		["type"] = "Tu-95MS",
		["name"] = "Tu-95MS",
		["category"] = "Plane",
		["description"] = "Tu-95MS",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Refuelable"] = true,
			[4] = true,
			["Strategic bombers"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Bombers"] = true,
			[21] = true,
			["All"] = true,
			["Planes"] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [662]
	[663] = 
	{
		["air"] = true,
		["type"] = "WingLoong-I",
		["name"] = "WingLoong-I",
		["category"] = "Plane",
		["description"] = "WingLoong-I",
		["attribute"] = 
		{
			[1] = true,
			["Air"] = true,
			["Battleplanes"] = true,
			["NonArmoredUnits"] = true,
			["UAVs"] = true,
			["Battle airplanes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Planes"] = true,
			["All"] = true,
			[6] = true,
			[273] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [663]
	[664] = 
	{
		["air"] = true,
		["type"] = "Yak-40",
		["name"] = "Yak-40",
		["category"] = "Plane",
		["description"] = "Yak-40",
		["attribute"] = 
		{
			[1] = true,
			[57] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Air"] = true,
			["Transports"] = true,
			["All"] = true,
			[5] = true,
			["NonArmoredUnits"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [664]
	[665] = 
	{
		["air"] = true,
		["type"] = "Yak-52",
		["name"] = "Yak-52",
		["category"] = "Plane",
		["description"] = "Yak-52",
		["attribute"] = 
		{
			[1] = true,
			["UAVs"] = true,
			["Planes"] = true,
			["NonAndLightArmoredUnits"] = true,
			[293] = true,
			["NonArmoredUnits"] = true,
			["All"] = true,
			[5] = true,
			["Air"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [665]
	[666] = 
	{
		["type"] = "ALBATROS",
		["name"] = "Corvette 1124.4 Grisha",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Corvette 1124.4 Grisha",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			[14] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			[11] = true,
			["Armed Ship"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["All"] = true,
			[12] = true,
			[3] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [666]
	[667] = 
	{
		["type"] = "ara_vdm",
		["name"] = "ARA Veinticinco de Mayo",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "ARA Veinticinco de Mayo",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier"] = true,
			["Arresting Gear"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			[343] = true,
			[3] = true,
			[12] = true,
			["AircraftCarrier With Catapult"] = true,
			["Heavy armed ships"] = true,
			["Naval"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["All"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["catapult"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [667]
	[668] = 
	{
		["type"] = "BDK-775",
		["name"] = "LS Ropucha",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "LS Ropucha",
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			[341] = true,
			["Landing Ships"] = true,
			["Armed ships"] = true,
			["All"] = true,
			["Naval"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["Heavy armed ships"] = true,
			[12] = true,
			["Armed Ship"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [668]
	[669] = 
	{
		["type"] = "CastleClass_01",
		["name"] = "Castle Class",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Castle Class",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			[14] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["Naval"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["Corvettes"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [669]
	[670] = 
	{
		["type"] = "CV_1143_5",
		["name"] = "CV 1143.5 Admiral Kuznetsov(2017)",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CV 1143.5 Admiral Kuznetsov(2017)",
		["attribute"] = 
		{
			[255] = true,
			["Aircraft Carriers"] = true,
			["AircraftCarrier With Tramplin"] = true,
			["AircraftCarrier"] = true,
			["Arresting Gear"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			["ski_jump"] = true,
			[3] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			["Straight_in_approach_type"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Naval"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [670]
	[671] = 
	{
		["type"] = "CVN_71",
		["name"] = "CVN-71 Theodore Roosevelt",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CVN-71 Theodore Roosevelt",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier"] = true,
			["ACLS"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			[265] = true,
			["AircraftCarrier With Arresting Gear"] = true,
			["Link4"] = true,
			[3] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			["AircraftCarrier With Catapult"] = true,
			["Naval"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["Arresting Gear"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["catapult"] = true,
			["Datalink"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [671]
	[672] = 
	{
		["type"] = "CVN_72",
		["name"] = "CVN-72 Abraham Lincoln",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CVN-72 Abraham Lincoln",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier"] = true,
			["ACLS"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
			["Link4"] = true,
			[3] = true,
			["Heavy armed ships"] = true,
			[12] = true,
			["Arresting Gear"] = true,
			["AircraftCarrier With Catapult"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			[266] = true,
			["Naval"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["catapult"] = true,
			["Datalink"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [672]
	[673] = 
	{
		["type"] = "CVN_73",
		["name"] = "CVN-73 George Washington",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CVN-73 George Washington",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier"] = true,
			["ACLS"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
			["Link4"] = true,
			[3] = true,
			["Heavy armed ships"] = true,
			[12] = true,
			["Arresting Gear"] = true,
			["AircraftCarrier With Catapult"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["Datalink"] = true,
			["Naval"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["catapult"] = true,
			[267] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [673]
	[674] = 
	{
		["type"] = "CVN_75",
		["name"] = "CVN-75 Harry S. Truman",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CVN-75 Harry S. Truman",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier"] = true,
			["ACLS"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
			["Link4"] = true,
			[3] = true,
			["Heavy armed ships"] = true,
			[12] = true,
			["Naval"] = true,
			["AircraftCarrier With Catapult"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			[268] = true,
			["Arresting Gear"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["catapult"] = true,
			["Datalink"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [674]
	[675] = 
	{
		["type"] = "Dry-cargo ship-1",
		["name"] = "Bulker Yakushev",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Bulker Yakushev",
		["attribute"] = 
		{
			[15] = true,
			["Unarmed ships"] = true,
			[5] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [675]
	[676] = 
	{
		["type"] = "Dry-cargo ship-2",
		["name"] = "Cargo Ivanov",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Cargo Ivanov",
		["attribute"] = 
		{
			[15] = true,
			["Unarmed ships"] = true,
			[5] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [676]
	[677] = 
	{
		["type"] = "ELNYA",
		["name"] = "Tanker Elnya 160",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Tanker Elnya 160",
		["attribute"] = 
		{
			[15] = true,
			["Unarmed ships"] = true,
			[5] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [677]
	[678] = 
	{
		["type"] = "Forrestal",
		["name"] = "CV-59 Forrestal",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CV-59 Forrestal",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier"] = true,
			["ACLS"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
			["Link4"] = true,
			[3] = true,
			["Heavy armed ships"] = true,
			[12] = true,
			["Arresting Gear"] = true,
			["AircraftCarrier With Catapult"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["Datalink"] = true,
			["Naval"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["catapult"] = true,
			[315] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [678]
	[679] = 
	{
		["type"] = "HandyWind",
		["name"] = "Bulker Handy Wind",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Bulker Handy Wind",
		["attribute"] = 
		{
			[15] = true,
			["Unarmed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			[5] = true,
			["HelicopterCarrier"] = true,
			["Side approach departure"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [679]
	[680] = 
	{
		["type"] = "HarborTug",
		["name"] = "Harbor Tug",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Harbor Tug",
		["attribute"] = 
		{
			[15] = true,
			["Unarmed ships"] = true,
			[5] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [680]
	[681] = 
	{
		["type"] = "Higgins_boat",
		["name"] = "Boat LCVP Higgins",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Boat LCVP Higgins",
		["attribute"] = 
		{
			["Light armed ships"] = true,
			[14] = true,
			["NonArmoredUnits"] = true,
			["Ships"] = true,
			["Armed Ship"] = true,
			["Armed ships"] = true,
			["All"] = true,
			["Naval"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NO_SAM"] = true,
			[3] = true,
			[6] = true,
			["low_reflection_vessel"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [681]
	[682] = 
	{
		["type"] = "hms_invincible",
		["name"] = "HMS Invincible (R05)",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "HMS Invincible (R05)",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier With Tramplin"] = true,
			["AircraftCarrier"] = true,
			["Ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			["ski_jump"] = true,
			[3] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			["All"] = true,
			["Armed ships"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Naval"] = true,
			[331] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [682]
	[683] = 
	{
		["type"] = "IMPROVED_KILO",
		["name"] = "SSK 636 Improved Kilo",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "SSK 636 Improved Kilo",
		["attribute"] = 
		{
			["Submarines"] = true,
			["Heavy armed ships"] = true,
			["Ships"] = true,
			["HeavyArmoredUnits"] = true,
			[16] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["All"] = true,
			[23] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [683]
	[684] = 
	{
		["type"] = "KILO",
		["name"] = "SSK 877V Kilo",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "SSK 877V Kilo",
		["attribute"] = 
		{
			["Submarines"] = true,
			["Heavy armed ships"] = true,
			["Ships"] = true,
			["HeavyArmoredUnits"] = true,
			[16] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["All"] = true,
			[23] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [684]
	[685] = 
	{
		["type"] = "KUZNECOW",
		["name"] = "CV 1143.5 Admiral Kuznetsov",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CV 1143.5 Admiral Kuznetsov",
		["attribute"] = 
		{
			[1] = true,
			["Aircraft Carriers"] = true,
			["AircraftCarrier With Tramplin"] = true,
			["AircraftCarrier"] = true,
			["Ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			["ski_jump"] = true,
			[3] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			["Arresting Gear"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Straight_in_approach_type"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Naval"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [685]
	[686] = 
	{
		["type"] = "La_Combattante_II",
		["name"] = "FAC La Combattante IIa",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "FAC La Combattante IIa",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			["Armed ships"] = true,
			[12] = true,
			["Naval"] = true,
			[304] = true,
			["Armed Ship"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			["Corvettes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [686]
	[687] = 
	{
		["type"] = "leander-gun-achilles",
		["name"] = "HMS Achilles (F12)",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "HMS Achilles (F12)",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [687]
	[688] = 
	{
		["type"] = "leander-gun-andromeda",
		["name"] = "HMS Andromeda (F57)",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "HMS Andromeda (F57)",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [688]
	[689] = 
	{
		["type"] = "leander-gun-ariadne",
		["name"] = "HMS Ariadne (F72)",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "HMS Ariadne (F72)",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [689]
	[690] = 
	{
		["type"] = "leander-gun-condell",
		["name"] = "CNS Almirante Condell (PFG-06)",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CNS Almirante Condell (PFG-06)",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [690]
	[691] = 
	{
		["type"] = "leander-gun-lynch",
		["name"] = "CNS Almirante Lynch (PFG-07)",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CNS Almirante Lynch (PFG-07)",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [691]
	[692] = 
	{
		["type"] = "LHA_Tarawa",
		["name"] = "LHA-1 Tarawa",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "LHA-1 Tarawa",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier With Tramplin"] = true,
			["AircraftCarrier"] = true,
			["Ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			["ski_jump"] = true,
			[3] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			["Armed ships"] = true,
			["Armed Air Defence"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Naval"] = true,
			[269] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [692]
	[693] = 
	{
		["type"] = "LST_Mk2",
		["name"] = "LST Mk.II",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "LST Mk.II",
		["attribute"] = 
		{
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			["Armed ships"] = true,
			["Landing Ships"] = true,
			["All"] = true,
			["Naval"] = true,
			["Armed Ship"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["Heavy armed ships"] = true,
			[289] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [693]
	[694] = 
	{
		["type"] = "MOLNIYA",
		["name"] = "Corvette 1241.1 Molniya",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Corvette 1241.1 Molniya",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[14] = true,
			["Armed Air Defence"] = true,
			[15] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			[12] = true,
			["Naval"] = true,
			["Armed Ship"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			["Corvettes"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [694]
	[695] = 
	{
		["type"] = "MOSCOW",
		["name"] = "Cruiser 1164 Moskva",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Cruiser 1164 Moskva",
		["attribute"] = 
		{
			[13] = true,
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Ships"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["Cruisers"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [695]
	[696] = 
	{
		["type"] = "NEUSTRASH",
		["name"] = "Frigate 11540 Neustrashimy",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Frigate 11540 Neustrashimy",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[14] = true,
			[28] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [696]
	[697] = 
	{
		["type"] = "PERRY",
		["name"] = "FFG Oliver Hazard Perry",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "FFG Oliver Hazard Perry",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			[14] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["Naval"] = true,
			[17] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["DetectionByAWACS"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [697]
	[698] = 
	{
		["type"] = "PIOTR",
		["name"] = "Battlecruiser 1144.2 Pyotr Velikiy",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Battlecruiser 1144.2 Pyotr Velikiy",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			[14] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["HelicopterCarrier"] = true,
			["Naval"] = true,
			["Cruisers"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			[19] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [698]
	[699] = 
	{
		["type"] = "REZKY",
		["name"] = "Frigate 1135M Rezky",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Frigate 1135M Rezky",
		["attribute"] = 
		{
			[14] = true,
			["Heavy armed ships"] = true,
			["Ships"] = true,
			["Frigates"] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Naval"] = true,
			["Armed Ship"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["All"] = true,
			[12] = true,
			[3] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [699]
	[700] = 
	{
		["type"] = "santafe",
		["name"] = "ARA Santa Fe S-21",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "ARA Santa Fe S-21",
		["attribute"] = 
		{
			["Submarines"] = true,
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			["Armed Air Defence"] = true,
			[16] = true,
			["Armed ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			[12] = true,
			[332] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [700]
	[701] = 
	{
		["type"] = "Schnellboot_type_S130",
		["name"] = "Boat Schnellboot type S130",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Boat Schnellboot type S130",
		["attribute"] = 
		{
			["Light armed ships"] = true,
			[292] = true,
			[14] = true,
			["NonArmoredUnits"] = true,
			["Ships"] = true,
			["Armed Ship"] = true,
			["Armed ships"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["All"] = true,
			["Naval"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [701]
	[702] = 
	{
		["type"] = "Seawise_Giant",
		["name"] = "Tanker Seawise Giant",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Tanker Seawise Giant",
		["attribute"] = 
		{
			[15] = true,
			["Unarmed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			["All"] = true,
			["HelicopterCarrier"] = true,
			["Side approach departure"] = true,
			[3] = true,
			[303] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [702]
	[703] = 
	{
		["type"] = "Ship_Tilde_Supply",
		["name"] = "Supply Ship MV Tilde",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Supply Ship MV Tilde",
		["attribute"] = 
		{
			[15] = true,
			["Unarmed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			[5] = true,
			["HelicopterCarrier"] = true,
			["Side approach departure"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [703]
	[704] = 
	{
		["type"] = "SOM",
		["name"] = "SSK 641B Tango",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "SSK 641B Tango",
		["attribute"] = 
		{
			[24] = true,
			["Submarines"] = true,
			["Ships"] = true,
			["Heavy armed ships"] = true,
			[16] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [704]
	[705] = 
	{
		["type"] = "speedboat",
		["name"] = "Boat Armed Hi-speed",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Boat Armed Hi-speed",
		["attribute"] = 
		{
			["Light armed ships"] = true,
			[14] = true,
			["NonArmoredUnits"] = true,
			["Ships"] = true,
			["Armed Ship"] = true,
			["Armed ships"] = true,
			["All"] = true,
			["Naval"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NO_SAM"] = true,
			[3] = true,
			[6] = true,
			["low_reflection_vessel"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [705]
	[706] = 
	{
		["type"] = "Stennis",
		["name"] = "CVN-74 John C. Stennis",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CVN-74 John C. Stennis",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier"] = true,
			["ACLS"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
			["Link4"] = true,
			[3] = true,
			["Heavy armed ships"] = true,
			[12] = true,
			[264] = true,
			["AircraftCarrier With Catapult"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["Arresting Gear"] = true,
			["Naval"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["catapult"] = true,
			["Datalink"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [706]
	[707] = 
	{
		["type"] = "TICONDEROG",
		["name"] = "CG Ticonderoga",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CG Ticonderoga",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			[14] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			[21] = true,
			["Naval"] = true,
			["Cruisers"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [707]
	[708] = 
	{
		["type"] = "Type_052B",
		["name"] = "Type 052B Destroyer",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Type 052B Destroyer",
		["attribute"] = 
		{
			[270] = true,
			["Ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			[3] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			[13] = true,
			["Armed ships"] = true,
			["Armed Air Defence"] = true,
			["Destroyers"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Naval"] = true,
			["HelicopterCarrier"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [708]
	[709] = 
	{
		["type"] = "Type_052C",
		["name"] = "Type 052C Destroyer",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Type 052C Destroyer",
		["attribute"] = 
		{
			["Ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			[272] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			[3] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			[13] = true,
			["Cruisers"] = true,
			["Armed Air Defence"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Naval"] = true,
			["Armed ships"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [709]
	[710] = 
	{
		["type"] = "Type_054A",
		["name"] = "Type 054A Frigate",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Type 054A Frigate",
		["attribute"] = 
		{
			["Ships"] = true,
			[271] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			[3] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			[13] = true,
			["Armed ships"] = true,
			["Armed Air Defence"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Naval"] = true,
			["Frigates"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [710]
	[711] = 
	{
		["type"] = "Type_071",
		["name"] = "Type 071 Amphibious Transport Dock",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Type 071 Amphibious Transport Dock",
		["attribute"] = 
		{
			["Aircraft Carriers"] = true,
			["AircraftCarrier With Tramplin"] = true,
			["AircraftCarrier"] = true,
			["Ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["NO_SAM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["DetectionByAWACS"] = true,
			[3] = true,
			[274] = true,
			[12] = true,
			["Heavy armed ships"] = true,
			["Armed ships"] = true,
			["Armed Air Defence"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["Naval"] = true,
			["Straight_in_approach_type"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [711]
	[712] = 
	{
		["type"] = "Type_093",
		["name"] = "Type 093 Attack Submarine",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Type 093 Attack Submarine",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Submarines"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			["All"] = true,
			[16] = true,
			["Naval"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["Armed Ship"] = true,
			["NO_SAM"] = true,
			[3] = true,
			[273] = true,
			[12] = true,
			["DetectionByAWACS"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [712]
	[713] = 
	{
		["type"] = "Uboat_VIIC",
		["name"] = "U-boat VIIC U-flak",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "U-boat VIIC U-flak",
		["attribute"] = 
		{
			[291] = true,
			["Submarines"] = true,
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			["Armed Air Defence"] = true,
			[16] = true,
			["Armed ships"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["Naval"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["All"] = true,
			[12] = true,
			["Armed Ship"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [713]
	[714] = 
	{
		["type"] = "USS_Arleigh_Burke_IIa",
		["name"] = "DDG Arleigh Burke IIa",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "DDG Arleigh Burke IIa",
		["attribute"] = 
		{
			["Heavy armed ships"] = true,
			["HeavyArmoredUnits"] = true,
			["Armed Air Defence"] = true,
			[14] = true,
			["Armed ships"] = true,
			["Ships"] = true,
			[21] = true,
			["Naval"] = true,
			["Cruisers"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["HelicopterCarrier"] = true,
			["Armed Ship"] = true,
			[3] = true,
			["All"] = true,
			["DetectionByAWACS"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [714]
	[715] = 
	{
		["type"] = "USS_Samuel_Chase",
		["name"] = "LS Samuel Chase",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "LS Samuel Chase",
		["attribute"] = 
		{
			[290] = true,
			[14] = true,
			["HeavyArmoredUnits"] = true,
			["Ships"] = true,
			["Armed Air Defence"] = true,
			["Landing Ships"] = true,
			["Armed ships"] = true,
			["All"] = true,
			["Naval"] = true,
			["NO_SAM"] = true,
			[3] = true,
			["Heavy armed ships"] = true,
			[12] = true,
			["Armed Ship"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [715]
	[716] = 
	{
		["type"] = "VINSON",
		["name"] = "CVN-70 Carl Vinson",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "CVN-70 Carl Vinson",
		["attribute"] = 
		{
			[2] = true,
			["Aircraft Carriers"] = true,
			["AircraftCarrier"] = true,
			["Arresting Gear"] = true,
			["RADAR_BAND1_FOR_ARM"] = true,
			["RADAR_BAND2_FOR_ARM"] = true,
			["AircraftCarrier With Arresting Gear"] = true,
			[3] = true,
			[12] = true,
			["AircraftCarrier With Catapult"] = true,
			["Heavy armed ships"] = true,
			["Ships"] = true,
			["Armed Air Defence"] = true,
			["Armed ships"] = true,
			["Armed Ship"] = true,
			["HeavyArmoredUnits"] = true,
			["All"] = true,
			["catapult"] = true,
			["Naval"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [716]
	[717] = 
	{
		["type"] = "ZWEZDNY",
		["name"] = "Boat Zvezdny type",
		["category"] = "Ship",
		["naval"] = true,
		["description"] = "Boat Zvezdny type",
		["attribute"] = 
		{
			[15] = true,
			["Unarmed ships"] = true,
			["HeavyArmoredUnits"] = true,
			[5] = true,
			["Ships"] = true,
			["Naval"] = true,
			[3] = true,
			["All"] = true,
			["low_reflection_vessel"] = true,
			[12] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [717]
	[718] = 
	{
		["type"] = "AA8",
		["name"] = "Firefighter Vehicle AA-7.2/60",
		["category"] = "Unarmed",
		["description"] = "Firefighter Vehicle AA-7.2/60",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			[295] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [718]
	[719] = 
	{
		["type"] = "ATMZ-5",
		["name"] = "Refueler ATMZ-5",
		["category"] = "Unarmed",
		["description"] = "Refueler ATMZ-5",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			[4] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [719]
	[720] = 
	{
		["type"] = "ATZ-10",
		["name"] = "Refueler ATZ-10",
		["category"] = "Unarmed",
		["description"] = "Refueler ATZ-10",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			[5] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "ATZ-10 Fuel Truck",
		}, -- end of ["aliases"]
	}, -- end of [720]
	[721] = 
	{
		["type"] = "ATZ-5",
		["name"] = "Refueler ATZ-5",
		["category"] = "Unarmed",
		["description"] = "Refueler ATZ-5",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[294] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[2] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [721]
	[722] = 
	{
		["type"] = "ATZ-60_Maz",
		["name"] = "Refueler ATZ-60 Tractor (MAZ-7410)",
		["category"] = "Unarmed",
		["description"] = "Refueler ATZ-60 Tractor (MAZ-7410)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			[310] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [722]
	[723] = 
	{
		["type"] = "B600_drivable",
		["name"] = "M92 B600 drivable",
		["category"] = "Unarmed",
		["description"] = "M92 B600 drivable",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["human_vehicle"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[17] = true,
			["Ground Units Non Airdefence"] = true,
			["Cars"] = true,
			[38] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [723]
	[724] = 
	{
		["type"] = "Bedford_MWD",
		["name"] = "Truck Bedford",
		["category"] = "Unarmed",
		["description"] = "Truck Bedford",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [724]
	[725] = 
	{
		["type"] = "Blitz_36-6700A",
		["name"] = "Truck Opel Blitz",
		["category"] = "Unarmed",
		["description"] = "Truck Opel Blitz",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [725]
	[726] = 
	{
		["type"] = "CCKW_353",
		["name"] = "Truck GMC \"Jimmy\" 6x6",
		["category"] = "Unarmed",
		["description"] = "Truck GMC \"Jimmy\" 6x6",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [726]
	[727] = 
	{
		["type"] = "GAZ-3307",
		["name"] = "Truck GAZ-3307",
		["category"] = "Unarmed",
		["description"] = "Truck GAZ-3307",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			[68] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [727]
	[728] = 
	{
		["type"] = "GAZ-3308",
		["name"] = "Truck GAZ-3308",
		["category"] = "Unarmed",
		["description"] = "Truck GAZ-3308",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			[69] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [728]
	[729] = 
	{
		["type"] = "GAZ-66",
		["name"] = "Truck GAZ-66",
		["category"] = "Unarmed",
		["description"] = "Truck GAZ-66",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			[67] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [729]
	[730] = 
	{
		["type"] = "HEMTT TFFT",
		["name"] = "Firefighter HEMMT TFFT",
		["category"] = "Unarmed",
		["description"] = "Firefighter HEMMT TFFT",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [730]
	[731] = 
	{
		["type"] = "Horch_901_typ_40_kfz_21",
		["name"] = "LUV Horch 901 Staff Car",
		["category"] = "Unarmed",
		["description"] = "LUV Horch 901 Staff Car",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [731]
	[732] = 
	{
		["type"] = "Hummer",
		["name"] = "LUV HMMWV Jeep",
		["category"] = "Unarmed",
		["description"] = "LUV HMMWV Jeep",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["human_vehicle"] = true,
			["Infantry carriers"] = true,
			[25] = true,
			["LightArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[14] = true,
			["Armed vehicles"] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "M1025 HMMWV",
		}, -- end of ["aliases"]
	}, -- end of [732]
	[733] = 
	{
		["type"] = "IKARUS Bus",
		["name"] = "Bus IKARUS-280",
		["category"] = "Unarmed",
		["description"] = "Bus IKARUS-280",
		["vehicle"] = true,
		["attribute"] = 
		{
			[46] = true,
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [733]
	[734] = 
	{
		["type"] = "KAMAZ Truck",
		["name"] = "Truck KAMAZ 43101",
		["category"] = "Unarmed",
		["description"] = "Truck KAMAZ 43101",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[57] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "KAMAZ-43101",
		}, -- end of ["aliases"]
	}, -- end of [734]
	[735] = 
	{
		["type"] = "KrAZ6322",
		["name"] = "Truck KrAZ-6322 6x6",
		["category"] = "Unarmed",
		["description"] = "Truck KrAZ-6322 6x6",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["human_vehicle"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Trucks"] = true,
			[17] = true,
			["Ground Units Non Airdefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			["Vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [735]
	[736] = 
	{
		["type"] = "Kubelwagen_82",
		["name"] = "LUV Kubelwagen Jeep",
		["category"] = "Unarmed",
		["description"] = "LUV Kubelwagen Jeep",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [736]
	[737] = 
	{
		["type"] = "Land_Rover_101_FC",
		["name"] = "Truck Land Rover 101 FC",
		["category"] = "Unarmed",
		["description"] = "Truck Land Rover 101 FC",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [737]
	[738] = 
	{
		["type"] = "Land_Rover_109_S3",
		["name"] = "LUV Land Rover 109",
		["category"] = "Unarmed",
		["description"] = "LUV Land Rover 109",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [738]
	[739] = 
	{
		["type"] = "LARC-V",
		["name"] = "Truck LARC-V",
		["category"] = "Unarmed",
		["description"] = "Truck LARC-V",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[333] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [739]
	[740] = 
	{
		["type"] = "LAZ Bus",
		["name"] = "Bus LAZ-695",
		["category"] = "Unarmed",
		["description"] = "Bus LAZ-695",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			[58] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [740]
	[741] = 
	{
		["type"] = "LiAZ Bus",
		["name"] = "Bus LiAZ-677",
		["category"] = "Unarmed",
		["description"] = "Bus LiAZ-677",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			[58] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [741]
	[742] = 
	{
		["type"] = "M 818",
		["name"] = "Truck M939 Heavy",
		["category"] = "Unarmed",
		["description"] = "Truck M939 Heavy",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["Trucks"] = true,
			[17] = true,
			["Ground Units Non Airdefence"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground vehicles"] = true,
			[6] = true,
			["All"] = true,
			["Datalink"] = true,
			["Ground Units"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "M818",
		}, -- end of ["aliases"]
	}, -- end of [742]
	[743] = 
	{
		["type"] = "M30_CC",
		["name"] = "Ammo M30 Cargo Carrier",
		["category"] = "Unarmed",
		["description"] = "Ammo M30 Cargo Carrier",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [743]
	[744] = 
	{
		["type"] = "M978 HEMTT Tanker",
		["name"] = "Refueler M978 HEMTT",
		["category"] = "Unarmed",
		["description"] = "Refueler M978 HEMTT",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [744]
	[745] = 
	{
		["type"] = "MAZ-6303",
		["name"] = "Truck MAZ-6303",
		["category"] = "Unarmed",
		["description"] = "Truck MAZ-6303",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[70] = true,
			[2] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [745]
	[746] = 
	{
		["type"] = "MJ-1_drivable",
		["name"] = "M92 MJ-1 drivable",
		["category"] = "Unarmed",
		["description"] = "M92 MJ-1 drivable",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["human_vehicle"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[17] = true,
			["Ground Units Non Airdefence"] = true,
			["Cars"] = true,
			[38] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [746]
	[747] = 
	{
		["type"] = "P20_drivable",
		["name"] = "M92 P20 drivable",
		["category"] = "Unarmed",
		["description"] = "M92 P20 drivable",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["human_vehicle"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[17] = true,
			["Ground Units Non Airdefence"] = true,
			["Cars"] = true,
			[38] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [747]
	[748] = 
	{
		["type"] = "Predator GCS",
		["name"] = "MCC Predator UAV CP & GCS",
		["category"] = "Unarmed",
		["description"] = "MCC Predator UAV CP & GCS",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [748]
	[749] = 
	{
		["type"] = "Predator TrojanSpirit",
		["name"] = "MCC-COMM Predator UAV CL",
		["category"] = "Unarmed",
		["description"] = "MCC-COMM Predator UAV CL",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [749]
	[750] = 
	{
		["type"] = "r11_volvo_drivable",
		["name"] = "M92 R11 Volvo drivable",
		["category"] = "Unarmed",
		["description"] = "M92 R11 Volvo drivable",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [750]
	[751] = 
	{
		["type"] = "S_75_ZIL",
		["name"] = "S-75 Tractor (ZIL-131)",
		["category"] = "Unarmed",
		["description"] = "S-75 Tractor (ZIL-131)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[338] = true,
			[2] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [751]
	[752] = 
	{
		["type"] = "Sd_Kfz_2",
		["name"] = "LUV Kettenrad",
		["category"] = "Unarmed",
		["description"] = "LUV Kettenrad",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [752]
	[753] = 
	{
		["type"] = "Sd_Kfz_7",
		["name"] = "Tractor Sd.Kfz.7 Art'y Tractor",
		["category"] = "Unarmed",
		["description"] = "Tractor Sd.Kfz.7 Art'y Tractor",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[26] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [753]
	[754] = 
	{
		["type"] = "SKP-11",
		["name"] = "Truck SKP-11 Mobile ATC",
		["category"] = "Unarmed",
		["description"] = "Truck SKP-11 Mobile ATC",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "SKP-11 Mobile Command Post",
		}, -- end of ["aliases"]
	}, -- end of [754]
	[755] = 
	{
		["type"] = "Suidae",
		["name"] = "Suidae",
		["category"] = "Unarmed",
		["description"] = "Suidae",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			[47] = true,
			["NonAndLightArmoredUnits"] = true,
			["Cars"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [755]
	[756] = 
	{
		["type"] = "tacr2a",
		["name"] = "Firefighter RAF Rescue",
		["category"] = "Unarmed",
		["description"] = "Firefighter RAF Rescue",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			[340] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			["Ground vehicles"] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["Infantry carriers"] = true,
			[25] = true,
			["NonAndLightArmoredUnits"] = true,
			[14] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [756]
	[757] = 
	{
		["type"] = "Tigr_233036",
		["name"] = "LUV Tigr",
		["category"] = "Unarmed",
		["description"] = "LUV Tigr",
		["vehicle"] = true,
		["attribute"] = 
		{
			[2] = true,
			["Vehicles"] = true,
			["Armored vehicles"] = true,
			["AntiAir Armed Vehicles"] = true,
			[17] = true,
			["Ground vehicles"] = true,
			[10] = true,
			["Armed ground units"] = true,
			["APC"] = true,
			["Ground Units Non Airdefence"] = true,
			["human_vehicle"] = true,
			["Infantry carriers"] = true,
			[25] = true,
			["NonAndLightArmoredUnits"] = true,
			["LightArmoredUnits"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Armed vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [757]
	[758] = 
	{
		["type"] = "Trolley bus",
		["name"] = "ZIU-9 Trolley",
		["category"] = "Unarmed",
		["description"] = "ZIU-9 Trolley",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			[49] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [758]
	[759] = 
	{
		["type"] = "TugHarlan_drivable",
		["name"] = "M92 Tug Harlan drivable",
		["category"] = "Unarmed",
		["description"] = "M92 Tug Harlan drivable",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["human_vehicle"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[17] = true,
			["Ground Units Non Airdefence"] = true,
			["Cars"] = true,
			[38] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [759]
	[760] = 
	{
		["type"] = "TZ-22_KrAZ",
		["name"] = "Refueler TZ-22 Tractor (KrAZ-258B1)",
		["category"] = "Unarmed",
		["description"] = "Refueler TZ-22 Tractor (KrAZ-258B1)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[312] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [760]
	[761] = 
	{
		["type"] = "UAZ-469",
		["name"] = "LUV UAZ-469 Jeep",
		["category"] = "Unarmed",
		["description"] = "LUV UAZ-469 Jeep",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["human_vehicle"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			[17] = true,
			["Ground Units Non Airdefence"] = true,
			["Cars"] = true,
			[38] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [761]
	[762] = 
	{
		["type"] = "Ural ATsP-6",
		["name"] = "Firefighter Ural ATsP-6",
		["category"] = "Unarmed",
		["description"] = "Firefighter Ural ATsP-6",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [762]
	[763] = 
	{
		["type"] = "Ural-375",
		["name"] = "Truck Ural-4320",
		["category"] = "Unarmed",
		["description"] = "Truck Ural-4320",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			[40] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [763]
	[764] = 
	{
		["type"] = "Ural-375 PBU",
		["name"] = "Truck Ural-4320 MCC",
		["category"] = "Unarmed",
		["description"] = "Truck Ural-4320 MCC",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			[41] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [764]
	[765] = 
	{
		["type"] = "Ural-4320 APA-5D",
		["name"] = "GPU APA-5D on Ural 4320",
		["category"] = "Unarmed",
		["description"] = "GPU APA-5D on Ural 4320",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "Ural-4320 APA-5D Ground Power Unit",
		}, -- end of ["aliases"]
	}, -- end of [765]
	[766] = 
	{
		["type"] = "Ural-4320-31",
		["name"] = "Truck Ural-4320-31 Arm'd",
		["category"] = "Unarmed",
		["description"] = "Truck Ural-4320-31 Arm'd",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [766]
	[767] = 
	{
		["type"] = "Ural-4320T",
		["name"] = "Truck Ural-4320T",
		["category"] = "Unarmed",
		["description"] = "Truck Ural-4320T",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[75] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [767]
	[768] = 
	{
		["type"] = "VAZ Car",
		["name"] = "Car VAZ-2109",
		["category"] = "Unarmed",
		["description"] = "Car VAZ-2109",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["NonArmoredUnits"] = true,
			[47] = true,
			["NonAndLightArmoredUnits"] = true,
			["Cars"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[17] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [768]
	[769] = 
	{
		["type"] = "Willys_MB",
		["name"] = "Car Willys Jeep",
		["category"] = "Unarmed",
		["description"] = "Car Willys Jeep",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [769]
	[770] = 
	{
		["type"] = "ZiL-131 APA-80",
		["name"] = "GPU APA-80 on ZIL-131",
		["category"] = "Unarmed",
		["description"] = "GPU APA-80 on ZIL-131",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[6] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
			[1] = "ZiL-131 APA-80 Ground Power Unit",
		}, -- end of ["aliases"]
	}, -- end of [770]
	[771] = 
	{
		["type"] = "ZIL-131 KUNG",
		["name"] = "Truck ZIL-131 (C2)",
		["category"] = "Unarmed",
		["description"] = "Truck ZIL-131 (C2)",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			[79] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [771]
	[772] = 
	{
		["type"] = "ZIL-135",
		["name"] = "Truck ZIL-135",
		["category"] = "Unarmed",
		["description"] = "Truck ZIL-135",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			[311] = true,
			["Trucks"] = true,
			[17] = true,
			["Unarmed vehicles"] = true,
			["NonAndLightArmoredUnits"] = true,
			["NonArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["All"] = true,
			["Ground Units"] = true,
			["Ground vehicles"] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [772]
	[773] = 
	{
		["type"] = "ZIL-4331",
		["name"] = "Truck ZIL-4331",
		["category"] = "Unarmed",
		["description"] = "Truck ZIL-4331",
		["vehicle"] = true,
		["attribute"] = 
		{
			[25] = true,
			["Vehicles"] = true,
			[2] = true,
			["Unarmed vehicles"] = true,
			["Trucks"] = true,
			[17] = true,
			["NonArmoredUnits"] = true,
			["NonAndLightArmoredUnits"] = true,
			["Ground Units Non Airdefence"] = true,
			["Ground vehicles"] = true,
			["All"] = true,
			["Ground Units"] = true,
			[71] = true,
		}, -- end of ["attribute"]
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [773]
	[774] = 
	{
		["type"] = ".Ammunition depot",
		["name"] = "Ammunition depot",
		["category"] = "Warehouse",
		["description"] = "Ammunition depot",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [774]
	[775] = 
	{
		["type"] = "Tank",
		["name"] = "Tank 1",
		["category"] = "Warehouse",
		["description"] = "Tank 1",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [775]
	[776] = 
	{
		["type"] = "Tank 2",
		["name"] = "Tank 2",
		["category"] = "Warehouse",
		["description"] = "Tank 2",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [776]
	[777] = 
	{
		["type"] = "Tank 3",
		["name"] = "Tank 3",
		["category"] = "Warehouse",
		["description"] = "Tank 3",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [777]
	[778] = 
	{
		["type"] = "Warehouse",
		["name"] = "Warehouse",
		["category"] = "Warehouse",
		["description"] = "Warehouse",
		["aliases"] = 
		{
		}, -- end of ["aliases"]
	}, -- end of [778]
} -- end of units


------------------ END script dcsUnits.lua  ------------------


------------------ START script veafCacheManager.lua  ------------------

------------------------------------------------------------------
-- VEAF Cache Manager
-- By Zip (2024-25)
--
-- Features:
-- ---------
-- * Manage cached data with fixed or flexible lifespan
------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafCacheManager = {}

--- Identifier. All output in the log will start with this.
veafCacheManager.Id = "CACHE - "

--- Version.
veafCacheManager.Version = "0.0.1"

-- trace level, specific to this module
veafCacheManager.LogLevel = "trace"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veaf.loggers.new(veafCacheManager.Id, veafCacheManager.LogLevel)

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafCache class
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafCache = {}

VeafCache.DEFAULT_TIME_TO_LIVE = 1 -- 1 second
VeafCache.LIVE_FOREVER = -1 -- this is the TTL for a computed once, cached forever data

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- CTOR

function VeafCache.init(object)
  -- technical name (VeafCache instance name)
  object.name = nil
  -- cache data
  object.cache = {}
  -- default time to live
  object.defaultTTL = VeafCache.DEFAULT_TIME_TO_LIVE
end

function VeafCache:new(objectToCopy)
-- LOGGING DISABLED WHEN COMPILING("VeafCache:new()")
  local objectToCreate = objectToCopy or {} -- create object if user does not provide one
  setmetatable(objectToCreate, self)
  self.__index = self

  -- init the new object
  VeafCache.init(objectToCreate)

  return objectToCreate
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- PROPERTIES

-- technical name (VeafCache instance name)
function VeafCache:setName(value)
-- LOGGING DISABLED WHEN COMPILING("VeafCache[]:setName(%s)", veaf.p(value))
  self.name = value
end

-- technical name (VeafCache instance name)
function VeafCache:getName()
  return self.name or self.description
end

-- default time to live
function VeafCache:setDefaultTimeToLive(value)
  self.defaultTTL = value
-- LOGGING DISABLED WHEN COMPILING("VeafCache[%s]:setDefaultTimeToLive(%s)", veaf.p(self:getName()), veaf.p(value))
  return self
end

-- default time to live
function VeafCache:getDefaultTimeToLive()
  return self.defaultTTL
end


-- remove cached data
function VeafCache:delCachedData(key)
  if self.cache then
    self.cache[key] = nil
  end
end

-- set cached data
function VeafCache:setCachedData(key, value, timetolive)
  local cachedData = nil
  if self.cache then
    local _endoflife = timer.getTime() + (timetolive or self:getDefaultTimeToLive())
    if timetolive == VeafCache.LIVE_FOREVER then
      _endoflife = VeafCache.LIVE_FOREVER
    end
    cachedData = {
      data = value,
      endoflife = _endoflife
    }
    self.cache[key] = cachedData
  end
  return cachedData
end

-- get cached data
function VeafCache:getCachedData(key)
  if self.cache then
    local cachedData = self.cache[key]
    if cachedData and cachedData.endoflife < timer.getTime() then
      return cachedData
    end
  end
  return nil
end

------------------ END script veafCacheManager.lua  ------------------


------------------ START script veafEventHandler.lua  ------------------

------------------------------------------------------------------
-- VEAF Event handler for DCS World
-- By Zip (2023)
--
-- Features:
-- ---------
-- * handles DCS events
-- * can be plugged to objects that react to events
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafEventHandler = {}

--- Identifier. All output in the log will start with this.
veafEventHandler.Id = "EVENTS - "

--- Version.
veafEventHandler.Version = "1.2.1"

-- trace level, specific to this module
--veafEventHandler.LogLevel = "trace"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veaf.loggers.new(veafEventHandler.Id, veafEventHandler.LogLevel)

function veafEventHandler.completeUnit(unit)
  if unit ~= nil and unit.getName then
    local unitName = unit:getName()
    return veafEventHandler.completeUnitFromName(unitName)
  else
    return nil
  end
end

function veafEventHandler.completeUnitFromName(unitName)
-- LOGGING DISABLED WHEN COMPILING("veafEventHandler.completeUnitFromName(unitName=%s)", veaf.p(unitName))
  if unitName ~= nil then
    local unitType = nil
    local unitLifePercent = nil
    local unit = Unit.getByName(unitName)
    if unit and unit.getTypeName then
      unitType = unit:getTypeName()
      local unitLife = unit:getLife()
      local unitLife0 = 0
      if unit.getLife0 then -- statics have no life0
        unitLife0 = unit:getLife0()
      end
      unitLifePercent = unitLife
      if unitLife0 > 0 then
        unitLifePercent = 100 * unitLife / unitLife0
      end
    end
    local unitPilotName = nil
    local unitPilotUcid = nil
    local unitPilot = veafRemote.getRemoteUserFromUnit(unitName)
    if unitPilot then
-- LOGGING DISABLED WHEN COMPILING("unitPilot=%s", veaf.p(unitPilot))
      unitPilotName = unitPilot.name
      unitPilotUcid = unitPilot.ucid
    end
    return {
      unitName = unitName,
      unitType = unitType,
      unitPilotName = unitPilotName,
      unitPilotUcid = unitPilotUcid,
      unitLifePercent = unitLifePercent
    }
  else
    return nil
  end
end

--- name self explanatory
--- events the list of event names or ids that your callback is interested into
--- callback the function to be called (we'll pass it an event; the definition of "event" can be found just below)
function veafEventHandler.addCallback(name, events, callback)
-- LOGGING DISABLED WHEN COMPILING("veafEventHandler.addCallback(name=[%s])", veaf.p(name))

  if name == nil then
    veaf.loggers.get(veafEventHandler.Id):error("veafEventHandler.addCallback: parameter `name` is mandatory")
    return false
  end
  if callback == nil then
    veaf.loggers.get(veafEventHandler.Id):error("veafEventHandler.addCallback: parameter `callback` is mandatory")
    return false
  end
  if events ~= nil then
    -- validate all event types
    for _, eventNameOrId in pairs(events) do
      if not(veafEventHandler.checkEventKnown(eventNameOrId)) then
        return false
      end
    end
  end
  table.insert(veafEventHandler.callbacks, {name=name, events=events, call=callback})
  return true
end

veafEventHandler.callbacks = {}

local function transformEvent(event)
  local _event = {
    type = veafEventHandler.knownEvents[event.id],
    time = event.time,
    idx = event.idx,
    coordinates = event.pos,
    text = event.text,
    coalition = event.coalition,
    groupId = event.groupID,
    place = veafEventHandler.completeUnit(event.place),
    birthPlace = event.subPlace,
    initiator = veafEventHandler.completeUnit(event.initiator),
    target = veafEventHandler.completeUnit(event.target),
    weapon = event.weapon,
    weaponName = event.weapon_name,
    comment = event.comment
  }
  return _event
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Main event handler (used for PLAYER ENTER UNIT events)
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Event handler.
veafEventHandler.eventHandler = {}

veafEventHandler.knownEventsNames = {
  [00] = "S_EVENT_INVALID",
  [01] = "S_EVENT_SHOT",
  [02] = "S_EVENT_HIT",
  [03] = "S_EVENT_TAKEOFF",
  [04] = "S_EVENT_LAND",
  [05] = "S_EVENT_CRASH",
  [06] = "S_EVENT_EJECTION",
  [07] = "S_EVENT_REFUELING",
  [08] = "S_EVENT_DEAD",
  [09] = "S_EVENT_PILOT_DEAD",
  [10] = "S_EVENT_BASE_CAPTURED",
  [11] = "S_EVENT_MISSION_START",
  [12] = "S_EVENT_MISSION_END",
  [13] = "S_EVENT_TOOK_CONTROL",
  [14] = "S_EVENT_REFUELING_STOP",
  [15] = "S_EVENT_BIRTH",
  [16] = "S_EVENT_HUMAN_FAILURE",
  [17] = "S_EVENT_DETAILED_FAILURE",
  [18] = "S_EVENT_ENGINE_STARTUP",
  [19] = "S_EVENT_ENGINE_SHUTDOWN",
  [20] = "S_EVENT_PLAYER_ENTER_UNIT",
  [21] = "S_EVENT_PLAYER_LEAVE_UNIT",
  [22] = "S_EVENT_PLAYER_COMMENT",
  [23] = "S_EVENT_SHOOTING_START",
  [24] = "S_EVENT_SHOOTING_END",
  [25] = "S_EVENT_MARK_ADDED",
  [26] = "S_EVENT_MARK_CHANGE",
  [27] = "S_EVENT_MARK_REMOVED",
  [28] = "S_EVENT_KILL",
  [29] = "S_EVENT_SCORE",
  [30] = "S_EVENT_UNIT_LOST",
  [31] = "S_EVENT_LANDING_AFTER_EJECTION",
  [32] = "S_EVENT_PARATROOPER_LENDING",
  [33] = "S_EVENT_DISCARD_CHAIR_AFTER_EJECTION",
  [34] = "S_EVENT_WEAPON_ADD",
  [35] = "S_EVENT_TRIGGER_ZONE",
  [36] = "S_EVENT_LANDING_QUALITY_MARK",
  [37] = "S_EVENT_BDA",
  [38] = "S_EVENT_AI_ABORT_MISSION",
  [39] = "S_EVENT_DAYNIGHT",
  [40] = "S_EVENT_FLIGHT_TIME",
  [41] = "S_EVENT_PLAYER_SELF_KILL_PILOT",
  [42] = "S_EVENT_PLAYER_CAPTURE_AIRFIELD",
  [43] = "S_EVENT_EMERGENCY_LANDING",
  [44] = "S_EVENT_UNIT_CREATE_TASK",
  [45] = "S_EVENT_UNIT_DELETE_TASK",
  [46] = "S_EVENT_SIMULATION_START",
  [47] = "S_EVENT_WEAPON_REARM",
  [48] = "S_EVENT_WEAPON_DROP",
  [49] = "S_EVENT_UNIT_TASK_TIMEOUT",
  [50] = "S_EVENT_UNIT_TASK_STAGE",
  [51] = "S_EVENT_MAX",
  [52] = "[UNKNOWN]", -- ???
  [53] = "[UNKNOWN]", -- ???
  [54] = "S_EVENT_RUNWAY_TAKEOFF", -- since 2.9.6
  [55] = "S_EVENT_RUNWAY_TOUCH", -- since 2.9.6
}

veafEventHandler.knownEvents = {} -- will be set at initialisation
veafEventHandler.unknownEvents = {} -- will be used to remember already signaled unknown events

function veafEventHandler.checkEventKnown(eventNameOrId, warnOnly)
  if veafEventHandler.knownEvents[eventNameOrId] ~= nil then
    return true
  elseif veafEventHandler.unknownEvents[eventNameOrId] == nil then
    veafEventHandler.unknownEvents[eventNameOrId] = true
    local message = string.format("Event is not recognized by the VEAF Recorder: [%s]", veaf.p(eventNameOrId))
    if warnOnly then
      veaf.loggers.get(veafEventHandler.Id):warn(message)
    else
      veaf.loggers.get(veafEventHandler.Id):error(message)
    end
  end
  return false
end

function veafEventHandler.setEventEnabled(eventNameOrId, enabled)
  if veafEventHandler.checkEventKnown(eventNameOrId) then
    veafEventHandler.knownEvents[eventNameOrId].enabled = enabled
  end
end

function veafEventHandler.isEventEnabled(eventNameOrId)
  if veafEventHandler.checkEventKnown(eventNameOrId) then
    return veafEventHandler.knownEvents[eventNameOrId].enabled
  end
end

--- Handle world events.
function veafEventHandler.eventHandler:onEvent(event)

  if event == nil then
    veaf.loggers.get(veafEventHandler.Id):error("Event handler was called with a nil event!")
    return
  end

  -- check that we know the event
  if not(veafEventHandler.checkEventKnown(event.id, true)) then
    return
  end

  -- skip disabled events
  if not(veafEventHandler.isEventEnabled(event.id)) then
      return true
  end

  local _event = transformEvent(event)

  -- Debug output.
  if veaf.loggers.get(veafEventHandler.Id):wouldLogTrace() then
-- LOGGING DISABLED WHEN COMPILING("event = %s", veaf.p(event))
-- LOGGING DISABLED WHEN COMPILING("_event = %s", veaf.p(_event))
  end

  -- process event
  for _, callback in pairs(veafEventHandler.callbacks) do
    local callIt = false
    if callback.events == nil then
      callIt = true
    else
      for _, eventNameOrId in pairs(callback.events) do
        if _event.type.id == eventNameOrId or _event.type.name == eventNameOrId then
          callIt = true
          break
        end
      end
    end
    if callIt then
-- LOGGING DISABLED WHEN COMPILING("calling callback %s", veaf.p(callback.name))
      callback.call(_event)
    end
  end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Other functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafEventHandler.initialize()
-- LOGGING DISABLED WHEN COMPILING("veafEventHandler.initialize()")

  -- prepare the events maps
  for eventId, eventName in pairs(veafEventHandler.knownEventsNames) do
    local event = {
      name = eventName,
      id = eventId,
      enabled = true --false
    }
    veafEventHandler.knownEvents[eventName] = event
    veafEventHandler.knownEvents[eventId] = event
  end

  -- Add event handler.
  world.addEventHandler(veafEventHandler.eventHandler)
end

veafEventHandler.initialize()

------------------ END script veafEventHandler.lua  ------------------


------------------ START script veafMarkers.lua  ------------------

------------------------------------------------------------------
-- VEAF markers function library for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Listen to marker events and execute event handlers.
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafMarkers = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafMarkers.Id = "MARKERS"

--- Version.
veafMarkers.Version = "1.1.0"

--- DCS bug regarding wrong marker vector components was fixed. If so, set to true!
veafMarkers.DCSbugfixed = true

-- trace level, specific to this module
--veafMarkers.LogLevel = "trace"

veaf.loggers.new(veafMarkers.Id, veafMarkers.LogLevel)

veafMarkers.MarkerAdd = 1
veafMarkers.MarkerChange = 2
veafMarkers.MarkerRemove = 3

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafMarkers.eventHandlerId = 0
veafMarkers.onEventMarkChangeEventHandlers = {}
veafMarkers.onEventMarkAddEventHandlers = {}
veafMarkers.onEventMarkRemoveEventHandlers = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Event handler.
veafMarkers.eventHandler = {}

--- Handle world events.
function veafMarkers.eventHandler:onEvent(Event)
    -- Only interested in S_EVENT_MARK_*
    if Event == nil or Event.idx == nil then
        return true
    end

    -- Debug output.
    if Event.id == world.event.S_EVENT_MARK_ADDED then
-- LOGGING DISABLED WHEN COMPILING("S_EVENT_MARK_ADDED")
    elseif Event.id == world.event.S_EVENT_MARK_CHANGE then
-- LOGGING DISABLED WHEN COMPILING("S_EVENT_MARK_CHANGE")
    elseif Event.id == world.event.S_EVENT_MARK_REMOVED then
-- LOGGING DISABLED WHEN COMPILING("S_EVENT_MARK_REMOVED")
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("Event id        = %s", tostring(Event.id)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event time      = %s", tostring(Event.time)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event idx       = %s", tostring(Event.idx)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event coalition = %s", tostring(Event.coalition)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event group id  = %s", tostring(Event.groupID)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event pos X     = %s", tostring(Event.pos.x)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event pos Y     = %s", tostring(Event.pos.y)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event pos Z     = %s", tostring(Event.pos.z)))
    if Event.initiator ~= nil then
        local _unitname = Event.initiator:getName()
-- LOGGING DISABLED WHEN COMPILING(string.format("Event ini unit  = %s", tostring(_unitname)))
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("Event text      = \n%s", tostring(Event.text)))

    -- Call event function when a marker has changed, i.e. text was entered or changed.
    if Event.id == world.event.S_EVENT_MARK_CHANGE then
        veafMarkers.onEvent(Event, veafMarkers.onEventMarkChangeEventHandlers)
    elseif Event.id == world.event.S_EVENT_MARK_ADDED then
        veafMarkers.onEvent(Event, veafMarkers.onEventMarkAddEventHandlers)
    elseif Event.id == world.event.S_EVENT_MARK_REMOVED then
        veafMarkers.onEvent(Event, veafMarkers.onEventMarkRemoveEventHandlers)
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a marker event occurs.
function veafMarkers.onEvent(event, eventHandlersTable)
    local vec3 = nil

    -- Check if marker has a text and the veafMarkers.keyphrase keyphrase.
    if event.text ~= nil then
        -- browse all the event handlers registered for this type of event
        for i = 1, #eventHandlersTable do 

            -- compute the event position if not already done
            if vec3 == nil then
                if veafMarkers.DCSbugfixed then
                    vec3 = {x = event.pos.x, y = event.pos.y, z = event.pos.z}
                else
                    -- Convert (wrong x-->z, z-->x) vec3
                    vec3 = {x = event.pos.z, y = event.pos.y, z = event.pos.x}
                end

                -- By default, alt of mark point is always 5 m! Adjust for the correct ASL height.
                vec3.y = veaf.getLandHeight(vec3)
            end

            -- call the event handler
            local eventHandler = eventHandlersTable[i]
-- LOGGING DISABLED WHEN COMPILING("Calling eventHandler #" .. eventHandler.id)
            local err, errmsg = pcall(eventHandler.f, vec3, event)
            if not err then
                veaf.loggers.get(veafMarkers.Id):error('Error in event handler #' .. eventHandler.id .. ' : '.. errmsg)
            end
-- LOGGING DISABLED WHEN COMPILING("Returning after eventHandler #" .. eventHandler.id)
        end
    end    
end

--- Register an event handler
-- @tparam function f event handling function (the first parameter is always the event position, and the second is the event)
-- @treturn number event handler id.
function veafMarkers.registerEventHandler(eventType, eventHandler)
    --verify correct types
    assert(type(eventHandler) == 'function', 'variable 1, expected function, got ' .. type(eventHandler))
    if not vars then
        vars = {}
    end
    veafMarkers.eventHandlerId = veafMarkers.eventHandlerId + 1
    if eventType == veafMarkers.MarkerAdd then
        table.insert(veafMarkers.onEventMarkAddEventHandlers, {f = eventHandler, id = veafMarkers.eventHandlerId})
    elseif eventType == veafMarkers.MarkerChange then
        table.insert(veafMarkers.onEventMarkChangeEventHandlers, {f = eventHandler, id = veafMarkers.eventHandlerId})
    elseif eventType == veafMarkers.MarkerRemove then
        table.insert(veafMarkers.onEventMarkRemoveEventHandlers, {f = eventHandler, id = veafMarkers.eventHandlerId})
    else
        -- wrong event type
    end
    return veafMarkers.eventHandlerId
end

function veafMarkers.removeItemFromList(list, id)
    local i = 1
    while i <= #list do
        if list[i].id == id then
            table.remove(list, i)
            return true
        else
            i = i + 1
        end
    end
    return false
end

--- Removes an event handler
-- @tparam number id event handler id
-- @treturn boolean true if event handler was successfully removed, false otherwise.
function veafMarkers.unregisterEventHandler(id)
    local result = veafMarkers.removeItemFromList(veafMarkers.onEventMarkAddEventHandlers, id)
    if not(result) then
        result = veafMarkers.removeItemFromList(veafMarkers.onEventMarkChangeEventHandlers, id)
    end
    if not(result) then
        result = veafMarkers.removeItemFromList(veafMarkers.onEventMarkRemoveEventHandlers, id)
    end
    return result
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Add event handler.
world.addEventHandler(veafMarkers.eventHandler)

--- Enable/Disable error boxes displayed on screen.
env.setErrorMessageBoxEnabled(false)

veaf.loggers.get(veafMarkers.Id):info(string.format("Loading version %s", veafMarkers.Version))

------------------ END script veafMarkers.lua  ------------------


------------------ START script veafInterpreter.lua  ------------------

------------------------------------------------------------------
-- VEAF interpreter for DCS World
-- By Zip (2019)
--
-- Features:
-- ---------
-- * interprets a command and a position, and executes one of the VEAF script commands as if it had been requested in a map marker
-- * Possibilities : 
-- *    - at mission start, have pre-placed units trigger specific commands
-- *    - serve as a base for activating commands in Combat Zones (see veafCombatZone.lua)
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

--- veafInterpreter Table.
veafInterpreter = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafInterpreter.Id = "INTERPRETER"

--- Version.
veafInterpreter.Version = "1.6.1"

-- trace level, specific to this module
--veafInterpreter.LogLevel = "trace"

veaf.loggers.new(veafInterpreter.Id, veafInterpreter.LogLevel)

--- Key phrase to look for in the unit name which triggers the interpreter.
veafInterpreter.Starter = "#veafInterpreter%[\""
veafInterpreter.Trailer = "\"%]"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- delay before the mission editor unit names are interpreted
veafInterpreter.DelayForStartup = 1

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the text
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafInterpreter.interpret(text)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafInterpreter.interpret([%s])",text))
    local result = nil
    local p1, p2 = text:find(veafInterpreter.Starter)
    if p2 then
      -- starter has been found
      text = text:sub(p2 + 1)
      p1, p2 = text:find(veafInterpreter.Trailer)
      if p1 then
        -- trailer has been found
        result = text:sub(1, p1 - 1)
      end
    end
    return result
end

function veafInterpreter.execute(command, position, coalition, route, spawnedGroups)
    local function logDebug(message)
-- LOGGING DISABLED WHEN COMPILING(message)
        return true
    end

    if command == nil then return end
    if position == nil then return end
-- LOGGING DISABLED WHEN COMPILING(string.format("veafInterpreter.execute([%s],[%s])", command, veaf.vecToString(position)))

    local commandExecuted = false
    spawnedGroups = spawnedGroups or {}

    if logDebug("checking in veafShortcuts") and veafShortcuts.executeCommand(position, command, coalition, nil, true, spawnedGroups, route) then
        return true
    elseif logDebug("checking in veafSpawn") and veafSpawn.executeCommand(position, command, coalition, nil, true, spawnedGroups, nil, nil, route, true) then
        return true
    elseif logDebug("checking in veafNamedPoints") and veafNamedPoints.executeCommand(position, {text=command, coalition=-1}, true) then
        return true
    elseif logDebug("checking in veafCasMission") and veafCasMission.executeCommand(position, command, coalition, true) then
        return true
    elseif logDebug("checking in veafSecurity") and veafSecurity.executeCommand(position, command, true) then
        return true
    elseif logDebug("checking in veafMove") and veafMove.executeCommand(position, command, true) then
        return true
    elseif logDebug("checking in veafRadio") and veafRadio.executeCommand(position, command, coalition, true) then
        return true
    elseif logDebug("checking in veafRemote") and veafRemote.executeCommand(position, command) then
        return true
    else
        return false
    end
end

function veafInterpreter.executeCommandOnUnit(unitName, command)
    if command then
        -- found an interpretable command
-- LOGGING DISABLED WHEN COMPILING(string.format("found an interpretable command : [%s]", command))
        local unit = Unit.getByName(unitName)
        if unit then
            local position = unit:getPosition().p
-- LOGGING DISABLED WHEN COMPILING(string.format("found the unit at : [%s]", veaf.vecToString(position)))
            local groupName = unit:getGroup():getName()
-- LOGGING DISABLED WHEN COMPILING(string.format("in [%s]", groupName))
            local route = mist.getGroupRoute(groupName, 'task')
-- LOGGING DISABLED WHEN COMPILING(string.format("route = [%s]", veaf.p(route)))
            if veafInterpreter.execute(command, position, unit:getCoalition(), route, nil) then
                unit:getGroup():destroy()
            end
        else
            -- it may be a static instead of a unit
            local static = StaticObject.getByName(unitName)
            if static then
                local position = static:getPosition().p
-- LOGGING DISABLED WHEN COMPILING("found the static at : [%s]", veaf.vecToString(position))
                if veafInterpreter.execute(command, position, static:getCoalition(), nil, nil) then
                    static:destroy()
                end
            end
        end
    end
end

function veafInterpreter.processObject(unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafInterpreter.processObject([%s])", unitName))
    local command = veafInterpreter.interpret(unitName)
    veafInterpreter.executeCommandOnUnit(unitName, command)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafInterpreter.initialize()
    mist.scheduleFunction(veafInterpreter._initialize, {}, timer.getTime()+veafInterpreter.DelayForStartup)
end


function veafInterpreter._initialize()
    -- the following code is liberally adapted from MiST (thanks Grimes !)
    local l_units = mist.DBs.units	--local reference for faster execution
    for coa, coa_tbl in pairs(l_units) do
        for country, country_table in pairs(coa_tbl) do
            for unit_type, unit_type_tbl in pairs(country_table) do
                if type(unit_type_tbl) == 'table' then
                    for group_ind, group_tbl in pairs(unit_type_tbl) do
                        if type(group_tbl) == 'table' then
                            for unit_ind, mist_unit in pairs(group_tbl.units) do
                                local unitName = mist_unit.unitName
-- LOGGING DISABLED WHEN COMPILING(string.format("initialize - checking unit [%s]", unitName))
                                veafInterpreter.processObject(unitName)
                            end
                        end
                    end
                end
            end
        end
    end
end

veaf.loggers.get(veafInterpreter.Id):info(string.format("Loading version %s", veafInterpreter.Version))



------------------ END script veafInterpreter.lua  ------------------


------------------ START script veafRadio.lua  ------------------

------------------------------------------------------------------
-- VEAF radio menu script library for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Manage the VEAF radio menus in the F10 - Other menu
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

--- veafRadio Table.
veafRadio = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafRadio.Id = "RADIO"

--- Version.
veafRadio.Version = "1.13.1"

-- trace level, specific to this module
--veafRadio.LogLevel = "trace"

veaf.loggers.new(veafRadio.Id, veafRadio.LogLevel)

veafRadio.RadioMenuName = "VEAF"

-- constants used to determine how the radio menu is set up
veafRadio.USAGE_ForAll   = 0
veafRadio.USAGE_ForGroup = 1
veafRadio.USAGE_ForUnit  = 2

-- maximum size for radio menu
veafRadio.MAXIMUM_SIZE = 99999 -- 4200

-- delay for the actual refresh
veafRadio.refreshRadioMenu_DELAY = 1

--- Key phrase to look for in the mark text which triggers the command.
veafRadio.Keyphrase = "_radio"

--- number of seconds between beacons checks
veafRadio.BEACONS_SCHEDULE = 5

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------
veafRadio.skipHelpMenus = true

--- Humans Units (associative array unitName => unit)
veafRadio.humanUnits = {}
veafRadio.humanGroups = {}

--- This structure contains all the radio menus
veafRadio.radioMenu = {}
veafRadio.radioMenu.title = veafRadio.RadioMenuName
veafRadio.radioMenu.dcsRadioMenu = nil
veafRadio.radioMenu.subMenus = {}
veafRadio.radioMenu.commands = {}

--- Counts the size of the radio menu
veafRadio.radioMenuSize = {}

veafRadio.beacons = {}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafRadio.onEventMarkChange(eventPos, event)
  if veafRadio.executeCommand(eventPos, event.text, event.coalition) then

      -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
      trigger.action.removeMark(event.idx)
  end
end

function veafRadio.executeCommand(eventPos, eventText, eventCoalition, bypassSecurity)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRadio.executeCommand(%s)", eventText))

  -- Check if marker has a text and the veafRadio.keyphrase keyphrase.
  if eventText ~= nil and eventText:lower():find(veafRadio.Keyphrase) then

      -- Analyse the mark point text and extract the keywords.
      local options = veafRadio.markTextAnalysis(eventText)

      if options then
-- LOGGING DISABLED WHEN COMPILING(string.format("options.path=%s",veaf.p(options.path)))
          -- Check options commands
          if options.transmit and options.message and options.frequencies and options.name then
              -- transmit a radio message via SRS
              veafRadio.transmitMessage(options.message, options.frequencies, options.modulations, options.name, eventCoalition, eventPos, options.quiet)
              return true
          elseif options.playmp3 and options.path and options.frequencies and options.name then
            -- play a MP3 file via SRS
            veafRadio.playToRadio(options.path, options.frequencies, options.modulations, options.name, eventCoalition, eventPos, options.quiet)
            return true
          end
      else
          -- None of the keywords matched.
          return false
      end
  end
  return false
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the mark text and extract keywords.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Extract keywords from mark text.
function veafRadio.markTextAnalysis(text)

-- LOGGING DISABLED WHEN COMPILING(string.format("markTextAnalysis(%s)", text))

  -- Option parameters extracted from the mark text.
  local switch = {}
  switch.transmit = false
  switch.playmp3 = false

  switch.message = nil
  switch.frequencies = "251"
  switch.modulations = "AM"
  switch.name = "SRS"
  switch.quiet = false
  switch.path = nil

  -- Check for correct keywords.
  if text:lower():find(veafRadio.Keyphrase .. " transmit") then
    switch.transmit = true
  elseif text:lower():find(veafRadio.Keyphrase .. " play") then
    switch.playmp3 = true
  else
      return nil
  end

  -- keywords are split by ","
  local keywords = veaf.split(text, ",")

  for _, keyphrase in pairs(keywords) do
    -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
    local str = veaf.breakString(veaf.trim(keyphrase), " ")
    local key = str[1]
    local val = str[2]

    if key:lower() == "message" then
      -- Set message.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword message = %s", tostring(val)))
      switch.message = val
    elseif key:lower() == "path" then
      -- Set path.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword path = %s", tostring(val)))
      switch.path = val
    elseif key:lower() == "name" then
      -- Set name.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword name = %s", tostring(val)))
      switch.name = val
    elseif key:lower() == "quiet" then
      -- Set quiet.
-- LOGGING DISABLED WHEN COMPILING("Keyword quiet found")
      switch.quiet = true
    elseif key:lower() == "freq" or key:lower() == "freqs" or key:lower() == "frequency" or key:lower() == "frequencies" then
      -- Set frequencies.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword frequencies = %s", tostring(val)))
      switch.frequencies = val
    elseif key:lower() == "mod" or key:lower() == "mods" or key:lower() == "modulation" or key:lower() == "modulations" then
      -- Set modulations.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword modulations = %s", tostring(val)))
      switch.modulations = val
    elseif key:lower() == "path" then
      -- Set path.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword path = %s", tostring(val)))
      switch.path = val
    end

  end

  return switch
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Main event handler (used for PLAYER ENTER UNIT events)
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Event handler.
veafRadio.eventHandler = {}

--- Handle world events.
function veafRadio.eventHandler:onEvent(Event)
    local EVENTS = {
  [0] =  "S_EVENT_INVALID",
  [1] =  "S_EVENT_SHOT",
  [2] =  "S_EVENT_HIT",
  [3] =  "S_EVENT_TAKEOFF",
  [4] =  "S_EVENT_LAND",
  [5] =  "S_EVENT_CRASH",
  [6] =  "S_EVENT_EJECTION",
  [7] =  "S_EVENT_REFUELING",
  [8] =  "S_EVENT_DEAD",
  [9] =  "S_EVENT_PILOT_DEAD",
  [10] =  "S_EVENT_BASE_CAPTURED",
  [11] =  "S_EVENT_MISSION_START",
  [12] =  "S_EVENT_MISSION_END",
  [13] =  "S_EVENT_TOOK_CONTROL",
  [14] =  "S_EVENT_REFUELING_STOP",
  [15] =  "S_EVENT_BIRTH",
  [16] =  "S_EVENT_HUMAN_FAILURE",
  [17] =  "S_EVENT_DETAILED_FAILURE",
  [18] =  "S_EVENT_ENGINE_STARTUP",
  [19] =  "S_EVENT_ENGINE_SHUTDOWN",
  [20] =  "S_EVENT_PLAYER_ENTER_UNIT",
  [21] =  "S_EVENT_PLAYER_LEAVE_UNIT",
  [22] =  "S_EVENT_PLAYER_COMMENT",
  [23] =  "S_EVENT_SHOOTING_START",
  [24] =  "S_EVENT_SHOOTING_END",
  [25] =  "S_EVENT_MARK_ADDED",
  [26] =  "S_EVENT_MARK_CHANGE",
  [27] =  "S_EVENT_MARK_REMOVED",
  [28] =  "S_EVENT_KILL",
  [29] =  "S_EVENT_SCORE",
  [30] =  "S_EVENT_UNIT_LOST",
  [31] =  "S_EVENT_LANDING_AFTER_EJECTION"}

  local enabledEvents = {
  ["S_EVENT_INVALID"] = false,
  ["S_EVENT_SHOT"] = false,
  ["S_EVENT_HIT"] = false,
  ["S_EVENT_TAKEOFF"] = false,
  ["S_EVENT_LAND"] = false,
  ["S_EVENT_CRASH"] = false,
  ["S_EVENT_EJECTION"] = false,
  ["S_EVENT_REFUELING"] = false,
  ["S_EVENT_DEAD"] = true,
  ["S_EVENT_PILOT_DEAD"] = true,
  ["S_EVENT_BASE_CAPTURED"] = false,
  ["S_EVENT_MISSION_START"] = false,
  ["S_EVENT_MISSION_END"] = false,
  ["S_EVENT_TOOK_CONTROL"] = true,
  ["S_EVENT_REFUELING_STOP"] = false,
  ["S_EVENT_BIRTH"] = true,
  ["S_EVENT_HUMAN_FAILURE"] = false,
  ["S_EVENT_DETAILED_FAILURE"] = false,
  ["S_EVENT_ENGINE_STARTUP"] = false,
  ["S_EVENT_ENGINE_SHUTDOWN"] = false,
  ["S_EVENT_PLAYER_ENTER_UNIT"] = true,
  ["S_EVENT_PLAYER_LEAVE_UNIT"] = true,
  ["S_EVENT_PLAYER_COMMENT"] = true,
  ["S_EVENT_SHOOTING_START"] = false,
  ["S_EVENT_SHOOTING_END"] = false,
  ["S_EVENT_MARK_ADDED"] = false,
  ["S_EVENT_MARK_CHANGE"] = false,
  ["S_EVENT_MARK_REMOVED"] = false,
  ["S_EVENT_KILL"] = false,
  ["S_EVENT_SCORE"] = false,
  ["S_EVENT_UNIT_LOST"] = true,
  ["S_EVENT_LANDING_AFTER_EJECTION"] = false
  }

  -- Only interested in S_EVENT_BIRTH (S_EVENT_PLAYER_ENTER_UNIT is not fired in MP)
  if Event == nil or not(enabledEvents[EVENTS[Event.id]]) then
      return true
  end

  -- Debug output.
  local _unitname = ""
-- LOGGING DISABLED WHEN COMPILING(string.format("got event %s", veaf.p(EVENTS[Event.id])))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event id        = %s", veaf.p(Event.id)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event time      = %s", veaf.p(Event.time)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event idx       = %s", veaf.p(Event.idx)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event coalition = %s", veaf.p(Event.coalition)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Event group id  = %s", veaf.p(Event.groupID)))
  if Event.initiator ~= nil then
    _unitname = Event.initiator:getName()
-- LOGGING DISABLED WHEN COMPILING(string.format("Event ini unit  = %s", veaf.p(_unitname)))
  end
-- LOGGING DISABLED WHEN COMPILING(string.format("Event text      = \n%s", veaf.p(Event.text)))

  local refreshRadioMenu = false
  -- human unit birth
  if Event.id == world.event.S_EVENT_BIRTH then
    if _unitname and veafRadio.humanUnits[_unitname] then
    -- a human spawned in this slot ! Yay !
-- LOGGING DISABLED WHEN COMPILING(string.format("a human spawned in this slot: %s", veaf.p(_unitname)))
    veafRadio.humanUnits[_unitname].spawned = true

      -- refresh the radio menu
      refreshRadioMenu = true
    end
  end

  -- human unit death
  if Event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT then

    if _unitname and veafRadio.humanUnits[_unitname] then
      -- it was a human in this slot !
      if veafRadio.humanUnits[_unitname] then
        veafRadio.humanUnits[_unitname].spawned = false
      end

      -- refresh the radio menu
      refreshRadioMenu = true
    end
  end

  if refreshRadioMenu then
    -- refresh the radio menu
    veafRadio.refreshRadioMenu() -- TODO refresh it only for this player ? Is this even possible ?
-- LOGGING DISABLED WHEN COMPILING(string.format("refreshRadioMenu() following event %s of human unit %s", veaf.p(EVENTS[Event.id]), veaf.p(_unitname)))
  end
end

-- function veafRadio.eventHandler:onEvent(Event)
--   local EVENTS = {
--   [0] =  "S_EVENT_INVALID",
--   [1] =  "S_EVENT_SHOT",
--   [2] =  "S_EVENT_HIT",
--   [3] =  "S_EVENT_TAKEOFF",
--   [4] =  "S_EVENT_LAND",
--   [5] =  "S_EVENT_CRASH",
--   [6] =  "S_EVENT_EJECTION",
--   [7] =  "S_EVENT_REFUELING",
--   [8] =  "S_EVENT_DEAD",
--   [9] =  "S_EVENT_PILOT_DEAD",
--   [10] =  "S_EVENT_BASE_CAPTURED",
--   [11] =  "S_EVENT_MISSION_START",
--   [12] =  "S_EVENT_MISSION_END",
--   [13] =  "S_EVENT_TOOK_CONTROL",
--   [14] =  "S_EVENT_REFUELING_STOP",
--   [15] =  "S_EVENT_BIRTH",
--   [16] =  "S_EVENT_HUMAN_FAILURE",
--   [17] =  "S_EVENT_DETAILED_FAILURE",
--   [18] =  "S_EVENT_ENGINE_STARTUP",
--   [19] =  "S_EVENT_ENGINE_SHUTDOWN",
--   [20] =  "S_EVENT_PLAYER_ENTER_UNIT",
--   [21] =  "S_EVENT_PLAYER_LEAVE_UNIT",
--   [22] =  "S_EVENT_PLAYER_COMMENT",
--   [23] =  "S_EVENT_SHOOTING_START",
--   [24] =  "S_EVENT_SHOOTING_END",
--   [25] =  "S_EVENT_MARK_ADDED",
--   [26] =  "S_EVENT_MARK_CHANGE",
--   [27] =  "S_EVENT_MARK_REMOVED",
--   [28] =  "S_EVENT_KILL",
--   [29] =  "S_EVENT_SCORE",
--   [30] =  "S_EVENT_UNIT_LOST",
--   [31] =  "S_EVENT_LANDING_AFTER_EJECTION"}

--   local _unitname = ""
--   veaf.loggers.get(veafRadio.Id):info("GOT AN EVENT")
--   veaf.loggers.get(veafRadio.Id):info(string.format("Event id        = %s - %s", tostring(Event.id), EVENTS[Event.id]))
--   veaf.loggers.get(veafRadio.Id):info(string.format("Event time      = %s", tostring(Event.time)))
--   veaf.loggers.get(veafRadio.Id):info(string.format("Event idx       = %s", tostring(Event.idx)))
--   veaf.loggers.get(veafRadio.Id):info(string.format("Event coalition = %s", tostring(Event.coalition)))
--   veaf.loggers.get(veafRadio.Id):info(string.format("Event group id  = %s", tostring(Event.groupID)))
--   if Event.initiator ~= nil then
--     _unitname = Event.initiator:getName()
--     veaf.loggers.get(veafRadio.Id):info(string.format("Event ini unit  = %s", tostring(_unitname)))
--   end
--   veaf.loggers.get(veafRadio.Id):info(string.format("Event text      = \n%s", tostring(Event.text)))

--   if Event.id == 15 and _unitname and veafRadio.humanUnits[_unitname] then
--     -- refresh the radio menu
--     veafRadio.refreshRadioMenu() -- TODO refresh it only for this player ? Is this even possible ?
--     -- debug with logInfo message to check if this mechanism is working
--     veaf.loggers.get(veafRadio.Id):info(string.format("refreshRadioMenu() following event S_EVENT_BIRTH of human unit %s", tostring(_unitname)))
--   end
-- end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafRadio._proxyMethod(parameters)
-- LOGGING DISABLED WHEN COMPILING("parameters="..veaf.p(parameters))
  local realMethod, realParameters = veaf.safeUnpack(parameters)
-- LOGGING DISABLED WHEN COMPILING("realMethod="..veaf.p(realMethod))
-- LOGGING DISABLED WHEN COMPILING("realParameters="..veaf.p(realParameters))
  if veafSecurity.isAuthenticated() then
    realMethod(realParameters)
  else
    veaf.loggers.get(veafRadio.Id):error("Your radio has to be authenticated for '+'' commands")
    trigger.action.outText("Your radio has to be authenticated for '+'' commands", 5)
  end
end

--- Refresh the radio menu, based on stored information
--- This is called from another method that has first changed the radio menu information by adding or removing elements
function veafRadio.refreshRadioMenu(dontDelay)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRadio.refreshRadioMenu()"))

  -- delay the refresh if possible
  if not dontDelay then
    if not veafRadio.refreshRadioMenuDelayedScheduling then
      veafRadio.refreshRadioMenuDelayedScheduling = mist.scheduleFunction(veafRadio._refreshRadioMenu,{},timer.getTime()+veafRadio.refreshRadioMenu_DELAY)
    end
  else
    veafRadio._refreshRadioMenu()
  end
end

--- actually refresh the radio menu, based on stored information
function veafRadio._refreshRadioMenu()
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRadio._refreshRadioMenu()"))
  veafRadio.refreshRadioMenuDelayedScheduling = nil

  -- completely delete the dcs radio menu
-- LOGGING DISABLED WHEN COMPILING("completely delete the dcs radio menu")
  if veafRadio.radioMenu.dcsRadioMenu then
    missionCommands.removeItem(veafRadio.radioMenu.dcsRadioMenu)
  else
    veaf.loggers.get(veafRadio.Id):info("_refreshRadioMenu() first time : no DCS radio menu yet")
  end

  local radioMeasures = {
       nbMenus = 0
      ,maxNbMenusInGroups = 0
      ,nbCommands = 0
      ,maxNbCommandsInGroups = 0
  }

  veafRadio.radioMenuSize = {}
  veafRadio.addSizeForAll(string.len(veafRadio.RadioMenuName))

  -- create all the commands and submenus in the dcs radio menu
-- LOGGING DISABLED WHEN COMPILING("create all the commands and submenus in the dcs radio menu")
  veafRadio.refreshRadioSubmenu(nil, veafRadio.radioMenu, radioMeasures)

  -- warn if the size starts to get too big
  local maxSize = 0
  local maxGroup = -1
  for group, size in pairs(veafRadio.radioMenuSize) do
    if maxSize < size then
      maxSize = size
      maxGroup = group
    end
    if veafRadio.MAXIMUM_SIZE > 0 and size >= veafRadio.MAXIMUM_SIZE  then
      veafRadio.reportRadioMenuSizeBreached("veafRadio._refreshRadioMenu()", group, size)
    end
  end

-- LOGGING DISABLED WHEN COMPILING(string.format("veafRadio._refreshRadioMenu() max(veafRadio.radioMenuSize)=%d,%d",maxSize, maxGroup))
-- LOGGING DISABLED WHEN COMPILING("radioMeasures="..veaf.p(radioMeasures))

end

function veafRadio._addCommand(groupId, title, menu, command, parameters)
  if not command.method then
    veaf.loggers.get(veafRadio.Id):error("ERROR - missing method for command " .. title)
  end
  local _title = title
  local _method = command.method
  local _parameters = parameters
  if command.isSecured then
-- LOGGING DISABLED WHEN COMPILING("adding secured command")

    _method = veafRadio._proxyMethod
    _parameters = {command.method, _parameters}

    if veafSecurity.isAuthenticated() then
      _title = "-" .. title
    else
      _title = "+" .. title
    end
  end

-- LOGGING DISABLED WHEN COMPILING("_title=%s", veaf.p(_title))
-- LOGGING DISABLED WHEN COMPILING("_parameters=%s", veaf.p(_parameters))

  if groupId then
-- LOGGING DISABLED WHEN COMPILING("adding for group %s command %s",groupId or "", _title or "")
    missionCommands.addCommandForGroup(groupId, _title, menu, _method, _parameters)
  else
-- LOGGING DISABLED WHEN COMPILING("adding for all command %s",_title or "")
    missionCommands.addCommand(_title, menu, _method, _parameters)
  end

end

function veafRadio.refreshRadioSubmenu(parentRadioMenu, radioMenu, radioMeasures)
-- LOGGING DISABLED WHEN COMPILING("veafRadio.refreshRadioSubmenu %s", veaf.p(veaf.ifnn(radioMenu, "title")))

  if not radioMenu or not radioMenu.title then
    return
  end

  local trace = false

  local measures_addMenu = function(group)
    radioMeasures.nbMenus = radioMeasures.nbMenus + 1
    if group ~= nil then
      if radioMeasures.maxNbMenusInGroups < radioMeasures.nbMenus then
        radioMeasures.maxNbMenusInGroups = radioMeasures.nbMenus
      end
    end
  end

  local measures_addCommand = function(group)
    radioMeasures.nbCommands = radioMeasures.nbCommands + 1
    if group ~= nil then
      if radioMeasures.maxNbCommandsInGroups < radioMeasures.nbCommands then
        radioMeasures.maxNbCommandsInGroups = radioMeasures.nbCommands
      end
    end
  end

  -- warn if the size starts to get too big
  for group, size in pairs(veafRadio.radioMenuSize) do
    if veafRadio.MAXIMUM_SIZE > 0 and size >= veafRadio.MAXIMUM_SIZE then
      veafRadio.reportRadioMenuSizeBreached(string.format("veafRadio.refreshRadioSubmenu()",radioMenu.title), group, size)
    end
  end

  -- create the radio menu in DCS
  veafRadio.addSizeForAll(string.len(radioMenu.title))
  if parentRadioMenu then
    radioMenu.dcsRadioMenu = missionCommands.addSubMenu(radioMenu.title, parentRadioMenu.dcsRadioMenu)
  else
    radioMenu.dcsRadioMenu = missionCommands.addSubMenu(radioMenu.title)
  end
  measures_addMenu()

  -- create the commands in the radio menu
  for count = 1,#radioMenu.commands do
    local command = radioMenu.commands[count]
-- LOGGING DISABLED WHEN COMPILING(string.format("command=%s",veaf.p(command)))

    if not command.usage then
        command.usage = veafRadio.USAGE_ForAll
    end
    if command.usage ~= veafRadio.USAGE_ForAll then

        -- build menu for each player group
        local alreadyDoneGroups = {}
        for groupId, groupData in pairs(veafRadio.humanGroups) do
-- LOGGING DISABLED WHEN COMPILING(string.format("groupId=%s",veaf.p(groupId)))
            for _, callsign in pairs(groupData.callsigns) do
-- LOGGING DISABLED WHEN COMPILING(string.format("callsign=%s",veaf.p(callsign)))
                local unitData = groupData.units[callsign]
                local unitName = unitData.name
-- LOGGING DISABLED WHEN COMPILING(string.format("unitName=%s",veaf.p(unitName)))
                local humanUnit =  veafRadio.humanUnits[unitName]
-- LOGGING DISABLED WHEN COMPILING(string.format("humanUnit=%s",veaf.p(humanUnit)))
-- LOGGING DISABLED WHEN COMPILING(string.format("checking if unit %s is spawned",veaf.p(unitName)))
                if humanUnit and humanUnit.spawned then
-- LOGGING DISABLED WHEN COMPILING(string.format("add radio command for player unit %s",veaf.p(unitName)))
                    -- add radio command by player unit or group
                    local parameters = command.parameters
                    if parameters == nil then
                        parameters = unitName
                    else
                        parameters = { command.parameters }
                        table.insert(parameters, unitName)
                    end
                    local _title = command.title
                    if command.usage == veafRadio.USAGE_ForUnit then
                        _title = callsign .. " - " .. command.title
                    end
                    if alreadyDoneGroups[groupId] == nil or command.usage == veafRadio.USAGE_ForUnit then
                        veafRadio.addSizeForGroup(groupId, string.len(_title))
                        veafRadio._addCommand(groupId, _title, radioMenu.dcsRadioMenu, command, parameters)
                        measures_addCommand(groupId)
                    end
                    alreadyDoneGroups[groupId] = true
                end
            end
        end
    else
        veafRadio.addSizeForAll(string.len(command.title))
        veafRadio._addCommand(nil, command.title, radioMenu.dcsRadioMenu, command, command.parameters)
        measures_addCommand()
    end
  end

  -- recurse to create the submenus in the radio menu
  for count = 1,#radioMenu.subMenus do
    local subMenu = radioMenu.subMenus[count]
    veafRadio.refreshRadioSubmenu(radioMenu, subMenu, radioMeasures)
  end

end

function veafRadio.addCommandToMainMenu(title, method)
  return veafRadio._addCommandToMainMenu(title, method, false)
end

function veafRadio.addSecuredCommandToMainMenu(title, method)
  return veafRadio._addCommandToMainMenu(title, method, true)
end

function veafRadio._addCommandToMainMenu(title, method, isSecured)
  return veafRadio._addCommandToSubmenu(title, nil, method, nil, nil, isSecured)
end

function veafRadio.addCommandToSubmenu(title, radioMenu, method, parameters, usage)
  return veafRadio._addCommandToSubmenu(title, radioMenu, method, parameters, usage, false)
end

function veafRadio.addSecuredCommandToSubmenu(title, radioMenu, method, parameters, usage)
  return veafRadio._addCommandToSubmenu(title, radioMenu, method, parameters, usage, true)
end

function veafRadio._addCommandToSubmenu(title, radioMenu, method, parameters, usage, isSecured)
-- LOGGING DISABLED WHEN COMPILING(string.format("_addCommandToSubmenu(%s)",veaf.p(title)))
    local command = {}
    command.title = title
    command.method = method
    command.parameters = parameters
    command.isSecured = isSecured
    command.usage = usage
    if command.usage == nil then command.usage = veafRadio.USAGE_ForAll end
    local menu = veafRadio.radioMenu
    if radioMenu then
       menu = radioMenu
    end

    -- add command to menu
    table.insert(menu.commands, command)

    return command
end

function veafRadio.delCommand(radioMenu, title)
  for count = 1,#radioMenu.commands do
    local command = radioMenu.commands[count]
    if command.title == title then
      table.remove(radioMenu.commands, count)
      return true
    end
  end

  return false
end

function veafRadio.addMenu(title)
  return veafRadio.addSubMenu(title, nil)
end

function veafRadio.addSubMenu(title, radioMenu)

    local subMenu = {}
    subMenu.title = title
    subMenu.dcsRadioMenu = nil
    subMenu.subMenus = {}
    subMenu.commands = {}

    local menu = veafRadio.radioMenu
    if radioMenu then
       menu = radioMenu
    end

    -- add subMenu to menu
    table.insert(menu.subMenus, subMenu)

    return subMenu
end

function veafRadio.clearSubmenu(subMenu)
  if not subMenu then
    veaf.loggers.get(veafRadio.Id):error("veafRadio.clearSubmenu() subMenu parameter is nil !")
    return
  end
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRadio.clearSubmenu(%s)",subMenu.title))
  subMenu.subMenus = {}
  subMenu.commands = {}
end

function veafRadio.delSubmenu(subMenu, radioMenu)
  if not subMenu then
    veaf.loggers.get(veafRadio.Id):error("veafRadio.delSubmenu() subMenu parameter is nil !")
    return
  end
  local menu = veafRadio.radioMenu
  if radioMenu then
    menu = radioMenu
  end
  veaf.arrayRemoveWhen(menu.subMenus, function(t, i, j)
    -- Return true to keep the value, or false to discard it.
-- LOGGING DISABLED WHEN COMPILING("searching for " .. subMenu.title)
    local v = menu.subMenus[i]
-- LOGGING DISABLED WHEN COMPILING("checking " .. v.title)    
    if v == subMenu or v.title == subMenu then
-- LOGGING DISABLED WHEN COMPILING("found ! removing " .. v.title)
      return false
    else
-- LOGGING DISABLED WHEN COMPILING("keeping " .. v.title)
      return true
    end
  end);
end

-- build a paginated submenu (internal paginating method)
local function _buildRadioMenuPage(menu, titles, elementsByTitle, addCommandToSubmenuMethod, pageSize, startIndex)
-- LOGGING DISABLED WHEN COMPILING(string.format("_buildRadioMenuPage(pageSize=%s, startIndex=%s)",tostring(pageSize), tostring(startIndex)))

  local titlesCount = #titles
-- LOGGING DISABLED WHEN COMPILING(string.format("titlesCount = %d",titlesCount))

  local pageSize = pageSize
  if not pageSize then
    pageSize = 10
  end

  local endIndex = titlesCount
  if endIndex - startIndex >= pageSize then
      endIndex = startIndex + pageSize - 2
  end
-- LOGGING DISABLED WHEN COMPILING(string.format("endIndex = %d",endIndex))
-- LOGGING DISABLED WHEN COMPILING(string.format("adding commands from %d to %d",startIndex, endIndex))
  for index = startIndex, endIndex do
      local title = titles[index]
-- LOGGING DISABLED WHEN COMPILING(string.format("titles[%d] = %s",index, title))
      local element = elementsByTitle[title]
      addCommandToSubmenuMethod(menu, title, element)
  end
  if endIndex < titlesCount then
-- LOGGING DISABLED WHEN COMPILING("adding next page menu")
      local nextPageMenu = veafRadio.addSubMenu("Next page", menu)
      _buildRadioMenuPage(nextPageMenu, titles, elementsByTitle, addCommandToSubmenuMethod, 10, endIndex+1)
  end
end

-- build a paginated submenu (main method)
function veafRadio.addPaginatedRadioElements(radioMenu, addCommandToSubmenuMethod, elements, titleAttribute, sortAttribute)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRadio.addPaginatedRadioElements() : elements=%s",veaf.p(elements)))

    if not addCommandToSubmenuMethod then
        veaf.loggers.get(veafRadio.Id):error("veafRadio.addPaginatedRadioMenu : addCommandToSubmenuMethod is mandatory !")
        return
    end

    local pageSize = 10 - #radioMenu.commands

    local sortedElements = {}
    local sortAttribute = sortAttribute or "sort"
    local titleAttribute = titleAttribute or "title"
    for name, element in pairs(elements) do
        local sortValue = element[sortAttribute]
        if not sortValue then sortValue = name end
        table.insert(sortedElements, {element=element, sort=sortValue, title=name})
    end
    local compare = function(a,b)
      if not(a) then
        a = {}
      end
      if not(a["sort"]) then
        a["sort"] = 0
      end
      if not(b) then
        b = {}
      end
      if not(b["sort"]) then
        b["sort"] = 0
      end

      return a["sort"] < b["sort"]
    end
    table.sort(sortedElements, compare)

    local sortedTitles = {}
    local elementsByTitle = {}
    for i = 1, #sortedElements do
        local title = sortedElements[i].element[titleAttribute]
        if not title then title = sortedElements[i].title end
        table.insert(sortedTitles, title)
        elementsByTitle[title] = sortedElements[i].element
    end
-- LOGGING DISABLED WHEN COMPILING("sortedTitles="..veaf.p(sortedTitles))

    _buildRadioMenuPage(radioMenu, sortedTitles, elementsByTitle, addCommandToSubmenuMethod, pageSize, 1)
    --veafRadio.refreshRadioMenu()
end

-- build a paginated submenu (main method)
function veafRadio.addPaginatedRadioMenu(title, radioMenu, addCommandToSubmenuMethod, elements, titleAttribute, sortAttribute)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRadio.addPaginatedRadioMenu(title=%s)",title))

    local firstPagePath = veafRadio.addSubMenu(title, radioMenu)
    veafRadio.addPaginatedRadioElements(firstPagePath, addCommandToSubmenuMethod, elements, titleAttribute, sortAttribute)
    return firstPagePath
end

-- prepare humans units
function veafRadio.buildHumanUnits()

    veafRadio.humanUnits = {}

    -- build menu for each player
    for name, unit in pairs(mist.DBs.humansByName) do
        -- not already in units list ?
        if veafRadio.humanUnits[unit.unitName] == nil then
-- LOGGING DISABLED WHEN COMPILING(string.format("human player found name=%s, unitName=%s, groupId=%s", name, unit.unitName,unit.groupId))
            local callsign = unit.callsign
            if type(callsign) == "table" then callsign = callsign["name"] end
            if type(callsign) == "number" then callsign = "" .. callsign end
            local unitObject = {name=unit.unitName, groupId=unit.groupId, callsign=callsign}
            veafRadio.humanUnits[unit.unitName] = unitObject
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRadio.humanUnits[%s]=\n%s",unit.unitName,veaf.p(veafRadio.humanUnits[unit.unitName])))
            if not veafRadio.humanGroups[unit.groupId] then
              veafRadio.humanGroups[unit.groupId] = {}
              veafRadio.humanGroups[unit.groupId].callsigns = {}
              veafRadio.humanGroups[unit.groupId].units = {}
            end
            table.insert(veafRadio.humanGroups[unit.groupId].callsigns,callsign)
            veafRadio.humanGroups[unit.groupId].units[callsign] = unitObject
        end
    end

    -- sort callsigns for each group
    for _, groupData in pairs(veafRadio.humanGroups) do
      table.sort(groupData.callsigns)
    end
end

function veafRadio.addSizeForGroup(groupId, sizeToAdd)
  if not veafRadio.radioMenuSize then
    veafRadio.radioMenuSize = {}
  end
  if not veafRadio.radioMenuSize[groupId] then
    veafRadio.radioMenuSize[groupId] = 0
  end
  veafRadio.radioMenuSize[groupId] = veafRadio.radioMenuSize[groupId] + sizeToAdd
end

function veafRadio.addSizeForAll(sizeToAdd)
  for groupId, _ in pairs(veafRadio.humanGroups) do
    veafRadio.addSizeForGroup(groupId, sizeToAdd)
  end
end

function veafRadio.reportRadioMenuSizeBreached_reset()
  veafRadio.reportRadioMenuSizeBreached_ALREADYDONE = false
end

function veafRadio.reportRadioMenuSizeBreached(text, group, size)
  if not veafRadio.reportRadioMenuSizeBreached_ALREADYDONE then
    local message = string.format("%s - Maximum radio menu size reached : [%s]%d / %d",text or "", tostring(group), size, veafRadio.MAXIMUM_SIZE)
    veaf.loggers.get(veafRadio.Id):warn(string.format("%s - Maximum radio menu size reached : [%s]%d / %d",text or "", tostring(group), size, veafRadio.MAXIMUM_SIZE))
    trigger.action.outText(string.format("Maximum radio menu size reached : [%s]%d / %d",tostring(group), size, veafRadio.MAXIMUM_SIZE),5)
    veafRadio.reportRadioMenuSizeBreached_ALREADYDONE = true
    mist.scheduleFunction(veafRadio.reportRadioMenuSizeBreached_reset,{},timer.getTime()+60)
  end
end

function veafRadio.getHumanUnitOrWingman(unitName)
    local result = Unit.getByName(unitName)
    if not result then
        local unitData = veafRadio.humanUnits[unitName]
-- LOGGING DISABLED WHEN COMPILING(string.format("unitData=%s",veaf.p(unitData)))
        if unitData and unitData.groupId then
            local mistGroup = mist.DBs.groupsById[unitData.groupId]
-- LOGGING DISABLED WHEN COMPILING(string.format("mistGroup=%s",veaf.p(mistGroup)))
            if mistGroup then
                local group = Group.getByName(mistGroup.groupName)
                if group then
-- LOGGING DISABLED WHEN COMPILING(string.format("group=%s",veaf.p(group)))
-- LOGGING DISABLED WHEN COMPILING(string.format("group:getUnits()=%s",veaf.p(group:getUnits())))
                    for _, groupUnit in pairs(group:getUnits()) do
                        if not result then
                            result = groupUnit
                        end
                    end
                end
            end
        end
    end
    if result then
-- LOGGING DISABLED WHEN COMPILING(string.format("result=%s",veaf.p(result)))
-- LOGGING DISABLED WHEN COMPILING(string.format("result:getName()=%s",veaf.p(result:getName())))
    end
    return result
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- radio beacons
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafRadio.startBeacon(name, firstRunDelay, secondsBetweenRepeats, frequencies, modulations, message, mp3, coalition)
-- LOGGING DISABLED WHEN COMPILING("startBeacon(name=%s, firstRunDelay=%s, secondsBetweenRepeats=%s, coalition=%s, frequencies=%s, modulations=%s, message=%s, mp3=%s)", veaf.p(name), veaf.p(firstRunDelay), veaf.p(secondsBetweenRepeats), veaf.p(coalition), veaf.p(frequencies), veaf.p(modulations), veaf.p(message), veaf.p(mp3))

  local beacon = veafRadio.beacons[name:lower()]
  if not beacon then beacon = {} end
  beacon.name = name
  beacon.secondsBetweenRepeats = secondsBetweenRepeats
  beacon.nextRun = timer.getTime()+firstRunDelay
  beacon.frequencies = frequencies
  beacon.modulations = modulations
  beacon.coalition = coalition
  beacon.message = message
  beacon.mp3 = mp3

-- LOGGING DISABLED WHEN COMPILING(string.format("adding beacon %s", tostring(name)))
  veafRadio.beacons[name:lower()] = beacon
end

function veafRadio._runBeacons()
-- LOGGING DISABLED WHEN COMPILING("_runBeacons()")

  local now = timer.getTime()
-- LOGGING DISABLED WHEN COMPILING(string.format("now = %s", tostring(now)))
  for name, beacon in pairs(veafRadio.beacons) do
-- LOGGING DISABLED WHEN COMPILING(string.format("checking %s supposed to run at %s", tostring(beacon.name), tostring(beacon.nextRun)))
    if beacon.nextRun <= now then
-- LOGGING DISABLED WHEN COMPILING(string.format("running beacon %s", tostring(name)))
      if beacon.message then
        veafRadio.transmitMessage(beacon.message, beacon.frequencies, beacon.modulations, beacon.name, beacon.coalition, nil, true)
      elseif beacon.mp3 then
        veafRadio.playToRadio(beacon.mp3, beacon.frequencies, beacon.modulations, beacon.name, beacon.coalition, nil, true)
      end
      beacon.nextRun = now + beacon.secondsBetweenRepeats
    end
  end

-- LOGGING DISABLED WHEN COMPILING(string.format("rescheduling in %s seconds", tostring(veafRadio.BEACONS_SCHEDULE)))
  mist.scheduleFunction(veafRadio._runBeacons,{},timer.getTime()+veafRadio.BEACONS_SCHEDULE)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- radio utilities
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- transmit a radio message or play a mp3 file via SRS
function veafRadio._transmitViaSRS(message, file, frequencies, modulations, name, coalition, eventPos)
-- LOGGING DISABLED WHEN COMPILING("transmitMessage(name=%s, coalition=%s, frequencies=%s, modulations=%s, message=%s, file=%s)", veaf.p(name), veaf.p(coalition), veaf.p(frequencies), veaf.p(modulations), veaf.p(message), veaf.p(file))
  local posOption = ""
  if eventPos then
-- LOGGING DISABLED WHEN COMPILING(string.format("eventPos=%s",veaf.p(eventPos)))
    local lat, lon, alt = coord.LOtoLL(eventPos)
    posOption = string.format("-L %d -O %d -A %d", lat, lon, alt)
  end

  local contentOption = ""
  if message then
    contentOption = string.format("-t \"%s\"", message)
  elseif file then
    contentOption = string.format("-i \"%s\"", file)
  else
    veaf.loggers.get(veafRadio.Id):error("no message nor file for veafRadio._transmitViaSRS()!")
    return
  end

  local l_os = os
  if not l_os and SERVER_CONFIG and SERVER_CONFIG.getModule then
      l_os = SERVER_CONFIG.getModule("os")
  end

  if l_os and STTS then
    local cmd = string.format("start /min \"%s\" \"%s\\%s\" %s -f %s -m %s -c %s -p %s -n \"%s\" %s", STTS.DIRECTORY, STTS.DIRECTORY, STTS.EXECUTABLE, contentOption, frequencies, modulations, coalition, STTS.SRS_PORT, name, posOption)
-- LOGGING DISABLED WHEN COMPILING(string.format("executing os command %s", cmd))
    local result = l_os.execute(cmd)
    if result == nil then
      veaf.loggers.get(veafRadio.Id):warn(string.format("Nil result after executing os command %s", cmd))
    end
    return result
  end
end

-- transmit a radio message via SRS
function veafRadio.transmitMessage(message, frequencies, modulations, name, coalition, eventPos, quiet)
-- LOGGING DISABLED WHEN COMPILING("transmitMessage(name=%s, coalition=%s, frequencies=%s, modulations=%s, message=%s)", veaf.p(name), veaf.p(coalition), veaf.p(frequencies), veaf.p(modulations), veaf.p(message))
  if eventPos then
-- LOGGING DISABLED WHEN COMPILING(string.format("eventPos=%s",veaf.p(eventPos)))
  end

  veafRadio._transmitViaSRS(message, nil, frequencies, modulations, name, coalition, eventPos)

  if not quiet and coalition then
    trigger.action.outTextForCoalition(coalition, string.format("%s (%s) : %s", name, frequencies, message), 30)
  end
end

-- play a MP3 file via SRS
function veafRadio.playToRadio(pathToMP3, frequencies, modulations, name, coalition, eventPos, quiet)
-- LOGGING DISABLED WHEN COMPILING("playToRadio(name=%s, coalition=%s, frequencies=%s, modulations=%s, pathToMP3=%s)", veaf.p(name), veaf.p(coalition), veaf.p(frequencies), veaf.p(modulations), veaf.p(pathToMP3))
  if eventPos then
-- LOGGING DISABLED WHEN COMPILING(string.format("eventPos=%s",veaf.p(eventPos)))
  end

  veafRadio._transmitViaSRS(nil, pathToMP3, frequencies, modulations, name, coalition, eventPos)

  if not quiet and coalition then
    trigger.action.outTextForCoalition(coalition, string.format("%s (%s) : playing %s", name, frequencies, pathToMP3), 30)
  end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- user menus
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafRadio.createUserMenu(configuration, groupId)
-- LOGGING DISABLED WHEN COMPILING("veafRadio.createUserMenu(groupId=%s, configuration=%s)",veaf.p(groupId), veaf.p(configuration))

    local function _recursivelyCreateMenu(configuration, parentMenu)
-- LOGGING DISABLED WHEN COMPILING("_recursivelyCreateMenu(configuration=%s, parentMenu=%s)",veaf.p(configuration), veaf.p(parentMenu))
        local result

        for _, item in pairs(configuration) do
            local itemType = item[1]
-- LOGGING DISABLED WHEN COMPILING("itemType = [%s]",veaf.p(itemType))
            local name = item[2]
-- LOGGING DISABLED WHEN COMPILING("name = [%s]",veaf.p(name))
            if itemType == "menu" then
                -- this is a menu with a content
                local content = item[3]
-- LOGGING DISABLED WHEN COMPILING("content = [%s]",veaf.p(content))

-- LOGGING DISABLED WHEN COMPILING("creating menu name=%s",veaf.p(name))
                if groupId ~= nil then
                    result = missionCommands.addSubMenuForGroup(groupId, name, parentMenu)
                else
                    result = missionCommands.addSubMenu(name, parentMenu)
                end
                -- recurse if needed
                if content ~= nil and #content > 0 then
                    _recursivelyCreateMenu(content, result)
                end
            else
                -- this is a command with a function
                local aFunction = item[3]
-- LOGGING DISABLED WHEN COMPILING("aFunction = [%s]",veaf.p(aFunction))
                local parameters = item[4]
-- LOGGING DISABLED WHEN COMPILING("parameters = [%s]",veaf.p(parameters))

-- LOGGING DISABLED WHEN COMPILING("creating command name=%s",veaf.p(name))
                if groupId ~= nil then
                    missionCommands.addCommandForGroup(groupId, name, parentMenu, aFunction, parameters)
                else
                    missionCommands.addCommand(name, parentMenu, aFunction, parameters)
                end
            end
        end
    end

    _recursivelyCreateMenu(configuration, nil)
end

-- helper functions for user menus
local spawnCapFunction = function () end


function veafRadio.menu(name, ...)
  return {
      "menu", name, {...}
  }
end

function veafRadio.command(name, aFunction, parameters)
  return {
      "command", name, aFunction, parameters
  }
end

function veafRadio.mainmenu(...)
  return { ... }
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafRadio.initialize(skipHelpMenus)
    -- Find the path of the SRS radio configuration script
    -- We're going to need it to define :
    --  STTS.DIRECTORY
    --- STTS.SRS_PORT
    local srsConfigPath=nil

    local l_lfs = lfs
    if not l_lfs and SERVER_CONFIG and SERVER_CONFIG.getModule then
        l_lfs = SERVER_CONFIG.getModule("lfs")
    end

    if l_lfs then
        srsConfigPath = l_lfs.writedir() .. "\\DCS-SimpleRadio-Standalone\\SRS_for_scripting_config.lua"
-- LOGGING DISABLED WHEN COMPILING(string.format("srsConfigPath = %s", tostring(srsConfigPath)))
        --local test = l_lfs.currentdir()
-- LOGGING DISABLED WHEN COMPILING(string.format("test = %s", tostring(test)))
        if srsConfigPath then
          -- execute the script
          local file = loadfile(srsConfigPath)
          if file then
            file()
            veaf.loggers.get(veafRadio.Id):info("SRS configuration file loaded")
            if STTS then
              STTS.MP3_FOLDER = l_lfs.writedir() .."\\..\\..\\Music"
-- LOGGING DISABLED WHEN COMPILING(string.format("STTS.SRS_PORT = %s", tostring(STTS.SRS_PORT)))
-- LOGGING DISABLED WHEN COMPILING(string.format("STTS.DIRECTORY = %s", tostring(STTS.DIRECTORY)))
-- LOGGING DISABLED WHEN COMPILING(string.format("STTS.EXECUTABLE = %s", tostring(STTS.EXECUTABLE)))
            end
          else
            veaf.loggers.get(veafRadio.Id):warn(string.format("Error while loading SRS configuration file [%s]",srsConfigPath))
          end
      end
    end

    veafRadio.skipHelpMenus = skipHelpMenus or false

    -- Build the initial radio menu
    veafRadio.buildHumanUnits()
    veafRadio.refreshRadioMenu(false)
    mist.scheduleFunction(veafRadio._refreshRadioMenu,{},timer.getTime()+15)

    -- Add "player unit birth" event handler.
    world.addEventHandler(veafRadio.eventHandler)

    -- add marker change event handler
    veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafRadio.onEventMarkChange)

    -- start the beacons
    veafRadio._runBeacons()
end

veaf.loggers.get(veafRadio.Id):info(string.format("Loading version %s", veafRadio.Version))


------------------ END script veafRadio.lua  ------------------


------------------ START script veafRemote.lua  ------------------

------------------------------------------------------------------
-- VEAF remote callback functions for DCS World
-- By zip (2020)
--
-- Features:
-- ---------
-- * This module offers support for calling script from a web server or a server hook
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafRemote = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafRemote.Id = "REMOTE"

--- Version.
veafRemote.Version = "2.3.0"

-- trace level, specific to this module
--veafRemote.LogLevel = "trace"

veaf.loggers.new(veafRemote.Id, veafRemote.LogLevel)

-- if false, SLMOD will not be called for regular commands
veafRemote.USE_SLMOD = false

-- if false, SLMOD will never be called
veafRemote.USE_SLMOD_FOR_SPECIAL_COMMANDS = false

veafRemote.CommandStarter = "_remote"

veafRemote.MIN_LEVEL_FOR_MARKER = 10

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafRemote.monitoredCommands = {}
veafRemote.remoteUsers = {}
veafRemote.remoteUnitsPilots = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- NIOD callbacks
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafRemote.addNiodCallback(name, parameters, code)
    if niod then
        veaf.loggers.get(veafRemote.Id):info("Adding NIOD function "..name)
        niod.functions[name] = function(payload)
        -- start of inline function

-- LOGGING DISABLED WHEN COMPILING(string.format("niod callback [%s] was called with payload %s", veaf.p(name), veaf.p(payload)))

            local errors = {}

            -- check mandatory parameters presence
            for parameterName, parameterData in pairs(parameters) do
-- LOGGING DISABLED WHEN COMPILING(string.format("checking if parameter [%s] is mandatory", veaf.p(parameterName)))
                if parameterData and parameterData.mandatory then
                    if not (payload and payload[parameterName]) then
                        local text = "missing mandatory parameter "..parameterName
-- LOGGING DISABLED WHEN COMPILING(text)
                        table.insert(errors, text)
                    end
                end
            end

            -- check parameters type
            if payload then
                for parameterName, value in pairs(payload) do
                    local parameter = parameters[parameterName]
                    if not parameter then
                        table.insert(errors, "unknown parameter "..parameterName)
                    elseif value and not(type(value) == parameter.type) then
                        local text =  string.format("parameter %s should have type %s, has %s ", parameterName, parameter.type, type(value))
-- LOGGING DISABLED WHEN COMPILING(text)
                        table.insert(errors, text)
                    end
                end
            end

            -- stop on error
            if #errors > 0 then
                local errorMessage = ""
                for _, error in pairs(errors) do
                    errorMessage = errorMessage .. "\n" .. error
                end
                veaf.loggers.get(veafRemote.Id):error(string.format("niod callback [%s] was called with incorrect parameters :", veaf.p(name), errorMessage))
                return errorMessage
            else
-- LOGGING DISABLED WHEN COMPILING(string.format("payload = %s", veaf.p(payload)))
-- LOGGING DISABLED WHEN COMPILING(string.format("unpacked payload = %s", veaf.p(veaf.safeUnpack(payload))))
                local status, retval = pcall(code,veaf.safeUnpack(payload))
                if status then
                    return retval
                else
                    return "an error occured : "..veaf.p(status)
                end
            end

        end -- of inline function

    else
        veaf.loggers.get(veafRemote.Id):error("NIOD is not loaded !")
    end
end

function veafRemote.addNiodCommand(name, command)
    veafRemote.addNiodCallback(
        name,
        {
            parameters={   mandatory=false, type="string"},
            x={   mandatory=false, type="number"},
            y={   mandatory=false, type="number"},
            z={   mandatory=false, type="number"},
            silent={    mandatory=false, type="boolean"}
        },
        function(parameters, x, y, z, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("niod->command %s (%s, %s, %s, %s, %s)", veaf.p(parameters), veaf.p(x), veaf.p(y), veaf.p(z), veaf.p(silent)))
            return veafRemote.executeCommand({x=x or 0, y=y or 0, z=z or 0}, command..parameters)
        end
    )
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- default endpoints list
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafRemote.buildDefaultList()
    local TEST = false
    if TEST then

        -- test
        veafRemote.addNiodCallback(
            "test",
            {
                param1S_M={  mandatory=true, type="string"},
                param2S={  mandatory=false, type="string"},
                param3N={  mandatory=false, type="number"},
                param4B={  mandatory=false, type="boolean"},
            },
            function(param1S_M, param2S, param3N, param4B)
                local text = string.format("niod.test(%s, %s, %s, %s)", veaf.p(param1S_M), veaf.p(param2S), veaf.p(param3N), veaf.p(param4B))
-- LOGGING DISABLED WHEN COMPILING(text)
                trigger.action.outText(text, 15)
            end
        )
        -- login
        veafRemote.addNiodCallback(
            "login",
            {
                password={  mandatory=true, type="string"},
                timeout={   mandatory=false, type="number"},
                silent={    mandatory=false, type="boolean"}
            },
            function(password, timeout, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("niod.login(%s, %s, %s)",veaf.p(password), veaf.p(timeout),veaf.p(silent))) -- TODO remove password from log
                if veafSecurity.checkPassword_L1(password) then
                    veafSecurity.authenticate(timeout)
                    return "Mission is unlocked"
                else
                    return "wrong password"
                end
            end
        )

    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafRemote.onEventMarkChange(eventPos, event)
    if veafRemote.executeCommand(eventPos, event.text) then

        -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
        trigger.action.removeMark(event.idx)

    end
end


function veafRemote.executeCommand(eventPos, eventText)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.executeCommand(eventText=[%s])", tostring(eventText)))

    -- Check if marker has a text and the veafRemote.CommandStarter keyphrase.
    if eventText ~= nil and eventText:lower():find(veafRemote.CommandStarter) then

        -- Analyse the mark point text and extract the keywords.
        local command, password = veafRemote.markTextAnalysis(eventText)

        if command then
            -- do the magic
            return veafRemote.executeRemoteCommand(command, password)
        end
    end
end

--- Extract keywords from mark text.
function veafRemote.markTextAnalysis(text)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.markTextAnalysis(text=[%s])", tostring(text)))

    if text then
        -- extract command and password
        local password, command = text:match(veafRemote.CommandStarter.."#?([^%s]*)%s+(.+)")
        if command then
-- LOGGING DISABLED WHEN COMPILING(string.format("command = [%s]", command))
            return command, password
        end
    end
    return nil
end

-- execute a command
function veafRemote.executeRemoteCommand(command, password)
    local command = command or ""
    local password = password or ""
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.executeRemoteCommand([%s])",command))
    if not(veafSecurity.checkPassword_L1(password)) then
        veaf.loggers.get(veafRemote.Id):error(string.format("veafRemote.executeRemoteCommand([%s]) - bad or missing password",command))
        trigger.action.outText("Bad or missing password",5)
        return false
    end
    local commandData = veafRemote.monitoredCommands[command:lower()]
    if commandData then
        local scriptToExecute = commandData.script
-- LOGGING DISABLED WHEN COMPILING(string.format("found script [%s] for command [%s]", scriptToExecute, command))
        local authorized = (not(commandData.requireAdmin)) or (veafSecurity.checkSecurity_L9(password))
        if not authorized then
            return false
        else
            local result, err = mist.utils.dostring(scriptToExecute)
            if result then
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.executeRemoteCommand() - lua code was successfully called for script [%s]", scriptToExecute))
                return true
            else
                veaf.loggers.get(veafRemote.Id):error(string.format("veafRemote.executeRemoteCommand() - error [%s] calling lua code for script [%s]", err, scriptToExecute))
                return false
            end
        end
    else
        veaf.loggers.get(veafRemote.Id):warn(string.format("veafRemote.executeRemoteCommand : cannot find command [%s]",command or ""))
    end
    return false
end

-- execute command from the remote interface (see VEAF-server-hook.lua)
function veafRemote.executeCommandFromRemote(username, level, unitName, veafModule, command)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.executeCommandFromRemote([%s], [%s], [%s], [%s], [%s])", veaf.p(username), veaf.p(level), veaf.p(unitName), veaf.p(veafModule), veaf.p(command)))
    --local _user = veafRemote.getRemoteUser(username)
-- LOGGING DISABLED WHEN COMPILING(string.format("_user = [%s]",veaf.p(_user)))
    --if not _user then 
    --    return false
    --end
    if not veafModule or not username or not command then
        return false
    end
    local _user = { name = username, level = tonumber(level or "-1")}
    local _parameters = { _user, username, unitName, command }
    local _status, _retval
    local _module = veafModule:lower()
    if _module == "air" then
-- LOGGING DISABLED WHEN COMPILING(string.format("running veafCombatMission.executeCommandFromRemote"))
        _status, _retval = pcall(veafCombatMission.executeCommandFromRemote, _parameters)
    elseif _module == "point" then
-- LOGGING DISABLED WHEN COMPILING(string.format("running veafNamedPoints.executeCommandFromRemote"))
        _status, _retval = pcall(veafNamedPoints.executeCommandFromRemote, _parameters)
    elseif _module == "atis" or _module == "atc" or _module == "weather" then
-- LOGGING DISABLED WHEN COMPILING(string.format("running veafWeather.executeCommandFromRemote"))
        _status, _retval = pcall(veafWeather.executeCommandFromRemote, _parameters)
    elseif _module == "alias" then
-- LOGGING DISABLED WHEN COMPILING(string.format("running veafShortcuts.executeCommandFromRemote"))
        _status, _retval = pcall(veafShortcuts.executeCommandFromRemote, _parameters)
    elseif _module == "carrier" then
-- LOGGING DISABLED WHEN COMPILING(string.format("running veafShortcuts.executeCommandFromRemote"))
        _status, _retval = pcall(veafCarrierOperations.executeCommandFromRemote, _parameters)
    elseif _module == "secu" then
-- LOGGING DISABLED WHEN COMPILING(string.format("running veafSecurity.executeCommandFromRemote"))
        _status, _retval = pcall(veafSecurity.executeCommandFromRemote, _parameters)
    else
        veaf.loggers.get(veafRemote.Id):error(string.format("Module not found : [%s]", veaf.p(veafModule)))
        return false
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("_status = [%s]",veaf.p(_status)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_retval = [%s]",veaf.p(_retval)))
    if not _status then
        veaf.loggers.get(veafRemote.Id):error(string.format("Error when [%s] tried running [%s] in module [%s]; it returned %s", veaf.p(_user.name), veaf.p(_parameters), veaf.p(veafModule), veaf.p(_retval)))
    else
        veaf.loggers.get(veafRemote.Id):info(string.format("[%s] ran [%s] in module [%s]; it returned %s", veaf.p(_user.name), veaf.p(_parameters), veaf.p(veafModule), veaf.p(_retval)))
    end
    return _status
end

-- register a user from the server
function veafRemote.registerUser(username, userpower, ucid)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.registerUser([%s], [%s], [%s])",veaf.p(username), veaf.p(userpower), veaf.p(ucid)))
    if not username or not ucid then
        return false
    end
    veafRemote.remoteUsers[username:lower()] = { name = username, level = tonumber(userpower or "-1"), ucid = ucid }
end

-- register a user slot from the server; called when the player changes slot
function veafRemote.registerUserSlot(username, ucid, unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.registerUserSlot([%s], [%s], [%s])",veaf.p(username), veaf.p(ucid), veaf.p(unitName)))
    if not username or not unitName then
        return false
    end
    local remoteUser = veafRemote.remoteUsers[username:lower()]
    if not remoteUser then
        remoteUser = { name = username, ucid = ucid}
    end
    local previousUnit = remoteUser.unitName
    remoteUser.unitName = unitName -- can be nil if the player got out of the unit
    -- unregister the previous unit, if any
    if previousUnit then
        veafRemote.remoteUnitsPilots[previousUnit] = nil
    end
    -- register the current unit, if any
    if unitName then
        veafRemote.remoteUnitsPilots[unitName] = remoteUser
    end
end

-- return a user from the server table
function veafRemote.getRemoteUser(username)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.getRemoteUser([%s])",veaf.p(username)))
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.remoteUsers = [%s]",veaf.p(veafRemote.remoteUsers)))
    if not username then
        return nil
    end
    return veafRemote.remoteUsers[username:lower()]
end

-- return a user from the server units table
function veafRemote.getRemoteUserFromUnit(unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.getRemoteUserFromUnit([%s])",veaf.p(unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("veafRemote.remoteUnitsPilots = [%s]",veaf.p(veafRemote.remoteUnitsPilots)))
    if not unitName then
        return nil
    end
    return veafRemote.remoteUnitsPilots[unitName]
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafRemote.initialize()
    veaf.loggers.get(veafRemote.Id):info("Initializing module")
    veafRemote.buildDefaultList()
    veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafRemote.onEventMarkChange)
end

veaf.loggers.get(veafRemote.Id):info(string.format("Loading version %s", veafRemote.Version))

------------------ END script veafRemote.lua  ------------------


------------------ START script veafSpawn.lua  ------------------

------------------------------------------------------------------
-- VEAF spawn command and functions for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Listen to marker change events and execute spawn commands, with optional parameters
-- * Possibilities : 
-- *    - spawn a specific ennemy unit or group
-- *    - create a cargo drop to be picked by a helo
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

--- veafSpawn Table.
veafSpawn = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafSpawn.Id = "SPAWN"

--- Version.
veafSpawn.Version = "1.57.0"

-- trace level, specific to this module
--veafSpawn.LogLevel = "trace"

veaf.loggers.new(veafSpawn.Id, veafSpawn.LogLevel)

--- Key phrase to look for in the mark text which triggers the spawn command.
veafSpawn.SpawnKeyphrase = "_spawn"

--- Key phrase to look for in the mark text which triggers the destroy command.
veafSpawn.DestroyKeyphrase = "_destroy"

--- Key phrase to look for in the mark text which triggers the teleport command.
veafSpawn.TeleportKeyphrase = "_teleport"

--- Key phrase to look for in the mark text which triggers the drawing commands.
veafSpawn.DrawingKeyphrase = "_drawing"

--- Key phrase to look for in the mark text which triggers the mission master commands.
veafSpawn.MissionMasterKeyphrase = "_mm"

--- Name of the spawned units group 
veafSpawn.RedSpawnedUnitsGroupName = "VEAF Spawned Units"

--- Illumination flare default initial altitude (in meters AGL)
veafSpawn.IlluminationFlareAglAltitude = 1000

veafSpawn.RadioMenuName = "SPAWN"
veafSpawn.HideRadioMenu = false

--- static object type spawned when using the "logistic" keyword
veafSpawn.LogisticUnitType = "FARP Ammo Dump Coating"
veafSpawn.LogisticUnitCategory = "Fortifications"

veafSpawn.ShellingInterval = 5 -- seconds between shells, randomized by 30%
veafSpawn.FlakingInterval = 2 -- seconds between flak shells, randomized by 30%
veafSpawn.IlluminationShellingInterval = 45 -- seconds between illumination shells, randomized by 30%

veafSpawn.MIN_REPEAT_DELAY = 5

veafSpawn.HoundElintAddDelay = 1 --delay before attempting to add a unit to Hound Elint, required for aircrafts spawned dynamically at least

veafSpawn.AirUnitTemplatesPrefix = "veafSpawn-"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafSpawn.rootPath = nil

-- counts the units generated 
veafSpawn.spawnedUnitsCounter = 0

-- store all the convoys spawned
veafSpawn.spawnedConvoys = {}

-- store all the air units templates (groups, actually)
veafSpawn.airUnitTemplates = {}

-- all the named groups that have been spawned
veafSpawn.spawnedNamesIndex = {}

-- time delay between the watchdog checks for each CAP
veafSpawn.CAP_WATCHDOG_DELAY = 10

-- range scale of cargo weight biases
veafSpawn.cargoWeightBiasRange = 6

--AFAC related base data
veafSpawn.AFAC = {}
-- number of AFAC spawned
veafSpawn.AFAC.numberSpawned = {}
veafSpawn.AFAC.numberSpawned[coalition.side.BLUE] = nil
veafSpawn.AFAC.numberSpawned[coalition.side.RED] = nil
-- maximum number of AFACs allowed for spawning by players
veafSpawn.AFAC.maximumAmount = 8
-- base frequency for the first AFAC spawned
veafSpawn.AFAC.baseAFACfrequency = {}
veafSpawn.AFAC.baseAFACfrequency[coalition.side.BLUE] = 226300000 -- 226.300000 MHz otherwise known as 226300000 Hz
veafSpawn.AFAC.baseAFACfrequency[coalition.side.RED] = 226300000 -- 226.300000 MHz otherwise known as 226300000 Hz
-- callsign list of the AFACs
veafSpawn.AFAC.callsigns = {}
veafSpawn.AFAC.callsigns[coalition.side.BLUE] = {
    [1] = {name = "Enfield 9 1", taken = false},
    [2] = {name = "Springfield 9 1", taken = false},
    [3] = {name = "Uzi 9 1", taken = false},
    [4] = {name = "Colt 9 1", taken = false},
    [5] = {name = "Dodge 9 1", taken = false},
    [6] = {name = "Ford 9 1", taken = false},
    [7] = {name = "Chevy 9 1", taken = false},
    [8] = {name = "Pontiac 9 1", taken = false},
}
veafSpawn.AFAC.callsigns[coalition.side.RED] = {
    [1] = {name = "181", taken = false},
    [2] = {name = "281", taken = false},
    [3] = {name = "381", taken = false},
    [4] = {name = "481", taken = false},
    [5] = {name = "581", taken = false},
    [6] = {name = "681", taken = false},
    [7] = {name = "781", taken = false},
    [8] = {name = "881", taken = false},
}
-- AFAC mission data as MIST isn't able to recover it from dynamically spawned aircrafts
veafSpawn.AFAC.missionData = {}
veafSpawn.AFAC.missionData[coalition.side.BLUE] = {}
veafSpawn.AFAC.missionData[coalition.side.RED] = {}

veafSpawn.traceMarkerId = 3727

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafSpawn.onEventMarkChange(eventPos, event)
-- LOGGING DISABLED WHEN COMPILING(string.format("event  = %s", veaf.p(event)))

    -- choose by default the coalition opposing the player who triggered the event
    local invertedCoalition = 1
    if event.coalition == 1 then
        invertedCoalition = 2
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("event.idx  = %s", veaf.p(event.idx)))

    if veafSpawn.executeCommand(eventPos, event.text, invertedCoalition, event.idx, nil, nil, nil, nil, nil, true) then

        -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
        trigger.action.removeMark(event.idx)

    end
end

function veafSpawn.executeCommand(eventPos, eventText, coalition, markId, bypassSecurity, spawnedGroups, repeatCount, repeatDelay, route, allowStartDelay)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn.executeCommand(eventText=[%s])", eventText))
-- LOGGING DISABLED WHEN COMPILING(string.format("coalition=%s", veaf.p(coalition)))
-- LOGGING DISABLED WHEN COMPILING(string.format("markId=%s", veaf.p(markId)))
-- LOGGING DISABLED WHEN COMPILING(string.format("bypassSecurity=%s", veaf.p(bypassSecurity)))
-- LOGGING DISABLED WHEN COMPILING(string.format("repeatCount=%s", veaf.p(repeatCount)))
-- LOGGING DISABLED WHEN COMPILING(string.format("repeatDelay=%s", veaf.p(repeatDelay)))
-- LOGGING DISABLED WHEN COMPILING(string.format("route=%s", veaf.p(route)))
-- LOGGING DISABLED WHEN COMPILING(string.format("allowStartDelay=%s", veaf.p(allowStartDelay)))

    -- Check if marker has a text and the veafSpawn.SpawnKeyphrase keyphrase.
    if eventText ~= nil and (eventText:lower():find(veafSpawn.SpawnKeyphrase) or eventText:lower():find(veafSpawn.DestroyKeyphrase) or eventText:lower():find(veafSpawn.TeleportKeyphrase) or eventText:lower():find(veafSpawn.DrawingKeyphrase) or eventText:lower():find(veafSpawn.MissionMasterKeyphrase)) then

        -- Analyse the mark point text and extract the keywords.
        local options = veafSpawn.markTextAnalysis(eventText)

        if options then
            local repeatDelay = repeatDelay
            local repeatCount = repeatCount
            local allowStartDelay = allowStartDelay or false
            local startDelay = options.delayedStart

            if allowStartDelay and startDelay and startDelay > 0 then
-- LOGGING DISABLED WHEN COMPILING(string.format("scheduling veafSpawn.executeCommand for a delayed start in %s seconds", veaf.p(startDelay)))
                mist.scheduleFunction(veafSpawn.executeCommand, {eventPos, eventText, coalition, markId, bypassSecurity, spawnedGroups, nil, nil, route, false}, timer.getTime() + startDelay)
                return true
            end

            if options.repeatCount and not repeatCount then -- only use the parsed repeat options IF the parameter is not set (not during a repeat loop)
                -- set repeatCount and repeatDelay using the parsed options
                repeatCount = options.repeatCount
                repeatDelay = options.repeatDelay or veafSpawn.MIN_REPEAT_DELAY
-- LOGGING DISABLED WHEN COMPILING(string.format("using parsed repeat options to set repeatCount to %s and repeatDelay to %s", veaf.p(repeatCount), veaf.p(repeatDelay)))
            end

            if repeatCount and repeatCount > 0 then
                repeatDelay = repeatDelay
                if repeatDelay < veafSpawn.MIN_REPEAT_DELAY then
                    repeatDelay = veafSpawn.MIN_REPEAT_DELAY
                end
                repeatCount = repeatCount - 1

                -- schedule the next step of the repeated command
-- LOGGING DISABLED WHEN COMPILING(string.format("scheduling veafSpawn.executeCommand for %s repeats in %s seconds", veaf.p(repeatCount), veaf.p(repeatDelay)))
                mist.scheduleFunction(veafSpawn.executeCommand, {eventPos, eventText, coalition, markId, bypassSecurity, spawnedGroups, repeatCount, repeatDelay, route, false}, timer.getTime() + repeatDelay)
            end

            if not(options.radius) then
                if options.farp or options.cargo or options.logistic or options.destroy or options.teleport or options.bomb or options.smoke or options.flare or options.signal then
                    options.radius = 0
                else
                    options.radius = 150
                end
            end

            for i=1,options.multiplier do
                local spawnedGroup = nil

                if not options.side then
                    if options.country then
                        -- deduct the side from the country
                        options.side = veaf.getCoalitionForCountry(options.country, true)
                    else
                        options.side = coalition
                    end
                end

                if not options.country then
                    -- deduct the country from the side
                    options.country = veaf.getCountryForCoalition(options.side)
                end

-- LOGGING DISABLED WHEN COMPILING(string.format("options.side=%s",tostring(options.side)))
-- LOGGING DISABLED WHEN COMPILING(string.format("options.country=%s",tostring(options.country)))

                local routeDone = false

                --indication is the spawn is meant to be a convoy, to adapt it's spawning pattern
                local hasDest = false
                if (options.destination ~= nil) then
                    hasDest = true
                end

                -- Check options commands
                if options.unit then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    local code = options.laserCode
                    local channel = options.freq
                    local band = options.mod
                    if options.role == "tacan" then
                        channel = options.tacanChannel or 99
                        code = options.tacanCode or ("T"..tostring(channel))
                        band = options.tacanBand or "X"
                    end
                    spawnedGroup = veafSpawn.spawnUnit(eventPos, options.radius, options.name, options.country, options.altitude, options.heading, options.unitName, options.role, options.forceStatic, code, channel, band, bypassSecurity, not options.showMFD)
                elseif options.farp then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    if not options.type then
                        options.type = "invisible"
                    end
                    local channel = options.tacanChannel
                    local code = options.tacanCode
                    local mod = options.tacanBand
                    spawnedGroup = veafSpawn.spawnFarp(eventPos, options.radius, options.name, options.country, options.type, options.side, options.heading, options.spacing, bypassSecurity, not options.showMFD, options.noFarpMarkers, code, channel, mod)
                elseif options.fob then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnFob(eventPos, options.radius, options.name, options.country, options.type, options.side, options.heading, options.spacing, bypassSecurity, not options.showMFD)
                elseif options.cap then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnCombatAirPatrol(eventPos, options.radius, options.name, options.country, options.altitude, options.altitudedelta, options.heading, options.distance, options.speed, options.capradius, options.skill, bypassSecurity, options.showMFD)
                elseif options.afac then
                    --check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnAFAC(eventPos, options.name, options.country, options.altitude, options.speed, options.heading, options.freq, options.mod, options.laserCode, options.immortal, false, options.showMFD)
                elseif options.group then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnGroup(eventPos, options.radius, options.name, options.country, options.altitude, options.heading, options.spacing, options.unitName, bypassSecurity, hasDest, not options.showMFD)
                elseif options.infantryGroup then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnInfantryGroup(eventPos, options.radius, options.country, options.side, options.heading, options.spacing, options.defense, options.armor, options.size, bypassSecurity, not options.showMFD)
                elseif options.armoredPlatoon then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnArmoredPlatoon(eventPos, options.radius, options.country, options.side, options.heading, options.spacing, options.defense, options.armor, options.size, bypassSecurity, hasDest, not options.showMFD)
                elseif options.airDefenseBattery then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnAirDefenseBattery(eventPos, options.radius, options.country, options.side, options.heading, options.spacing, options.defense, bypassSecurity, hasDest, not options.showMFD)
                elseif options.transportCompany then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnTransportCompany(eventPos, options.radius, options.country, options.side, options.heading, options.spacing, options.defense, options.size, bypassSecurity, hasDest, not options.showMFD)
                elseif options.fullCombatGroup then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnFullCombatGroup(eventPos, options.radius, options.country, options.side, options.heading, options.spacing, options.defense, options.armor, options.size, bypassSecurity, not options.showMFD)
                elseif options.convoy then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnConvoy(eventPos, options.name, options.radius, options.country, options.side, options.heading, options.spacing, options.speed, options.patrol, options.offroad, options.destination, options.defense, options.size, options.armor, bypassSecurity, not options.showMFD)
                    routeDone = true
                elseif options.cargo then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnCargo(eventPos, options.radius, options.cargoType, options.country, options.cargoWeightBias, options.cargoSmoke, options.unitName, bypassSecurity, not options.showMFD)
                elseif options.logistic then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                    spawnedGroup = veafSpawn.spawnLogistic(eventPos, options.radius, options.country, bypassSecurity, not options.showMFD)
                elseif options.destroy then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L1(options.password, markId)) then return end
                    veafSpawn.destroy(eventPos, options.radius, options.unitName)
                elseif options.teleport then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L1(options.password, markId)) then return end
                    veafSpawn.teleport(eventPos, options.name, bypassSecurity)
                elseif options.bomb then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L1(options.password, markId)) then return end
                    veafSpawn.spawnBomb(eventPos, options.radius, options.shells, options.power, options.altitude, options.altitudedelta, options.password)
                elseif options.smoke then
                    veafSpawn.spawnSmoke(eventPos, options.smokeColor, options.radius, options.shells)
                elseif options.flare then
                    if not options.altitude or options.altitude == 0 then
                        options.altitude = 1000
                    end
                    if not options.power or options.power == 0 then
                        options.power = 500
                    end
                    options.power = options.power * 1000
                    veafSpawn.spawnIlluminationFlare(eventPos, options.radius, options.shells, options.power, options.altitude, options.heading, options.distance, options.speed)
                elseif options.signal then
                    veafSpawn.spawnSignalFlare(eventPos, options.radius, options.shells, options.smokeColor)
                elseif options.addDrawing then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L1(options.password, markId)) then return end
                    veafSpawn.addPointToDrawing(eventPos, options.name, options.drawColor, options.drawFillColor, options.type, options.drawArrow)
                elseif options.drawSquare then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L1(options.password, markId)) then return end
                    veafSpawn.drawSquare(eventPos, options.name, options.radius, options.drawColor, options.drawFillColor, options.type)
                elseif options.drawCircle then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L1(options.password, markId)) then return end
                    veafSpawn.drawCircle(eventPos, options.name, options.radius, options.drawColor, options.drawFillColor, options.type)
                elseif options.eraseDrawing then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_L1(options.password, markId)) then return end
                    veafSpawn.eraseDrawing(options.name)
                elseif options.mmFlagOn then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_MM(options.password)) then return end
                    veafSpawn.missionMasterSetFlag(options.name, 1)
                elseif options.mmFlagOff then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_MM(options.password)) then return end
                    veafSpawn.missionMasterSetFlag(options.name, 0)
                elseif options.mmGetFlag then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_MM(options.password)) then return end
                    veafSpawn.missionMasterGetFlag(options.name)
                elseif options.mmRun then
                    -- check security
                    if not (bypassSecurity or veafSecurity.checkSecurity_MM(options.password)) then return end
                    veafSpawn.missionMasterRun(options.name)
                end
                if spawnedGroup then
                    local groupObject = Group.getByName(spawnedGroup)
                    local isStatic = false
                    --group might not have been found because it was a static
                    if not groupObject then
                        isStatic = true
                        groupObject = StaticObject.getByName(spawnedGroup)
                    end
-- LOGGING DISABLED WHEN COMPILING("got groupObject (isStatic=%s) to add group to other platforms : %s", veaf.p(isStatic), veaf.p(groupObject))
                    if groupObject then
                        if not isStatic then
                            --stuff below does not support statics
                            -- make the group combat ready ! well except if the user said otherwise, tweak the AlarmState for some scenarios
-- LOGGING DISABLED WHEN COMPILING("options.disperse=%s", veaf.p(options.disperse))
                            veaf.readyForCombat(groupObject, options.AlarmState, options.disperse)
                            if not route and not routeDone and options.destination then
                                --  make the group go to destination
                                local actualPosition = groupObject:getUnit(1):getPosition().p
                                local route = veaf.generateVehiclesRoute(actualPosition, options.destination, not options.offroad, options.speed, options.patrol, spawnedGroup)
                                mist.goRoute(groupObject, route)
                            elseif route then
                                mist.goRoute(groupObject, route)
                            end
                            -- add the group to the IADS, if there is one
                            if veafSkynet and not veafSkynet.DynamicSpawn and options.skynet then -- only add static stuff like sam groups and sam batteries, not mobile groups and convoys -- and do not do that if DynamicSpawn is active in VeafSkynet
-- LOGGING DISABLED WHEN COMPILING("options.skynet= %s", veaf.p(options.skynet))
                                if type(options.skynet) == "boolean" then --it means options.skynet is true
                                    options.skynet = veafSkynet.defaultIADS[tostring(options.side)]
                                end
-- LOGGING DISABLED WHEN COMPILING("Adding spawned group to skynet, networkName= %s", veaf.p(options.skynet))
                                local networkName = options.skynet
                                if veafSkynet.addGroupToNetwork(networkName, groupObject, options.forceEwr, options.pointDefense, nil, bypassSecurity) then
-- LOGGING DISABLED WHEN COMPILING("Group Added to IADS network")
                                    if not bypassSecurity then trigger.action.outText(string.format("Group added to the IADS named \"%s\"", options.skynet),15) end
                                else
-- LOGGING DISABLED WHEN COMPILING("Could not find IADS network or group is not supported by IADS")
                                    if not bypassSecurity then trigger.action.outText(string.format("Could not add group to the IADS named \"%s\", network not found or group not supported", options.skynet),15) end
                                end
                            end
                        end
                        --but houndElint for example does support statics
                        -- reset the Hound Elint system, if the module is active
                        if veafHoundElint then
                            mist.scheduleFunction(veafHoundElint.addPlatformToSystem, {groupObject, nil, false}, timer.getTime()+veafSpawn.HoundElintAddDelay)
                        end
                        --might need to specify the if a group was static in here so that people on the other end know
                        if spawnedGroups then
                            table.insert(spawnedGroups, spawnedGroup)
                        end
                    end
                end
            end
            return true
        end
    end
    return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the mark text and extract keywords.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafSpawn.convertLaserToFreq(laser)
-- LOGGING DISABLED WHEN COMPILING(string.format("convertLaserToFreq(laser=%s)", tostring(laser)))
    local laser = tonumber(laser)
    if laser and laser >= 1111 and laser <= 1688 then
        local laserB = math.floor((laser - 1000)/100)
        local laserCD = laser - 1000 - laserB*100
        local frequency = tostring(30+laserB+laserCD*0.05)
-- LOGGING DISABLED WHEN COMPILING(string.format("laserB=%s", tostring(laserB)))
-- LOGGING DISABLED WHEN COMPILING(string.format("laserCD=%s", tostring(laserCD)))
-- LOGGING DISABLED WHEN COMPILING(string.format("frequency=%s", tostring(frequency)))
        return frequency
    else
        return nil
    end
end

--- Extract keywords from mark text.
function veafSpawn.markTextAnalysis(text)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn.markTextAnalysis(text=%s)", text))


    -- Option parameters extracted from the mark text.
    local options = {}
    options.unit = false
    options.forceStatic = false -- if true, will force the spawned unit to be a static
    options.group = false
    options.cap = false
    options.farp = false
    options.noFarpMarkers = false
    options.fob = false
    options.type = nil
    options.cargo = false
    options.logistic = false
    options.smoke = false
    options.flare = false
    options.signal = false
    options.bomb = false
    options.destroy = false
    options.teleport = false
    options.convoy = false
    options.role = nil
    options.laserCode = 1688
    options.infantryGroup = false
    options.armoredPlatoon = false
    options.airDefenseBattery = false
    options.transportCompany = false
    options.fullCombatGroup = false
    options.speed = nil
    options.capradius = nil
    options.shells = 1
    options.multiplier = 1
    options.skynet = false -- if true, add to skynet
    options.forceEwr = false -- if true, unit will be added as an IADS EWR
    options.pointDefense = false -- if true, unit will be added as point defense to the closest IADS SAM site
    options.AlarmState = 2 -- Alarm state of the convoy to be spawned, 0 is AUTO, 1 is GREEN, 2 is RED. Note: This option is useful for some vehicules which behave badly in Alarm State RED when spawned such as the Scud or Sa-11 (they deploy and can't drive anywhere). Auto is better suited
    options.disperse = 15 --disperse time of groups if under attack, by default is set to 20s
    options.showMFD = false --option to enable groups to be seen on MFDs
    options.addDrawing = false -- draw a polygon on the map
    options.drawSquare = false -- draw a square on the map
    options.drawCircle = false -- draw a circle on the map
    options.eraseDrawing = false -- erase a polygon from the map
    options.stopDrawing = false -- close a polygon started on the map

    options.drawColor = nil
    options.drawFillColor = nil
    options.drawArrow = nil

    -- spawned group/unit type/alias
    options.name = ""

    -- spawned unit name
    options.unitName = nil

    -- spawned group units spacing
    options.spacing = 5

    options.country = nil
    options.side = nil
    options.altitude = 0
    options.altitudedelta = 0
    options.heading = 0
    options.distance = nil
    options.skill = nil

    -- if true, group is part of a road convoy
    options.isConvoy = false

    -- if true, group is patroling between its spawn point and its destination named point
    options.patrol = false

    -- if true, group is set to not follow roads
    options.offroad = false

    -- if set and convoy is true, send the group to the named point
    options.destination = nil

    -- the size of the generated dynamic groups (platoons, convoys, etc.)
    options.size = math.random(7) + 8

    -- defenses force ; ranges from 1 to 5, 5 being the toughest.
    options.defense = math.random(5)

    -- armor force ; ranges from 1 to 5, 5 being the strongest and most modern.
    options.armor = math.random(5)

    -- bomb power
    options.power = 100

    -- smoke color
    options.smokeColor = trigger.smokeColor.Red

    -- optional cargo smoke
    options.cargoSmoke = false

    -- cargo type
    options.cargoType = "container_cargo"
    options.cargoWeightBias = 2 --weight bias of the cargo, if equal to 0, cargo will be very close to minimum weight, if equal to 5, cargo will be close to maximum

    options.password = nil

    --AFAC spawn option
    options.afac = false
    options.immortal = false

    -- JTAC radio comms
    options.freq = veafSpawn.convertLaserToFreq(options.laserCode)
    options.mod = "fm"

    -- TACAN name and channel
    options.tacanChannel = nil
    options.tacanBand = nil

    -- repeat options
    options.repeatCount = nil
    options.repeatDelay = nil

    -- delayed start option
    options.delayedStart = 0

    -- Check for correct keywords.
    if text:lower():find(veafSpawn.SpawnKeyphrase .. " unit") then
        options.unit = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " afac") then
        options.afac = true
        --default country for the AFAC
        options.country = "USA"
        --default AFAC spawned
        options.name = "mq-9"
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " cap") then
        options.cap = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " group") then
        options.group = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " farp") then
        options.farp = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " fob") then
        options.fob = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " convoy") then
        options.convoy = true
        options.size = 10 -- default the size parameter to 10
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " infantrygroup") then
        options.infantryGroup = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " armorgroup") then
        options.armoredPlatoon = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " samgroup") then
        options.airDefenseBattery = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " transportgroup") then
        options.transportCompany = true
        options.size = math.random(2, 5)
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " combatgroup") then
        options.fullCombatGroup = true
        options.size = 1 -- default the size parameter to 1
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " smoke") then
        options.smoke = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " flare") then
        options.flare = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " signal") then
        options.signal = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " cargo") then
        options.cargo = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " logistic") then
        options.logistic = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " bomb") then
        options.bomb = true
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " jtac") then
        options.role = 'jtac'
        options.unit = true
        -- default country for friendly JTAC: USA
        options.country = "USA"
        -- default name for JTAC
        options.name = "LUV HMMWV Jeep"
        -- default JTAC name (will overwrite previous unit with same name)
        options.unitName = "JTAC1"
    elseif text:lower():find(veafSpawn.SpawnKeyphrase .. " tacan") then
        options.role = 'tacan'
        options.unit = true
        -- default country for friendly tacan: USA
        options.country = "USA"
        -- default name for tacan
        options.name = "TACAN_beacon"
        -- default name (will overwrite previous unit with same name)
        options.unitName = "TACAN TCN"
    elseif text:lower():find(veafSpawn.DestroyKeyphrase) then
        options.destroy = true
    elseif text:lower():find(veafSpawn.TeleportKeyphrase) then
        options.teleport = true
    elseif text:lower():find(veafSpawn.DrawingKeyphrase .. " add") then
        options.addDrawing = true
    elseif text:lower():find(veafSpawn.DrawingKeyphrase .. " erase") then
        options.eraseDrawing = true
    elseif text:lower():find(veafSpawn.DrawingKeyphrase .. " square") then
        options.drawSquare = true
    elseif text:lower():find(veafSpawn.DrawingKeyphrase .. " circle") then
        options.drawCircle = true
    elseif text:lower():find(veafSpawn.MissionMasterKeyphrase .. " flagon") then
        options.mmFlagOn = true
    elseif text:lower():find(veafSpawn.MissionMasterKeyphrase .. " flagoff") then
        options.mmFlagOff = true
    elseif text:lower():find(veafSpawn.MissionMasterKeyphrase .. " getflag") then
        options.mmGetFlag = true
    elseif text:lower():find(veafSpawn.MissionMasterKeyphrase .. " run") then
        options.mmRun = true
    else
        return nil
    end

    -- keywords are split by ","
    local keywords = veaf.split(text, ",")

    for _, keyphrase in pairs(keywords) do
        -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
        local str = veaf.breakString(veaf.trim(keyphrase), " ")
        local key = str[1]
        local val = str[2] or ""

        if key:lower() == "unitname" then
            -- Set name.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword unitname = %s", tostring(val)))
            options.unitName = val
        end

        if key:lower() == "name" then
            -- Set name.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword name = %s", tostring(val)))
            options.name = val
        end

        if (key:lower() == "destination" or key:lower() == "dest") then
            -- Set destination.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword destination = %s", tostring(val)))
            options.destination = val
            options.AlarmState = 0 --since some units will not move when they are told to have an alarm state red, it's best to by default leave it on auto. AI is pretty all knowing anyways, it knows when it should go to red state
            options.spacing = 1 --compress the convoy to not make it extremely long at departure
            options.radius = 1 --convoy spawns on the marker exactly to not have them spawn in trees etc.
        end

        if key:lower() == "isconvoy" then
-- LOGGING DISABLED WHEN COMPILING("Keyword isconvoy found")
            options.convoy = true
        end

        if key:lower() == "patrol" then
-- LOGGING DISABLED WHEN COMPILING("Keyword patrol found")
            options.patrol = true
        end

        if key:lower() == "offroad" then
-- LOGGING DISABLED WHEN COMPILING("Keyword offroad found")
            options.offroad = true
        end

        if key:lower() == "skynet" then
            -- Retreive the name of the IADS you wish to add the spawned group to
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword skynet = %s", tostring(val)))
            options.skynet = val:lower()
            if options.skynet == "" or options.skynet == "true" then
                options.skynet = true
            elseif options.skynet == "false" then
                options.skynet = false
            end
        end

        if key:lower() == "ewr" then
            -- Set force IADS EWR toggle for unit spawn
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword ewr found"))
            options.forceEwr = true
        end

        if key:lower() == "pointdefense" then
            -- Tells IADS to add the spawned SAM to the point defenses of the specified site or to the nearest site
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword pointdefense found"))
            options.pointDefense = true
            if val ~= "" then
-- LOGGING DISABLED WHEN COMPILING(string.format("groupName specified : %s", tostring(val)))
                options.pointDefense = tostring(val)
            end
        end

        --to be placed after the skynet input, SAMs in the skynet network work better if set to AlarmState RED, so AlarmState is equal to 2 if skynet is enabled
        if key:lower() == "alarm" then
            -- Set Alarm State of the unit to be spawned
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword alarm = %s", tostring(val)))
            if (val == "0" or val == "2" or val =="1") and not options.skynet then
                options.AlarmState = tonumber(val)
            end
        end

        if key:lower() == "radius" then
            -- Set name.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword radius = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.radius = nVal
        end

        if key:lower() == "spacing" then
            -- Set spacing.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword spacing = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.spacing = nVal
        end

        if key:lower() == "multiplier" then
            -- Set multiplier.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword multiplier = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.multiplier = nVal
        end

        if key:lower() == "alt" then
            -- Set altitude.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword alt = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.altitude = nVal
        end

        if key:lower() == "altdelta" then
            -- Set altitude delta.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword altdelta = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.altitudedelta = nVal
        end

        if key:lower() == "speed" then
            -- Set speed.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword speed = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.speed = nVal
        end

        if key:lower() == "capradius" then
            -- Set capradius.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword capradius = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.capradius = nVal
        end

        if key:lower() == "shells" then
            -- Set altitude.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword shells = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.shells = nVal
        end

        if key:lower() == "hdg" then
            -- Set heading.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword hdg = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.heading = nVal
        end

        if key:lower() == "heading" then
            -- Set heading.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword heading = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.heading = nVal
        end

        if key:lower() == "country" then
            -- Set country
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword country = %s", tostring(val)))
            options.country = val:upper()
        end

        if key:lower() == "side" then
            -- Set side
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword side = %s", tostring(val)))
            if val:upper() == "BLUE" then
                options.side = veafCasMission.SIDE_BLUE
            else
                options.side = veafCasMission.SIDE_RED
            end
        end

        if key:lower() == "password" then
            -- Unlock the command
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword password", tostring(val)))
            options.password = val
        end

        if key:lower() == "power" then
            -- Set bomb power.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword power = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.power = nVal
        end

        if key:lower() == "laser" then
            -- Set laser code.
-- LOGGING DISABLED WHEN COMPILING(string.format("laser code = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.freq = veafSpawn.convertLaserToFreq(nVal)
            options.laserCode = nVal
        end

        if key:lower() == "freq" then
            -- Set JTAC/AFAC frequency.
-- LOGGING DISABLED WHEN COMPILING(string.format("freq = %s", tostring(val)))
            options.freq = val
        end

        if key:lower() == "mod" then
            -- Set JTAC/AFAC modulation.
-- LOGGING DISABLED WHEN COMPILING(string.format("mod = %s", tostring(val)))
            options.mod = val
        end

        if key:lower() == "band" then
            -- Set TACAN band
-- LOGGING DISABLED WHEN COMPILING(string.format("band = %s", tostring(val)))
            options.tacanBand = val
        end

        if key:lower() == "code" then
            -- Set TACAN code
-- LOGGING DISABLED WHEN COMPILING(string.format("code = %s", tostring(val)))
            options.tacanCode = val
        end

        if key:lower() == "channel" then
            -- Set TACAN channel.
-- LOGGING DISABLED WHEN COMPILING(string.format("channel = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.tacanChannel = nVal
        end

        if key:lower() == "arrow" then
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword arrow = %s", tostring(val)))
            options.drawArrow = true
        end
        if key:lower() == "fill" then
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword fill = %s", tostring(val)))
            options.drawFillColor = val
        end

        if key:lower() == "color" then
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword color = %s", tostring(val)))
            options.drawColor = val
            -- Set smoke color.
            if (val:lower() == "red") then
                options.smokeColor = trigger.smokeColor.Red
            elseif (val:lower() == "green") then
                options.smokeColor = trigger.smokeColor.Green
            elseif (val:lower() == "orange") then
                options.smokeColor = trigger.smokeColor.Orange
            elseif (val:lower() == "blue") then
                options.smokeColor = trigger.smokeColor.Blue
            elseif (val:lower() == "white") then
                options.smokeColor = trigger.smokeColor.White
            end
        end

        if key:lower() == "skill" then
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword skill = %s", tostring(val)))
            options.skill = val
        end

        if key:lower() == "dist" or key:lower() == "distance" then
            -- Set distance.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword distance = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.distance = nVal
        end

        if options.cargo and key:lower() == "name" then
            -- Set cargo type.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword name = %s", tostring(val)))
            options.cargoType = val
        end

        if options.cargo and key:lower() == "weight" then
            -- Set cargo type.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword weight = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            if nVal >= 0 and nVal <= veafSpawn.cargoWeightBiasRange then
                options.cargoWeightBias = nVal
            elseif nVal > veafSpawn.cargoWeightBiasRange then
                options.cargoWeightBias = veafSpawn.cargoWeightBiasRange
            elseif nVal < 0 then
                options.cargoWeightBias = 0
            end
        end

        if key:lower() == "type" then
            -- Set farp type.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword type = %s", tostring(val)))
            options.type = val
        end

        if options.farp and key:lower() == "nofarpmarkers" then
            -- Skip the invisible FARP special vehicles that mark the position of the FARP
-- LOGGING DISABLED WHEN COMPILING("Keyword noFarpMarkers is set")
            options.noFarpMarkers = true
        end

        if options.cargo and key:lower() == "smoke" then
            -- Mark with green smoke.
-- LOGGING DISABLED WHEN COMPILING("Keyword smoke is set")
            options.cargoSmoke = true
        end

        if key:lower() == "size" then
            -- Set size.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword size = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.size = nVal
        end

        if key:lower() == "defense" then
            -- Set defense.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword defense = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            if nVal >= 0 then
                options.defense = nVal
            end
        end

        if key:lower() == "armor" then
            -- Set armor.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword armor = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            if nVal >= 0 then
                options.armor = nVal
            end
        end

        if key:lower() == "repeat" then
            -- Set repeat count.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword repeat = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.repeatCount = nVal
        end

        if key:lower() == "delay" then
            -- Set delay.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword delay = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            options.repeatDelay = nVal
        end

        if key:lower() == "static" then
            -- Set static unit spawn toggle
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword static found"))
            options.forceStatic = true
        end

        if key:lower() == "immortal" then
            -- Set spawned unit to invisible and immortal
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword immortal found"))
            options.immortal = true
        end

        if key:lower() == "delayed" then
            -- Set delayed start on first spawn occurence
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword delayed = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            if nVal >= 0 then
                options.delayedStart = nVal
            else
                options.delayedStart = veafSpawn.MIN_REPEAT_DELAY
            end
        end

        if key:lower() == "showmfd" then
            -- Set hiddenOnMFD option or not
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword showmfd found"))
            options.showMFD = true
        end

        if key:lower() == "disperse" then
            -- Set hiddenOnMFD option or not
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword disperse = %s", tostring(val)))
            local nVal = veaf.getRandomizableNumeric(val)
            if nVal >= 0 then
                options.disperse = nVal
            end
        end
    end

    -- check mandatory parameter "name" for command "group"
    if options.group and not(options.name) then return nil end

    -- check mandatory parameter "name" for command "unit"
    if options.unit and not(options.name) then return nil end

    -- check mandatory parameter "name" for all mission master commands
    if (options.mmFlagOff or options.mmFlagOn or options.mmRun) and not(options.name) then return nil end

return options
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Manage drawings on the map
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafSpawn.drawings = {}
veafSpawn.drawingsMarkers = {}

--- Add a point to a drawing on the map (or start a new drawing)
function veafSpawn.addPointToDrawing(point, name, color, fillColor, lineType, isArrow)
-- LOGGING DISABLED WHEN COMPILING(string.format("addPointToDrawing(point=%s, name=%s, color=%s, fillColor=%s, lineType=%s, isArrow=%s)", veaf.p(point), veaf.p(name), veaf.p(color), veaf.p(fillColor), veaf.p(lineType), veaf.p(isArrow)))
    if not name then
        veaf.loggers.get(veafSpawn.Id):warn("Name is mandatory for drawing commands")
        return
    end
    local drawing = veafSpawn.drawings[name:lower()]
    if not drawing then
        drawing = VeafDrawingOnMap:new():setName(name)
        veafSpawn.drawings[name:lower()] = drawing
    end
    local drawingMarkerId = veafSpawn.drawingsMarkers[name:lower()]
    if drawingMarkerId then
        trigger.action.removeMark(drawingMarkerId)
    end
    drawingMarkerId = veaf.getUniqueIdentifier()
    trigger.action.markToAll(drawingMarkerId, name, point, true)
    veafSpawn.drawingsMarkers[name:lower()] = drawingMarkerId
    if color then
        drawing:setColor(color)
    end
    if lineType then
        drawing:setLineType(lineType)
    end
    if isArrow then
        drawing:setArrow()
    end
    if fillColor then
        drawing:setFillColor(fillColor)
    end

    drawing:addPoint(point)
    drawing:draw()
end

--- Add a circle to the map
function veafSpawn.drawCircle(point, name, radius, color, fillColor, lineType)
-- LOGGING DISABLED WHEN COMPILING(string.format("drawCircle(point=%s, name=%s, radius=%s, color=%s, fillColor=%s, lineType=%s)", veaf.p(point), veaf.p(name), veaf.p(radius), veaf.p(color), veaf.p(fillColor), veaf.p(lineType)))
    if not name then
        veaf.loggers.get(veafSpawn.Id):warn("Name is mandatory for drawing commands")
        return
    end
    local drawing = veafSpawn.drawings[name:lower()]
    if drawing then
        -- erase the old one first
        drawing:erase()
        veafSpawn.drawings[name:lower()] = nil
    end
    drawing = VeafCircleOnMap:new():setName(name)
    drawing:setCenter(point)
    drawing:setRadius(radius or 5000)
    veafSpawn.drawings[name:lower()] = drawing
    local drawingMarkerId = veafSpawn.drawingsMarkers[name:lower()]
    if drawingMarkerId then
        trigger.action.removeMark(drawingMarkerId)
    end
    drawingMarkerId = veaf.getUniqueIdentifier()
    trigger.action.markToAll(drawingMarkerId, name, point, true)
    veafSpawn.drawingsMarkers[name:lower()] = drawingMarkerId
    if color then
        drawing:setColor(color)
    end
    if lineType then
        drawing:setLineType(lineType)
    end
    if fillColor then
        drawing:setFillColor(fillColor)
    end
    drawing:draw()
end

--- Add a square to the map
function veafSpawn.drawSquare(point, name, side, color, fillColor, lineType)
-- LOGGING DISABLED WHEN COMPILING(string.format("drawSquare(point=%s, name=%s, side=%s, color=%s, fillColor=%s, lineType=%s)", veaf.p(point), veaf.p(name), veaf.p(side), veaf.p(color), veaf.p(fillColor), veaf.p(lineType)))
    if not name then
        veaf.loggers.get(veafSpawn.Id):warn("Name is mandatory for drawing commands")
        return
    end
    local drawing = veafSpawn.drawings[name:lower()]
    if drawing then
        -- erase the old one first
        drawing:erase()
        veafSpawn.drawings[name:lower()] = nil
    end
    drawing = VeafSquareOnMap:new():setName(name)
    drawing:setCenter(point)
    drawing:setSide(side or 5000)
    veafSpawn.drawings[name:lower()] = drawing
    local drawingMarkerId = veafSpawn.drawingsMarkers[name:lower()]
    if drawingMarkerId then
        trigger.action.removeMark(drawingMarkerId)
    end
    drawingMarkerId = veaf.getUniqueIdentifier()
    trigger.action.markToAll(drawingMarkerId, name, point, true)
    veafSpawn.drawingsMarkers[name:lower()] = drawingMarkerId
    if color then
        drawing:setColor(color)
    end
    if lineType then
        drawing:setLineType(lineType)
    end
    if fillColor then
        drawing:setFillColor(fillColor)
    end
    drawing:draw()
end

--- Erase drawing from the map
function veafSpawn.eraseDrawing(name)
-- LOGGING DISABLED WHEN COMPILING(string.format("eraseDrawing(name=%s)",veaf.p(name)))
    if not name then
        veaf.loggers.get(veafSpawn.Id):warn("Name is mandatory for drawing commands")
        return
    end
    local drawing = veafSpawn.drawings[name:lower()]
    if not drawing then
        local message = string.format("Could not find a drawing named %s", veaf.p(name))
        veaf.loggers.getSpawn(veaf.Id):warn(message)
        trigger.action.outText(message, 5)
        return
    end
    drawing:erase()
    veafSpawn.drawings[name:lower()] = nil
    local drawingMarkerId = veafSpawn.drawingsMarkers[name:lower()]
    if drawingMarkerId then
        trigger.action.removeMark(drawingMarkerId)
    end
    veafSpawn.drawingsMarkers[name:lower()] = nil
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Group spawn command
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Spawn a specific group at a specific spot
function veafSpawn.doSpawnGroup(spawnSpot, radius, groupDefinition, country, alt, hdg, spacing, groupName, silent, hasDest, hiddenOnMFD, shuffle)
-- LOGGING DISABLED WHEN COMPILING(string.format("doSpawnGroup(country=%s, alt=%s, hdg=%s, spacing=%s, groupName=%s, silent=%s, hasDest=%s, hiddenOnMFD=%s, shuffle=%s)", veaf.p(country), veaf.p(alt), veaf.p(hdg), veaf.p(spacing), veaf.p(groupName), veaf.p(silent), veaf.p(hasDest), veaf.p(hiddenOnMFD), veaf.p(shuffle)))

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=" .. veaf.vecToString(spawnSpot))

    veafSpawn.spawnedUnitsCounter = veafSpawn.spawnedUnitsCounter + 1

    if type(groupDefinition) == "string" then
        local name = groupDefinition
        -- find the desired group in the groups database
        groupDefinition = veafUnits.findGroup(name)
        if not(groupDefinition) then
            veaf.loggers.get(veafSpawn.Id):info("cannot find group "..name)
            if not(silent) then
                trigger.action.outText("cannot find group "..name, 5)
            end
            return nil
        end
    end

-- LOGGING DISABLED WHEN COMPILING("doSpawnGroup: groupDefinition.description=" .. groupDefinition.description)

    local units = {}

    -- place group units on the map
    local group, cells = veafUnits.placeGroup(groupDefinition, spawnSpot, spacing, hdg, hasDest)
    veafUnits.traceGroup(group, cells)

    if not(groupName) then
        groupName = group.groupName .. " #" .. veafSpawn.spawnedUnitsCounter
    end

    if hasDest then
        mist.scheduleFunction(veafUnits.removePathfindingFixUnit,{groupName}, timer.getTime()+veafUnits.delayBeforePathfindingFix)
    end

    for i=1, #group.units do
        local unit = group.units[i]
        local unitType = unit.typeName
        local unitName = groupName .. " / " .. unit.displayName .. " #" .. i

        local spawnPoint = unit.spawnPoint
        if alt > 0 then
            spawnPoint.y = alt
        end

        -- check if position is correct for the unit type
        if not veafUnits.checkPositionForUnit(spawnPoint, unit) then
            veaf.loggers.get(veafSpawn.Id):info("cannot find a suitable position for spawning unit ".. unitType)
            if not(silent) then
                trigger.action.outText("cannot find a suitable position for spawning unit "..unitType, 5)
            end
        else
            local toInsert = {
                    ["x"] = spawnPoint.x,
                    ["y"] = spawnPoint.z,
                    ["alt"] = spawnPoint.y,
                    ["type"] = unitType,
                    ["name"] = unitName,
                    ["speed"] = 0,  -- speed in m/s
                    ["skill"] = "Random",
                    ["heading"] = spawnPoint.hdg
            }

-- LOGGING DISABLED WHEN COMPILING(string.format("toInsert x=%.1f y=%.1f, alt=%.1f, type=%s, name=%s, speed=%d, heading=%d, skill=%s, country=%s", veaf.p(toInsert.x), veaf.p(toInsert.y), veaf.p(toInsert.alt), veaf.p(toInsert.type), veaf.p(toInsert.name), veaf.p(toInsert.speed), veaf.p(mist.utils.toDegree(toInsert.heading)), veaf.p(toInsert.skill), veaf.p(country)))
            table.insert(units, toInsert)
        end
    end

    -- shuffle the group if needed (useful for randomizing convoys)
    -- counter productive with hasDest which to speed up convoys orders all of the units so that they spawn in order and in a line
    -- the best way to execute this shuffle is to create groups with random cells for each unit, TBD
    if shuffle and not hasDest then
        units = veaf.shuffle(units)
    end

    -- actually spawn the group
    if group.naval then
        mist.dynAdd({country = country, category = "SHIP", name = groupName, hidden = false, units = units, hiddenOnMFD = hiddenOnMFD})
    elseif group.air then
        mist.dynAdd({country = country, category = "AIRPLANE", name = groupName, hidden = false, units = units, hiddenOnMFD = hiddenOnMFD})
    else
        mist.dynAdd({country = country, category = "GROUND_UNIT", name = groupName, hidden = false, units = units, hiddenOnMFD = hiddenOnMFD})
    end

    if not(silent) then
        -- message the group spawning
        trigger.action.outText("A " .. group.description .. "("..country..") has been spawned", 5)
    end

    return groupName
end

--- Spawn a FARP
function veafSpawn.spawnFarp(spawnSpot, radius, name, country, farptype, side, hdg, spacing, silent, hiddenOnMFD, noFarpMarkers, code, freq, mod)
-- LOGGING DISABLED WHEN COMPILING("spawnFarp(name=%s, country=%s, farptype=%s, side=%s, hdg=%s, spacing=%s, silent=%s, hiddenOnMFD=%s, noFarpMarkers=%s)",veaf.p(name), veaf.p(country), veaf.p(farptype), veaf.p(side), veaf.p(hdg), veaf.p(spacing), veaf.p(silent), veaf.p(hiddenOnMFD), veaf.p(noFarpMarkers))

    local radius = radius or 0
    local name = name
    local hdg = hdg or 0
    local side = side or 1
    local country = country or "usa"
    local farptype = farptype or ""
    local noFarpMarkers = noFarpMarkers or false

    local spawnPosition = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnPosition=%s", veaf.p(spawnPosition))
    if not name or name == "" then
        local _lat, _lon = coord.LOtoLL(spawnSpot)
-- LOGGING DISABLED WHEN COMPILING("_lat=%s", veaf.p(_lat))
-- LOGGING DISABLED WHEN COMPILING("_lon=%s", veaf.p(_lon))
        local _mgrs = coord.LLtoMGRS(_lat, _lon)
-- LOGGING DISABLED WHEN COMPILING("_mgrs=%s", veaf.p(_mgrs))
        local _UTM = _mgrs.MGRSDigraph .. math.floor(_mgrs.Easting / 1000) .. math.floor(_mgrs.Northing / 1000)
        name = "FARP ".. _UTM:upper() .. "-" .. timer.getTime()
    end

    local _type = "Invisible FARP"
    local _shape = "invisiblefarp"
    if farptype:lower() == "quad" then
        _type = "FARP"
        _shape = "FARPs"
    elseif farptype:lower() == "single" then
        _type = "FARP"
        _shape = "FARP"
    elseif farptype:lower() == "pad" then
        _type = "FARP_SINGLE_01"
        _shape = "FARP_SINGLE_01"
    elseif farptype:lower() == "invisible" then
        _type = "Invisible FARP"
        _shape = "invisiblefarp"
    end

    -- spawn the FARP
    local _farpStatic = {
        ["category"] = "Heliports",
        ["shape_name"] = _shape,
        ["type"] = _type,
        ["unitId"] =  mist.getNextUnitId(),
        ["y"] = spawnPosition.z,
        ["x"] = spawnPosition.x,
        ["groupName"] = name,
        ["name"] = name,
        ["canCargo"] = false,
        ["heading"] = mist.utils.toRadian(hdg),
        ["country"] = country,
        ["coalition"] = side,
        ["dead"] = false,
        ["dynamicSpawn"] = true
    }
    mist.dynAddStatic(_farpStatic)
    local _spawnedFARP = StaticObject.getByName(name)
-- LOGGING DISABLED WHEN COMPILING("_spawnedFARP=%s", veaf.p(_spawnedFARP))

    if _spawnedFARP then
-- LOGGING DISABLED WHEN COMPILING("Spawned the FARP static %s", veaf.p(name))

        -- populate the FARP but make the units invisible to MFDs as they are redundant (FARP already shows if wanted)
        veafGrass.buildFarpUnits(_farpStatic, nil, name, hiddenOnMFD, noFarpMarkers, code, freq, mod)
    end

    return name
end

--- Spawn a FARP
function veafSpawn.spawnFob(spawnSpot, radius, name, country, fobtype, side, hdg, spacing, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING("spawnFob(name=%s, country=%s, fobtype=%s, side=%s, hdg=%s, spacing=%s, silent=%s, hiddenOnMFD=%s)",veaf.p(name), veaf.p(country), veaf.p(fobtype), veaf.p(side), veaf.p(hdg), veaf.p(spacing), veaf.p(silent), veaf.p(hiddenOnMFD))
    local TOWER_DISTANCE = 20
    local BEACON_DISTANCE = 3

    if not veaf.ctld_initialized then
        veaf.loggers.get(veafSpawn.Id):error("spawnFob([%s]): cannot spawn FOB without CTLD!)",veaf.p(name))
        return nil
    end

    local _radius = radius or 0
    local _fobName = name
    local _side = side or 1
    local _country = country or "usa"
    local _fobtype = fobtype or "" -- only a single FOB type in CTLD, yet
	local _hdg = hdg or 0

    local _spawnPosition = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, _radius))
-- LOGGING DISABLED WHEN COMPILING("spawnPosition=%s", veaf.p(_spawnPosition))
    if not _fobName or _fobName == "" then
        local _lat, _lon = coord.LOtoLL(spawnSpot)
-- LOGGING DISABLED WHEN COMPILING("_lat=%s", veaf.p(_lat))
-- LOGGING DISABLED WHEN COMPILING("_lon=%s", veaf.p(_lon))
        local _mgrs = coord.LLtoMGRS(_lat, _lon)
-- LOGGING DISABLED WHEN COMPILING("_mgrs=%s", veaf.p(_mgrs))
        local _UTM = _mgrs.MGRSDigraph .. math.floor(_mgrs.Easting / 1000) .. math.floor(_mgrs.Northing / 1000)
        _fobName = "FOB ".. _UTM:upper()
    end

    -- make name unique
    _fobName = string.format("%s #%i", _fobName, veaf.getUniqueIdentifier())

    -- spawn the FOB buildings
    local _outpost = {
        category = "Fortifications",
        type = "outpost",
        y = _spawnPosition.z,
        x = _spawnPosition.x,
        name = _fobName,
        canCargo = false,
        heading = mist.utils.toRadian(hdg),
        country = _country
    }
    mist.dynAddStatic(_outpost)
    local _fob = StaticObject.getByName(_outpost["name"])

    local _tower = {
        type = "house2arm",
        rate = 100,
        y = _outpost.y + TOWER_DISTANCE * math.sin(mist.utils.toRadian(_hdg)),
        x = _outpost.x + TOWER_DISTANCE * math.cos(mist.utils.toRadian(_hdg)),
        name = _fobName .. " Watchtower #002",
        category = "Fortifications",
        canCargo = false,
        heading = mist.utils.toRadian(hdg),
        country = _country
    }
    mist.dynAddStatic(_tower)

    --make it able to deploy crates and pickup troops
    table.insert(ctld.logisticUnits, _fobName)
    table.insert(ctld.builtFOBS, _fobName)

    -- add the FOB to the named points
    local _namedPoint = _spawnPosition
    _namedPoint.atc = true
    _namedPoint.runways = {}

    -- spawn a beacon
    local _beaconPoint = {
        z = _tower.y + BEACON_DISTANCE * math.sin(mist.utils.toRadian(_hdg)),
        x = _tower.x + BEACON_DISTANCE * math.cos(mist.utils.toRadian(_hdg)),
        y = _spawnPosition.y,
    }
    ctld.beaconCount = ctld.beaconCount + 1
    local _radioBeaconName = "FOB Beacon #" .. ctld.beaconCount
    local _radioBeaconDetails = ctld.createRadioBeacon(_beaconPoint, _side, _country, _radioBeaconName, nil, true)
    ctld.fobBeacons[_fobName] = { vhf = _radioBeaconDetails.vhf, uhf = _radioBeaconDetails.uhf, fm = _radioBeaconDetails.fm }
    if _radioBeaconDetails ~= nil then
        _namedPoint.tacan = string.format("ADF : %.2f KHz - %.2f MHz - %.2f MHz FM", _radioBeaconDetails.vhf / 1000, _radioBeaconDetails.uhf / 1000000, _radioBeaconDetails.fm / 1000000)
-- LOGGING DISABLED WHEN COMPILING("_namedPoint.tacan=%s", veaf.p(_namedPoint.tacan))
    end
    trigger.action.outTextForCoalition(_side, string.format("Finished building FOB %s! Crates and Troops can now be picked up.", _fobName), 10)

	_namedPoint.tower = "No Control"

-- LOGGING DISABLED WHEN COMPILING("_namedPoint=%s", veaf.p(_namedPoint))

	veafNamedPoints.addPoint(_fobName, _namedPoint)

    veaf.loggers.get(veafSpawn.Id):info("Spawned FOB %s", veaf.p(_fobName))
    return _fobName
end

--- Spawn a specific group at a specific spot
function veafSpawn.spawnGroup(spawnSpot, radius, name, country, alt, hdg, spacing, groupName, silent, hasDest, hiddenOnMFD)

-- LOGGING DISABLED WHEN COMPILING("spawnGroup(name=%s, country=%s, alt=%s, hdg=%s, spacing=%s, groupName=%s, silent=%s, hiddenOnMFD=%s)", veaf.p(name), veaf.p(country), veaf.p(alt), veaf.p(hdg), veaf.p(spacing), veaf.p(silent), veaf.p(groupName), veaf.p(hiddenOnMFD))

    local spawnedGroupName = veafSpawn.doSpawnGroup(spawnSpot, radius, name, country, alt, hdg, spacing, groupName, silent, hasDest, hiddenOnMFD)

    return spawnedGroupName
end

function veafSpawn._createDcsUnits(country, units, groupName, hiddenOnMFD, hasDest)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn._createDcsUnits([%s])",country or ""))

    if hasDest then
        mist.scheduleFunction(veafUnits.removePathfindingFixUnit,{groupName}, timer.getTime()+veafUnits.delayBeforePathfindingFix)
    end

    local dcsUnits = {}
    for i=1, #units do
        local unit = units[i]
        local unitType = unit.typeName
        local unitName = groupName .. " / " .. unit.displayName .. " #" .. i
        local spawnPosition = unit.spawnPoint
        local hdg = spawnPosition.hdg or math.random(0, 359)

        -- check if position is correct for the unit type
        if veafUnits.checkPositionForUnit(spawnPosition, unit) then
            local toInsert = {
                    ["x"] = spawnPosition.x,
                    ["y"] = spawnPosition.z,
                    ["alt"] = spawnPosition.y,
                    ["type"] = unitType,
                    ["name"] = unitName,
                    ["speed"] = 0,
                    ["skill"] = "Excellent",
                    ["heading"] = hdg
            }

-- LOGGING DISABLED WHEN COMPILING(string.format("toInsert x=%.1f y=%.1f, alt=%.1f, type=%s, name=%s, speed=%d, heading=%d, skill=%s, country=%s", toInsert.x, toInsert.y, toInsert.alt, toInsert.type, toInsert.name, toInsert.speed, toInsert.heading, toInsert.skill, country ))
            table.insert(dcsUnits, toInsert)
        end
    end

    -- actually spawn groups
    mist.dynAdd({country = country, category = "GROUND_UNIT", name = groupName, hidden = false, units = dcsUnits, hiddenOnMFD = hiddenOnMFD})
end

--- Spawns a dynamic infantry group 
function veafSpawn.spawnInfantryGroup(spawnSpot, radius, country, side, heading, spacing, defense, armor, size, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnInfantryGroup(country=%s, side=%s, heading=%s, spacing=%s, defense=%s, armor=%s, size=%s, silent=%s, hiddenOnMFD=%s)", veaf.p(country), veaf.p(side), veaf.p(heading), veaf.p(spacing), veaf.p(defense), veaf.p(armor), veaf.p(size), veaf.p(silent), veaf.p(hiddenOnMFD)))

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=" .. veaf.vecToString(spawnSpot))

    local groupName = "spawn-" .. math.random(99999) .. " - Infantry Section "
    local group = veafCasMission.generateInfantryGroup(groupName, defense, armor, side, size)
    local group = veafUnits.processGroup(group)
    local groupPosition = veaf.placePointOnLand(spawnSpot)
-- LOGGING DISABLED WHEN COMPILING(string.format("groupPosition = %s",veaf.vecToString(groupPosition)))
    local group, cells = veafUnits.placeGroup(group, groupPosition, spacing, heading)

    -- shuffle the units in the group
    local units = veaf.shuffle(group.units)

    veafSpawn._createDcsUnits(country, units, groupName, hiddenOnMFD)

    if not silent then
        trigger.action.outText("Spawned dynamic infantry group "..groupName, 5)
    end

    return groupName
end

--- Spawns a dynamic armored platoon
function veafSpawn.spawnArmoredPlatoon(spawnSpot, radius, country, side, heading, spacing, defense, armor, size, silent, hasDest, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnArmoredPlatoon(country=%s, side=%s, heading=%s, spacing=%s, defense=%s, armor=%s, size=%s, silent=%s, hasDest=%s, hiddenOnMFD=%s)", veaf.p(country), veaf.p(side), veaf.p(heading), veaf.p(spacing), veaf.p(defense), veaf.p(armor), veaf.p(size), veaf.p(silent), veaf.p(hasDest), veaf.p(hiddenOnMFD)))

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=" .. veaf.vecToString(spawnSpot))

    local groupName = "spawn-" .. math.random(99999) .. " - Armored Platoon "
    local group = veafCasMission.generateArmorPlatoon(groupName, defense, armor, side, size)
    local group = veafUnits.processGroup(group)
    local groupPosition = veaf.placePointOnLand(spawnSpot)
-- LOGGING DISABLED WHEN COMPILING(string.format("groupPosition = %s",veaf.vecToString(groupPosition)))
    local group, cells = veafUnits.placeGroup(group, groupPosition, spacing, heading, hasDest)

    -- shuffle the units in the group
    local units = group.units
    if not(hasDest) then
        units = veaf.shuffle(group.units)
    end

    veafSpawn._createDcsUnits(country, units, groupName, hiddenOnMFD, hasDest)

    if not silent then
        trigger.action.outText("Spawned dynamic armored platoon "..groupName, 5)
    end

    return groupName
end

--- Spawns a dynamic air defense battery
function veafSpawn.spawnAirDefenseBattery(spawnSpot, radius, country, side, heading, spacing, defense, silent, hasDest, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnAirDefenseBattery(country=%s, side=%s, heading=%s, spacing=%s, defense=%s, silent=%s, hasDest=%s, hiddenOnMFD=%s)", veaf.p(country), veaf.p(side), veaf.p(heading), veaf.p(spacing), veaf.p(defense), veaf.p(silent), veaf.p(hasDest), veaf.p(hiddenOnMFD)))

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=" .. veaf.vecToString(spawnSpot))

    local groupName = "spawn-" .. math.random(99999) .. " - Air Defense Battery "
    local group = veafCasMission.generateAirDefenseGroup(groupName, defense, side)
    local group = veafUnits.processGroup(group)
    local groupPosition = veaf.placePointOnLand(spawnSpot)
-- LOGGING DISABLED WHEN COMPILING(string.format("groupPosition = %s",veaf.vecToString(groupPosition)))
    local group, cells = veafUnits.placeGroup(group, groupPosition, spacing, heading, hasDest)

    -- shuffle the units in the group
    local units = group.units
    if not(hasDest) then
        units = veaf.shuffle(group.units)
    end

    veafSpawn._createDcsUnits(country or veaf.getCountryForCoalition(side), units, groupName, hiddenOnMFD, hasDest)

    if not silent then
        trigger.action.outText("Spawned dynamic air defense battery "..groupName, 5)
    end

    return groupName
end

--- Spawns a dynamic transport company
function veafSpawn.spawnTransportCompany(spawnSpot, radius, country, side, heading, spacing, defense, size, silent, hasDest, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnTransportCompany(country=%s, side=%s, heading=%s, spacing=%s, defense=%s, size=%s, silent=%s, hasDest=%s, hiddenOnMFD=%s)", veaf.p(country), veaf.p(side), veaf.p(heading), veaf.p(spacing), veaf.p(defense), veaf.p(size), veaf.p(silent), veaf.p(hasDest), veaf.p(hiddenOnMFD)))

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=" .. veaf.vecToString(spawnSpot))

    local groupName = "spawn-" .. math.random(99999) .. " - Transport Company "
    local group = veafCasMission.generateTransportCompany(groupName, defense, side, size)
    local group = veafUnits.processGroup(group)
    local groupPosition = veaf.placePointOnLand(spawnSpot)
-- LOGGING DISABLED WHEN COMPILING(string.format("groupPosition = %s",veaf.vecToString(groupPosition)))
    local group, cells = veafUnits.placeGroup(group, groupPosition, spacing, heading, hasDest)

    -- shuffle the units in the group
    local units = group.units
    if not(hasDest) then
        units = veaf.shuffle(group.units)
    end

    veafSpawn._createDcsUnits(country, units, groupName, hiddenOnMFD, hasDest)

    if not silent then
        trigger.action.outText("Spawned dynamic transport company "..groupName, 5)
    end

    return groupName
end

--- Spawns a dynamic full combat group composed of multiple platoons
function veafSpawn.spawnFullCombatGroup(spawnSpot, radius, country, side, heading, spacing, defense, armor, size, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnFullCombatGroup(country=%s, side=%s, heading=%s, spacing=%s, defense=%s, armor=%s, size=%s, silent=%s, hiddenOnMFD=%s)", veaf.p(country), veaf.p(side), veaf.p(heading), veaf.p(spacing), veaf.p(defense), veaf.p(armor), veaf.p(size), veaf.p(silent), veaf.p(hiddenOnMFD)))

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=" .. veaf.vecToString(spawnSpot))

    local groupName = "spawn-" .. math.random(99999) .. " - Full Combat Group "
    local groupPosition = veaf.placePointOnLand(spawnSpot)
    local units = veafCasMission.generateCasGroup(groupName, groupPosition, size, defense, armor, spacing, side)

    veafSpawn._createDcsUnits(country, units, groupName, hiddenOnMFD)

    if not silent then
        trigger.action.outText("Spawned full combat group "..groupName, 5)
    end

    return groupName
end

--- Spawn a specific group at a specific spot
function veafSpawn.spawnConvoy(spawnSpot, name, radius, country, side, heading, spacing, speed, patrol, offroad, destination, defense, size, armor, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnConvoy(spawnSpot=[%s], name=[%s], radius=[%s], country=[%s], side=[%s], speed=[%s], patrol=[%s], offroad=[%s], destination=[%s], defense=[%s], size=[%s], armor=[%s], silent=[%s], hiddenOnMFD=[%s])", veaf.p(spawnSpot), veaf.p(name), veaf.p(radius), veaf.p(country), veaf.p(side), veaf.p(speed), veaf.p(patrol), veaf.p(offroad), veaf.p(destination), veaf.p(defense), veaf.p(size), veaf.p(armor), veaf.p(silent), veaf.p(hiddenOnMFD)))

    if not(destination) then
        trigger.action.outText("No destination enterred !", 5)
        return false
    end

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=" .. veaf.vecToString(spawnSpot))

    -- check that destination exists
    local point = nil
    if destination then
        point = veafNamedPoints.getPoint(destination)
    end
    if not(point) then
        local _lat, _lon = veaf.computeLLFromString(destination)
-- LOGGING DISABLED WHEN COMPILING(string.format("_lat=%s",veaf.p(_lat)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_lon=%s",veaf.p(_lon)))
        if _lat and _lon then
            point = coord.LLtoLO(_lat, _lon)
-- LOGGING DISABLED WHEN COMPILING(string.format("point=%s",veaf.p(point)))
        end
    end
    if not(point) then
        trigger.action.outText("A point named "..destination.." cannot be found, and these are not valid coordinates !", 5)
        return false
    end

    local groupUnits = {}
    groupUnits.units = {}
    local groupId = math.random(99999)
    local groupName = name
    if not groupName or groupName == "" then
        groupName = "convoy-" .. groupId
    end

    -- generate the transport vehicles and air defense
    if size and size > 0 then -- this is only for reading clarity sake
        -- generate the group
        local group = veafCasMission.generateTransportCompany(groupId, defense, side, size)

        -- process the group 
        local group = veafUnits.processGroup(group)

        -- add the units to the global units list
        for _,u in pairs(group.units) do
            table.insert(groupUnits.units, u)
        end
    end

    -- generate the armored vehicles
    if armor and armor > 0 then
        -- generate the group
        local group = veafCasMission.generateArmorPlatoon(groupId, defense, armor, side, size / 2)

        -- process the group 
        local group = veafUnits.processGroup(group)

        -- add the units to the global units list
        for _,u in pairs(group.units) do
            table.insert(groupUnits.units, u)
        end
    end

    if groupUnits.units then
        -- place its units
        local groupUnits, cells = veafUnits.placeGroup(groupUnits, veaf.placePointOnLand(spawnSpot), spacing, heading, true)
        veafUnits.traceGroup(groupUnits, cells)

        -- shuffle the units in the convoy
        --disabled the shuffle to not have interractions with the line spawn put in place for faster departure times, which shuffles units anyways
        --units = veaf.shuffle(units)

        veafSpawn._createDcsUnits(country, groupUnits.units, groupName, hiddenOnMFD, true)

        local route = veaf.generateVehiclesRoute(spawnSpot, destination, not offroad, speed, patrol, groupName)
        veafSpawn.spawnedConvoys[groupName] = {route=route, name=groupName}

        --  make the group go to destination
-- LOGGING DISABLED WHEN COMPILING("make the group go to destination : ".. groupName)
        mist.goRoute(groupName, route)

        if not silent then
            trigger.action.outText("Spawned convoy "..groupName, 5)
        end
    end

    return groupName
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Unit spawn command
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Spawn a specific unit at a specific spot
-- @param position spawnPosition
-- @param string name
-- @param string country
-- @param int speed
-- @param int alt
-- @param int speed
-- @param int hdg (0..359)
-- @param string unitName (callsign)
-- @param string role (ex: jtac)
-- @param boolean static (is the unit force to spawn as a static unit)
-- @param integer code (starts at 1111, laser code if jtac)
-- @param string freq (frequency if JTAC in MHz with . separator)
-- @param boolean silent (mutes messages to players except errors)
-- @param boolean hiddenOnMFD
function veafSpawn.spawnUnit(spawnPosition, radius, name, country, alt, hdg, unitName, role, static, code, freq, mod, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnUnit(name = %s, country=%s, alt=%d, hdg=%d, unitName=%s, role=%s, static=%s, code=%s, freq=%s, mod=%s, silent=%s, hiddenOnMFD=%s)", veaf.p(name), veaf.p(country), veaf.p(alt), veaf.p(hdg), veaf.p(unitName), veaf.p(role), veaf.p(static), veaf.p(code), veaf.p(freq), veaf.p(mod), veaf.p(silent), veaf.p(hiddenOnMFD)))

    veafSpawn.spawnedUnitsCounter = veafSpawn.spawnedUnitsCounter + 1

    -- find the desired unit in the groups database
    local unit = veafUnits.findUnit(name)

    if not(unit) then
        veaf.loggers.get(veafSpawn.Id):info("cannot find unit "..name)
        trigger.action.outText("cannot find unit "..name, 5)
        return
    end

    -- cannot spawn planes or helos yet [TODO], however spawning them as a static is fine
    if unit.air and not static then
        veaf.loggers.get(veafSpawn.Id):info("Air units cannot be spawned at the moment (work in progress)")
        trigger.action.outText("Air units cannot be spawned at the moment (work in progress)", 5)
        return
    end

    local units = {}
    local groupName = nil

-- LOGGING DISABLED WHEN COMPILING("spawnUnit unit = " .. unit.displayName .. ", dcsUnit = " .. tostring(unit.typeName))

    if role == "jtac" then
        local name = "JTAC " .. tostring(code):sub(1,1) .. " " .. tostring(code):sub(2,2) .. " " .. tostring(code):sub(3,3) .. " " .. tostring(code):sub(4,4)
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s", tostring(name)))
        groupName = name
        unitName = name
    elseif role == "tacan" then
        local name = "TACAN " .. tostring(freq)..tostring(mod)
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s", tostring(name)))
        groupName = name
        unitName = name
    else
      groupName = veafSpawn.RedSpawnedUnitsGroupName .. " #" .. veafSpawn.spawnedUnitsCounter
      if not unitName then
        unitName = unit.displayName .. " #" .. veafSpawn.spawnedUnitsCounter
      end
    end

-- LOGGING DISABLED WHEN COMPILING("groupName="..groupName)
-- LOGGING DISABLED WHEN COMPILING("unitName="..unitName)

    local spawnSpot = nil
    local nbTries = 25
    repeat
        spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnPosition, radius))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnUnit: spawnSpot  x=%.1f y=%.1f, z=%.1f", spawnSpot.x, spawnSpot.y, spawnSpot.z))
        if alt > 0 then
            spawnSpot.y = alt
        end
        if not veafUnits.checkPositionForUnit(spawnSpot, unit) then
-- LOGGING DISABLED WHEN COMPILING("finding another spawnSpot for unit %s, remaining tries #%s", unit.displayName, nbTries)
            spawnSpot = nil
            nbTries = nbTries - 1
        end
    until spawnSpot or nbTries <= 0

    if not spawnSpot then
        veaf.loggers.get(veafSpawn.Id):info("cannot find a suitable position for spawning unit "..unit.displayName)
        trigger.action.outText("cannot find a suitable position for spawning unit "..unit.displayName, 5)
        return
    else
        local toInsert = {}
        local effectPreset = nil
        local effectTransparency = nil
        local shapeName = nil

        if unit.static or static then

            if unit.category then
                if unit.category == "Heliport" then
                    unit.category = "Heliports"
                end
                -- if unit.category == "Effect" then
                --     unit.category = "Effects"
                --     effectPreset = 2
                --     effectTransparency = 1
                --     shapeName = "medium smoke and fire"
                -- end
            end

            groupName = unitName --this name here will be used for reference by DCS, since we return groupName for other scripts to do their thing, this must be the unitName

            toInsert = {
                ["x"] = spawnSpot.x,
                ["y"] = spawnSpot.z,
                ["alt"] = spawnSpot.y,
                ["type"] = unit.typeName,
                ["name"] = groupName,
                ["category"] = unit.category,
                ["heading"] = mist.utils.toRadian(hdg),
                -- ["effectTransparency"] = effectTransparency,
                -- ["effectPreset"] = effectPreset,
                -- ["shapeName"] = shapeName,
            }
        else
            toInsert = {
                ["x"] = spawnSpot.x,
                ["y"] = spawnSpot.z,
                ["alt"] = spawnSpot.y,
                ["type"] = unit.typeName,
                ["name"] = unitName,
                ["speed"] = 0,
                ["skill"] = "Random",
                ["heading"] = mist.utils.toRadian(hdg),
            }
        end

        table.insert(units, toInsert)
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("unitData = %s", veaf.p(units)))

    -- actually spawn the unit
    if unit.static or static then --if the unit was forced to spawn as a static it could still be an air or a naval unit so this check goes first
-- LOGGING DISABLED WHEN COMPILING("Spawning STATIC")
        mist.dynAddStatic({country = country, groupName = groupName, units = units, hiddenOnMFD = hiddenOnMFD})
        --groupName = nil --statics do not have a group name, you must set groupName to nil to avoid other scripts interacting
    elseif unit.air then
-- LOGGING DISABLED WHEN COMPILING("Spawning AIRPLANE")
        mist.dynAdd({country = country, category = "PLANE", groupName = groupName, units = units, hiddenOnMFD = hiddenOnMFD})
    elseif unit.naval then
-- LOGGING DISABLED WHEN COMPILING("Spawning SHIP")
        mist.dynAdd({country = country, category = "SHIP", groupName = groupName, units = units, hiddenOnMFD = hiddenOnMFD})
    else
-- LOGGING DISABLED WHEN COMPILING("Spawning GROUND_UNIT")
        mist.dynAdd({country = country, category = "GROUND_UNIT", groupName = groupName, units = units, hiddenOnMFD = hiddenOnMFD})
    end

    if role == "jtac" and not static then
        -- JTAC needs to be invisible and immortal
        local _setImmortal = {
            id = 'SetImmortal',
            params = {
                value = true
            }
        }
        -- invisible to AI, Shagrat
        local _setInvisible = {
            id = 'SetInvisible',
            params = {
                value = true
            }
        }

        local spawnedGroup = Group.getByName(groupName)
        local controller = spawnedGroup:getController()
        Controller.setCommand(controller, _setImmortal)
        Controller.setCommand(controller, _setInvisible)

        -- start lasing 
        if ctld then
            ctld.cleanupJTAC(groupName)
            local radioData = {freq=freq, mod=mod, name=groupName}
            veafSpawn.JTACAutoLase(groupName, code, radioData)
        end

    elseif role == "tacan" and not static then
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s", tostring(name)))
-- LOGGING DISABLED WHEN COMPILING(string.format("freq=%s", tostring(freq)))
        local mod = string.upper(mod) or "X"
-- LOGGING DISABLED WHEN COMPILING(string.format("mod=%s", tostring(mod)))
        local txFreq = (1025 + freq - 1) * 1000000
        local rxFreq = (962 + freq - 1) * 1000000
        if (freq < 64 and mod == "Y") or (freq >= 64 and mod == "X") then
            rxFreq = (1088 + freq - 1) * 1000000
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("txFreq=%s", tostring(txFreq)))
-- LOGGING DISABLED WHEN COMPILING(string.format("rxFreq=%s", tostring(rxFreq)))

        local command = {
            id = 'ActivateBeacon',
            params = {
                type = 4,
                system = 18,
                callsign = code or "TCN",
                frequency = rxFreq,
                AA = false,
                channel = freq,
                bearing = true,
                modeChannel = mod,
            }
        }

-- LOGGING DISABLED WHEN COMPILING(string.format("setting %s", veaf.p(command)))
        local spawnedGroup = Group.getByName(groupName)
        local controller = spawnedGroup:getController()
        controller:setCommand(command)
-- LOGGING DISABLED WHEN COMPILING(string.format("done setting command"))
    end

    -- message the unit spawning
-- LOGGING DISABLED WHEN COMPILING(string.format("message the unit spawning"))
    if (role == "jtac") or not silent then
        local message = "A " .. unit.displayName .. " ("..country..") has been spawned"
        if role == "jtac" and not static then
            message = "JTAC spawned, lasing on "..code..", available on "..freq.." "..mod
        end
-- LOGGING DISABLED WHEN COMPILING(message)
        trigger.action.outText(message, 15)
    end

    return groupName
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Cargo spawn command
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Spawn a specific cargo at a specific spot
function veafSpawn.spawnCargo(spawnSpot, radius, cargoType, country, weightBias, cargoSmoke, unitName, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING("spawnCargo(cargoType = " .. cargoType ..")")

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnCargo: spawnSpot  x=%.1f y=%.1f, z=%.1f", spawnSpot.x, spawnSpot.y, spawnSpot.z))

    return veafSpawn.doSpawnCargo(spawnSpot, radius, cargoType, country, weightBias, unitName, cargoSmoke, silent, hiddenOnMFD)
end

--- Spawn a logistic unit for CTLD at a specific spot
function veafSpawn.spawnLogistic(spawnSpot, radius, country, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING("spawnLogistic()")

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnLogistic: spawnSpot  x=%.1f y=%.1f, z=%.1f", spawnSpot.x, spawnSpot.y, spawnSpot.z))

    local unitName = veafSpawn.doSpawnStatic(spawnSpot, radius, veafSpawn.LogisticUnitCategory, veafSpawn.LogisticUnitType, country, nil, false, true, hiddenOnMFD)

    if unitName then
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnLogistic: inserting %s into CTLD logistics list", unitName))
        if ctld then
            table.insert(ctld.logisticUnits, unitName)
        end

        -- message the unit spawning
        if not silent then
            local message = "Logistic unit " .. unitName .. " has been spawned and was added to CTLD."
            trigger.action.outText(message, 15)
        end
        return unitName
    else
        local message = "Logistic unit could not be spawned"
        trigger.action.outText(message, 15)
        return
    end
end

--- Spawn a specific cargo at a specific spot
function veafSpawn.doSpawnCargo(spawnSpot, radius, cargoType, country, weightBias, unitName, cargoSmoke, silent, hiddenOnMFD)
    local weightBias = weightBias or 2
    local radius = radius or 0
-- LOGGING DISABLED WHEN COMPILING("spawnCargo(cargoType = " .. cargoType ..")")

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnCargo: spawnSpot  x=%.1f y=%.1f, z=%.1f", spawnSpot.x, spawnSpot.y, spawnSpot.z))

    local units = {}

    local spawnPosition = veaf.findPointInZone(spawnSpot)

    -- check spawned position validity
    if spawnPosition == nil then
        veaf.loggers.get(veafSpawn.Id):info("cannot find a suitable position for spawning cargo "..cargoType)
        trigger.action.outText("cannot find a suitable position for spawning cargo "..cargoType, 5)
        return
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("spawnCargo: spawnPosition  x=%.1f y=%.1f", spawnPosition.x, spawnPosition.y))

    -- compute cargo weight
    local cargoWeight = 250
    local unit = veafUnits.findDcsUnit(cargoType)
    if not unit then
        cargoType = cargoType.. "_cargo"
        unit = veafUnits.findDcsUnit(cargoType)
    end
    if unit then
        if unit.type then
            cargoType = unit.type
        else
            veaf.loggers.get(veafSpawn.Id):info("could not find cargo type named ".. veaf.p(cargoType))
            trigger.action.outText("could not find cargo type named ".. veaf.p(cargoType), 15)
            return
        end

-- LOGGING DISABLED WHEN COMPILING(string.format("weightBias=%s", veaf.p(weightBias)))
        if unit.desc and unit.desc.minMass and unit.desc.maxMass then
            local weightScaleRange = veafSpawn.cargoWeightBiasRange + 1
            local massDelta = unit.desc.maxMass - unit.desc.minMass
            if massDelta < 0 then --never can be too careful around DCS
                local temp = unit.desc.maxMass
                unit.desc.maxMass = unit.desc.minMass
                unit.desc.minMass = temp
                massDelta = math.abs(massDelta)
            end
            local minMass = unit.desc.minMass + weightBias * massDelta / weightScaleRange
            local maxMass =  unit.desc.minMass + (weightBias+1) * massDelta / weightScaleRange
-- LOGGING DISABLED WHEN COMPILING(string.format("cargo minMass=%s, cargo maxMass=%s", veaf.p(minMass), veaf.p(maxMass)))
            cargoWeight = math.random(minMass,maxMass)
        elseif unit.defaultMass then
            local BiasOffset = -math.floor(veafSpawn.cargoWeightBiasRange / 2)
            local weightBiasCentered = weightBias + BiasOffset
            local cargoWeightBiasScaleMin = BiasOffset
            local cargoWeightBiasScaleMax = veafSpawn.cargoWeightBiasRange + BiasOffset
            local weightBiasMax = weightBiasCentered + 1
            local weightBiasMin = weightBiasCentered

            cargoWeight = unit.defaultMass
-- LOGGING DISABLED WHEN COMPILING(string.format("cargo defaultMass=%s", veaf.p(cargoWeight)))
            local minMass = cargoWeight + weightBiasMin * cargoWeight / (2*cargoWeightBiasScaleMax)
            local maxMass = cargoWeight + weightBiasMax * cargoWeight / (2*cargoWeightBiasScaleMax)
-- LOGGING DISABLED WHEN COMPILING(string.format("cargo minMass=%s, cargo maxMass=%s", veaf.p(minMass), veaf.p(maxMass)))
            cargoWeight = math.random(minMass,maxMass)
        end
        if cargoWeight then
-- LOGGING DISABLED WHEN COMPILING(string.format("cargo mass=%s", veaf.p(cargoWeight)))

            if not(unitName) then
                veafSpawn.spawnedUnitsCounter = veafSpawn.spawnedUnitsCounter + 1
                unitName = unit.name .. " #" .. veafSpawn.spawnedUnitsCounter
            end

            -- create the cargo
            local cargoTable = {
                type = cargoType,
                country = country,
                category = 'Cargos',
                name = unitName,
                x = spawnPosition.x,
                y = spawnPosition.y,
                canCargo = true,
                mass = cargoWeight,
                hiddenOnMFD = hiddenOnMFD,
            }

            mist.dynAddStatic(cargoTable)

            -- smoke the cargo if needed
            if cargoSmoke then
                local smokePosition={x=spawnPosition.x + mist.random(10,20), y=0, z=spawnPosition.y + mist.random(10,20)}
                local height = veaf.getLandHeight(smokePosition)
                smokePosition.y = height
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnCargo: smokePosition  x=%.1f y=%.1f z=%.1f", smokePosition.x, smokePosition.y, smokePosition.z))
                veafSpawn.spawnSmoke(smokePosition, trigger.smokeColor.Green)
                for i = 1, 10 do
-- LOGGING DISABLED WHEN COMPILING("Signal flare 1 at " .. timer.getTime() + i*7)
                    mist.scheduleFunction(veafSpawn.spawnSignalFlare, {smokePosition, nil, nil, trigger.flareColor.Red}, timer.getTime() + i*3)
                end
            end

            -- message the unit spawning
            local message = "Cargo " .. unitName .. " weighting " .. cargoWeight .. " kg has been spawned"
            if cargoSmoke then
                message = message .. ". It's marked with green smoke and red flares"
            end
            if not(silent) then trigger.action.outText(message, 15) end
        end
    else
        veaf.loggers.get(veafSpawn.Id):info("could not find cargo type named ".. veaf.p(cargoType))
        trigger.action.outText("could not find cargo type named ".. veaf.p(cargoType), 15)
        return
    end
    return unitName
end


--- Spawn a specific static at a specific spot
function veafSpawn.doSpawnStatic(spawnSpot, radius, staticCategory, staticType, country, unitName, smoke, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING("doSpawnStatic(staticCategory = " .. staticCategory ..")")
-- LOGGING DISABLED WHEN COMPILING("doSpawnStatic(staticType = " .. staticType ..")")

    local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING(string.format("doSpawnStatic: spawnSpot  x=%.1f y=%.1f, z=%.1f", spawnSpot.x, spawnSpot.y, spawnSpot.z))

    local units = {}

    local spawnPosition = veaf.findPointInZone(spawnSpot, 50, false)

    -- check spawned position validity
    if spawnPosition == nil then
        veaf.loggers.get(veafSpawn.Id):info("cannot find a suitable position for spawning static "..staticType)
        if not(silent) then trigger.action.outText("cannot find a suitable position for spawning static "..staticType, 5) end
        return
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("doSpawnStatic: spawnPosition  x=%.1f y=%.1f", spawnPosition.x, spawnPosition.y))

    local unit = veafUnits.findDcsUnit(staticType)
    if unit then
        if not(unitName) then
            veafSpawn.spawnedUnitsCounter = veafSpawn.spawnedUnitsCounter + 1
            unitName = unit.name .. " #" .. veafSpawn.spawnedUnitsCounter
        end

        -- create the static
        local staticTable = {
            category = staticCategory,
            type = staticType,
            country = country,
            name = unitName,
            x = spawnPosition.x,
            y = spawnPosition.y,
            hiddenOnMFD = hiddenOnMFD,
        }

        mist.dynAddStatic(staticTable)

        -- smoke if needed
        if smoke then
            local smokePosition={x=spawnPosition.x + mist.random(10,20), y=0, z=spawnPosition.y + mist.random(10,20)}
            local height = veaf.getLandHeight(smokePosition)
            smokePosition.y = height
-- LOGGING DISABLED WHEN COMPILING(string.format("doSpawnStatic: smokePosition  x=%.1f y=%.1f z=%.1f", smokePosition.x, smokePosition.y, smokePosition.z))
            veafSpawn.spawnSmoke(smokePosition, trigger.smokeColor.Green)
            for i = 1, 10 do
-- LOGGING DISABLED WHEN COMPILING("Signal flare 1 at " .. timer.getTime() + i*7)
                mist.scheduleFunction(veafSpawn.spawnSignalFlare, {smokePosition, nil, nil, trigger.flareColor.Red}, timer.getTime() + i*3)
            end
        end

        -- message the unit spawning
        local message = "Static " .. unitName .. " has been spawned"
        if smoke then
            message = message .. ". It's marked with green smoke and red flares"
        end
        if not(silent) then trigger.action.outText(message, 5) end
    end
    return unitName
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Smoke and Flare commands
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- trigger an explosion at the marker area
function veafSpawn.spawnBomb(spawnSpot, radius, shells, power, altitude, altitudedelta, password)
-- LOGGING DISABLED WHEN COMPILING("spawnBomb(power=" .. power ..")")

    local shellTime = 0
    local shellDelay = 0
    for shell=1,shells do
        local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=%s", spawnSpot)
-- LOGGING DISABLED WHEN COMPILING("altitude=%s", altitude)
        if altitude and altitude > 0 then
            spawnSpot.y = altitude + altitudedelta * ((math.random(100)-50)/100)
            shellDelay = veafSpawn.FlakingInterval
        else
            shellDelay = veafSpawn.ShellingInterval
        end
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=%s", spawnSpot)

        local shellDelay = shellDelay * (math.random(100) + 30)/100
        local shellPower = power * (math.random(100) + 30)/100
        -- check security
        if not veafSecurity.checkPassword_L0(password) then
            if shellPower > 1000 then shellPower = 1000 end
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("shell #%d : shellTime=%d, shellDelay=%d, power=%d", shell, shellTime, shellDelay, shellPower))
        mist.scheduleFunction(trigger.action.explosion, {spawnSpot, power}, timer.getTime() + shellTime)
        shellTime = shellTime + shellDelay
    end
end

--- add a smoke marker over the marker area
function veafSpawn.spawnSmoke(spawnSpot, color, radius, shells)
-- LOGGING DISABLED WHEN COMPILING("spawnSmoke(color=%s",veaf.p(color))
    local radius = radius or 50
    local shells = shells or 1
-- LOGGING DISABLED WHEN COMPILING("radius=%s", veaf.p(radius))
-- LOGGING DISABLED WHEN COMPILING("shells=%s", veaf.p(shells))

    local shellTime = 0
    for shell=1,shells do
        local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnSpot=%s", veaf.vecToString(spawnSpot)))

        local shellDelay = veafSpawn.ShellingInterval * (math.random(100) + 30)/100
-- LOGGING DISABLED WHEN COMPILING(string.format("shell #%d : shellTime=%d, shellDelay=%d", shell, shellTime, shellDelay))
        if shells > 1 then
            -- add a small explosion under the smoke to simulate smoke shells
            mist.scheduleFunction(trigger.action.explosion, {spawnSpot, 1}, timer.getTime() + shellTime-1)
        end
        mist.scheduleFunction(trigger.action.smoke, {spawnSpot, color}, timer.getTime() + shellTime)
        shellTime = shellTime + shellDelay
    end
end

--- add a signal flare over the marker area
function veafSpawn.spawnSignalFlare(spawnSpot, radius, shells, color)
-- LOGGING DISABLED WHEN COMPILING("spawnSignalFlare(color = " .. color ..")")

    local shellTime = 0
    for shell=1,shells do
        local spawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(spawnSpot, radius))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnSpot=%s", veaf.vecToString(spawnSpot)))

        local shellDelay = veafSpawn.ShellingInterval * (math.random(100) + 30)/100
        local azimuth = math.random(359)
-- LOGGING DISABLED WHEN COMPILING(string.format("shell #%d : shellTime=%d, shellDelay=%d", shell, shellTime, shellDelay))
        mist.scheduleFunction(trigger.action.signalFlare, {spawnSpot, color, azimuth}, timer.getTime() + shellTime)
        shellTime = shellTime + shellDelay
    end
end

--- add an illumination flare over the target area
function veafSpawn.spawnIlluminationFlare(spawnSpot, radius, steps, power, height, heading, distance, speed)
    local radius = radius or 50
    local steps = steps or 1
    local power = power or 10
    local height = height or 500

-- LOGGING DISABLED WHEN COMPILING("spawnIlluminationFlare()")
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=%s", veaf.p(spawnSpot))
-- LOGGING DISABLED WHEN COMPILING("radius=%s", veaf.p(radius))
-- LOGGING DISABLED WHEN COMPILING("steps=%s", veaf.p(steps))
-- LOGGING DISABLED WHEN COMPILING("power=%s", veaf.p(power))
-- LOGGING DISABLED WHEN COMPILING("height=%s", veaf.p(height))
-- LOGGING DISABLED WHEN COMPILING("heading=%s", veaf.p(heading))
-- LOGGING DISABLED WHEN COMPILING("distance=%s", veaf.p(distance))


    local cosHeading
    local sinHeading
    local stepDistance
    if heading then
        if distance then
            distance = distance * 1852 -- meters
            stepDistance = distance / (steps - 1)
        elseif speed then
            speed = speed / 1.94384 -- m/s
            stepDistance = speed * veafSpawn.IlluminationShellingInterval
        end
        local headingRad = mist.utils.toRadian(heading)
        cosHeading = math.cos(headingRad)
        sinHeading = math.sin(headingRad)
    end

    local stepTime = 0
    for step=1, steps do
        local stepDelay = veafSpawn.IlluminationShellingInterval * (math.random(100, 130)-15)/100
        local newSpawnSpot = mist.utils.deepCopy(spawnSpot)
        if stepDistance then
            newSpawnSpot.x = spawnSpot.x + stepDistance * (step - 1) * cosHeading
            newSpawnSpot.z = spawnSpot.z + stepDistance * (step - 1) * sinHeading
        end
        local shellsPerStep = math.random(5, 10)
-- LOGGING DISABLED WHEN COMPILING(string.format("step #%d : stepTime=%d, shellDelay=%d", step, stepTime, stepDelay))
        for shell=1, shellsPerStep do
            local shellDelay = shell/4 + (math.random(100, 150)-25)/100
            local shellHeight = height * (math.random(100, 130)-15)/100
            local shellPower = power * (math.random(100, 130)-15)/100
            local newSpawnSpot = veaf.placePointOnLand(mist.getRandPointInCircle(newSpawnSpot, radius))
            newSpawnSpot.y = veaf.getLandHeight(newSpawnSpot) + shellHeight
-- LOGGING DISABLED WHEN COMPILING(string.format("shell #%d : shellHeight=%d, shellPower=%d", shell, shellHeight, shellPower))
            local time = timer.getTime() + stepTime + shellDelay
            -- add a small explosion under the flare to simulate flare shells
            mist.scheduleFunction(trigger.action.explosion, {newSpawnSpot, 0.1}, time)
            mist.scheduleFunction(trigger.action.illuminationBomb, {newSpawnSpot, shellPower}, time)
        end
        stepTime = stepTime + stepDelay
    end
end

--- FLAK-related constants
veafSpawn.NB_OF_FLAKS_AT_DENSITY_1 = 30
veafSpawn.DEFAULT_FLAK_CLOUD_SIZE = 30
veafSpawn.DEFAULT_FLAK_POWER = 1
veafSpawn.DEFAULT_FLAK_REPEAT_DELAY = 0.2
veafSpawn.DEFAULT_FLAK_FIRE_DELAY = 0.1

function veafSpawn.destroyObjectWithFlak(object, power, density)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn.destroyObjectWithFlak(%s, %s, %s)", veaf.p(power), veaf.p(power), veaf.p(density)))
-- LOGGING DISABLED WHEN COMPILING(string.format("object=%s", veaf.p(object)))
    local _power = power or veafSpawn.DEFAULT_FLAK_POWER
    local _density = density or 1

    if object and object:isExist() then
        local point = object:getPoint()
        local positionForFlak = mist.vec.add(point, mist.vec.scalarMult(object:getVelocity(), veafSpawn.DEFAULT_FLAK_FIRE_DELAY))
        local nbFlaks = veafSpawn.NB_OF_FLAKS_AT_DENSITY_1 * _density
-- LOGGING DISABLED WHEN COMPILING(string.format("firing %d flak shells", nbFlaks))
        for i = 1, nbFlaks do
            local flakPoint = {
                x = point.x + (veafSpawn.DEFAULT_FLAK_CLOUD_SIZE * math.random(-100,100) / 100),
                y = point.y + (veafSpawn.DEFAULT_FLAK_CLOUD_SIZE * math.random(-100,100) / 100),
                z = point.z + (veafSpawn.DEFAULT_FLAK_CLOUD_SIZE * math.random(-100,100) / 100)
            }
-- LOGGING DISABLED WHEN COMPILING(string.format("flakPoint=%s", veaf.p(flakPoint)))
            trigger.action.explosion(flakPoint, _power)
        end

        -- reschedule to check if the object is destroyed
-- LOGGING DISABLED WHEN COMPILING(string.format("reschedule to check if the object is destroyed"))
        mist.scheduleFunction(veafSpawn.destroyObjectWithFlak, {object, power, power, density}, timer.getTime() + veafSpawn.DEFAULT_FLAK_REPEAT_DELAY)
    end
end

--- destroy unit(s)
function veafSpawn.destroy(spawnSpot, radius, unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("destroy(radius=%s, unitName=%s)", tostring(radius), tostring(unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnSpot=%s", veaf.p(spawnSpot)))
    if unitName then
        -- destroy a specific unit
        local c = Unit.getByName(unitName)
        if c then
-- LOGGING DISABLED WHEN COMPILING("destroy a specific unit")
            Unit.destroy(c)
        end

        -- or a specific static
        c = StaticObject.getByName(unitName)
        if c then
-- LOGGING DISABLED WHEN COMPILING("destroy a specific static")
            StaticObject.destroy(c)
        end

        -- or a specific group
        c = Group.getByName(unitName)
        if c then
-- LOGGING DISABLED WHEN COMPILING("destroy a specific group")
            Group.destroy(c)
        end
    else
        -- radius based destruction
-- LOGGING DISABLED WHEN COMPILING("radius based destruction")
        local units = veaf.findUnitsInCircle(spawnSpot, radius or 150, true)
-- LOGGING DISABLED WHEN COMPILING(string.format("units=%s", veaf.p(units)))
        if units then
            for name, _ in pairs(units) do
                -- try and find a  unit
                local unit = Unit.getByName(name)
                if unit then
                    Unit.destroy(unit)
                else
                    unit = StaticObject.getByName(name)
                    if unit then
                        StaticObject.destroy(unit)
                    end
                end
            end
        end
    end
end

--- teleport group
function veafSpawn.teleport(spawnSpot, name, silent)
-- LOGGING DISABLED WHEN COMPILING("teleport(name = " .. name ..")")
    local vars = { groupName = name, point = spawnSpot, action = "teleport" }
    local grp = mist.teleportToPoint(vars)
    if not silent then
        if grp then
            trigger.action.outText("Teleported group "..name, 5)
        else
            trigger.action.outText("Cannot teleport group : "..name, 5)
        end
    end
end

function veafSpawn._findClosestConvoy(unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn._findClosestConvoy(%s)",unitName))
    local closestConvoyName = nil
    local minDistance = 99999999
    local unit = veafRadio.getHumanUnitOrWingman(unitName)
    if unit then
        for name, _ in pairs(veafSpawn.spawnedConvoys) do
            local averageGroupPosition = veaf.getAveragePosition(name)
            if not averageGroupPosition then
                veaf.loggers.get(veafSpawn.Id):error("cannot get average position of %s",veaf.p(unitName))
                return nil
            end
            local distanceFromPlayer = ((averageGroupPosition.x - unit:getPosition().p.x)^2 + (averageGroupPosition.z - unit:getPosition().p.z)^2)^0.5
-- LOGGING DISABLED WHEN COMPILING(string.format("distanceFromPlayer = %d",distanceFromPlayer))
            if distanceFromPlayer < minDistance then
                minDistance = distanceFromPlayer
                closestConvoyName = name
-- LOGGING DISABLED WHEN COMPILING(string.format("convoy %s is closest",closestConvoyName))
            end
        end
    end
    return closestConvoyName
end

function veafSpawn._commandConvoy(convoyName, stop)
    local group = Group.getByName(convoyName)
    if group then
        if stop then
            local stopped = veafSpawn.spawnedConvoys[convoyName].stopped
            if stopped then
                -- already stopped !
                return false
            else
                local task ={
                    id = 'Hold',
                    params = { }
                    }
                    group:getController():pushTask(task)
                veafSpawn.spawnedConvoys[convoyName].stopped = true
            end
        else
            local stopped = veafSpawn.spawnedConvoys[convoyName].stopped
            if stopped then
                mist.goRoute(convoyName, veafSpawn.spawnedConvoys[convoyName].route)
                veafSpawn.spawnedConvoys[convoyName].stopped = false
            else
                -- not stopped !
                return false
            end
        end
    end
end

function veafSpawn.stopClosestConvoy(unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn.stopClosestConvoy(unitName=%s)",unitName))
    local convoyName = veafSpawn._findClosestConvoy(unitName)
    if convoyName then
        return veafSpawn._commandConvoy(convoyName, true)
    end
end

function veafSpawn.moveClosestConvoy(unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn.moveClosestConvoy(unitName=%s)",unitName))
    local convoyName = veafSpawn._findClosestConvoy(unitName)
    if convoyName then
        return veafSpawn._commandConvoy(convoyName, false)
    end
end

function veafSpawn._markClosestConvoyWithSmoke(unitName, markRoute)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn.markClosestConvoyWithSmoke(unitName=%s)",unitName))
    local closestConvoyName = veafSpawn._findClosestConvoy(unitName)
    if closestConvoyName then
        if markRoute then
            local route = veafSpawn.spawnedConvoys[closestConvoyName].route
            local startPoint = veaf.placePointOnLand({x = route[1].x, y = 0, z = route[1].y})
            local endPoint = veaf.placePointOnLand({x = route[2].x, y = 0, z = route[2].y})
            trigger.action.smoke(startPoint, trigger.smokeColor.Green)
            trigger.action.smoke(endPoint, trigger.smokeColor.Red)
            veaf.outTextForUnit(unitName, closestConvoyName .. " is going from green to red smoke", 10)
        else
            local averageGroupPosition = veaf.getAveragePosition(closestConvoyName)
            trigger.action.smoke(averageGroupPosition, trigger.smokeColor.White)
            veaf.outTextForUnit(unitName, closestConvoyName .. " marked with white smoke", 10)
        end
    else
        veaf.outTextForUnit(unitName, "No convoy found", 10)
    end
end

function veafSpawn.markClosestConvoyWithSmoke(unitName)
    return veafSpawn._markClosestConvoyWithSmoke(unitName, false)
end

function veafSpawn.markClosestConvoyRouteWithSmoke(unitName)
    return veafSpawn._markClosestConvoyWithSmoke(unitName, true)
end

function veafSpawn.infoOnAllConvoys(unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn.infoOnAllConvoys(unitName=%s)",unitName))
    local text = ""
    for name, _ in pairs(veafSpawn.spawnedConvoys) do
        local nbVehicles, nbInfantry = veafUnits.countInfantryAndVehicles(name)
        if nbVehicles > 0 then
            local averageGroupPosition = veaf.getAveragePosition(name)
            local lat, lon = coord.LOtoLL(averageGroupPosition)
            local llString = mist.tostringLL(lat, lon, 0, true)
            text = text .. " - " .. name .. ", " .. nbVehicles .. " vehicles : " .. llString
            if veafSpawn.spawnedConvoys[name].stopped then
                text = text .. ", stopped"
            end
        else
            text = text .. " - " .. name .. "has been destroyed"
            -- convoy has been dispatched, remove it from the convoys list
            veafSpawn.spawnedConvoys[name] = nil
        end
    end
    if text == "" then
        veaf.outTextForUnit(unitName, "No convoy found", 10)
    else
        veaf.outTextForUnit(unitName, text, 30)
    end
end

function veafSpawn.cleanupAllConvoys()
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.cleanupAllConvoys()")
    local foundOne = false
    for name, _ in pairs(veafSpawn.spawnedConvoys) do
        foundOne = true
        local nbVehicles, nbInfantry = veafUnits.countInfantryAndVehicles(name)
        if nbVehicles > 0 then
            local group = Group.getByName(name)
            if group then
                Group.destroy(group)
            end
        end
        -- convoy has been dispatched, remove it from the convoys list
        veafSpawn.spawnedConvoys[name] = nil
    end
    if foundOne then
        trigger.action.outText("All convoys cleaned up", 10)
    else
        trigger.action.outText("No convoy found", 10)
    end
end

function veafSpawn.JTACAutoLase(groupName, laserCode, radioData)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.JTACAutoLase()")
-- LOGGING DISABLED WHEN COMPILING(string.format("groupName=%s",tostring(groupName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("laserCode=%s",tostring(laserCode)))
-- LOGGING DISABLED WHEN COMPILING(string.format("radioData=%s\n",veaf.p(radioData)))
    local _radio = radioData or {}
-- LOGGING DISABLED WHEN COMPILING(string.format("_radio=%s\n",veaf.p(_radio)))
-- LOGGING DISABLED WHEN COMPILING(string.format("calling CTLD"))
    ctld.JTACAutoLase(groupName, laserCode, false, "all", nil, _radio)
-- LOGGING DISABLED WHEN COMPILING(string.format("CTLD called"))
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- air units templates
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafAirUnitTemplate object
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafAirUnitTemplate = {}

function VeafAirUnitTemplate:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object

    -- name
    objectToCreate.name = nil
    --  coalition (0 = neutral, 1 = red, 2 = blue)
    objectToCreate.coalition = nil
    -- route, only for veaf commands (groups already have theirs)
    objectToCreate.route = nil
    objectToCreate.humanName = nil
    objectToCreate.groupData = nil

    return objectToCreate
end

---
--- setters and getters
---

function VeafAirUnitTemplate:setName(value)
    self.name = value
    return self
end

function VeafAirUnitTemplate:getName()
    return self.name
end

function VeafAirUnitTemplate:setCoalition(value)
    self.coalition = value
    return self
end

function VeafAirUnitTemplate:getCoalition()
    return self.coalition
end

function VeafAirUnitTemplate:setGroupData(value)
    self.groupData = value
    return self
end

function VeafAirUnitTemplate:getGroupData()
    return self.groupData
end

function veafSpawn.initializeAirUnitTemplates()

-- LOGGING DISABLED WHEN COMPILING("veafSpawn.initializeAirUnitTemplates()")

    -- find groups with the air units template prefix
-- LOGGING DISABLED WHEN COMPILING("find groups with the air units template prefix")
    local _prefix = veafSpawn.AirUnitTemplatesPrefix:upper()
-- LOGGING DISABLED WHEN COMPILING("_prefix=%s",_prefix)
    local _templateGroups = {}
    local _groups = veaf.getGroupsOfCoalition()
    for _, group in pairs(_groups) do
        local _name = group:getName():upper()
-- LOGGING DISABLED WHEN COMPILING("_name=%s",_name)
        if string.sub(_name,1,string.len(_prefix)) == _prefix then
            table.insert(_templateGroups, group)
        end
    end

-- LOGGING DISABLED WHEN COMPILING("_templateGroups=%s", _templateGroups)
    for _, group in pairs(_templateGroups) do
        local _groupName = group:getName()
-- LOGGING DISABLED WHEN COMPILING("_groupName=%s", _groupName)
        local _template = VeafAirUnitTemplate:new():setName(_groupName)
        veafSpawn.airUnitTemplates[_groupName:upper()] = _template
    end

    -- find groups within the veafSpawn.SpawnablePlanes table
    -- DOES NOT WORK YET
    if veafSpawn.SpawnablePlanes then
-- LOGGING DISABLED WHEN COMPILING("find groups within the veafSpawn.SpawnablePlanes table")
        for _, groupData in pairs(veafSpawn.SpawnablePlanes) do
            local _groupName = groupData.name
-- LOGGING DISABLED WHEN COMPILING("_groupName=%s", _groupName)
            groupData.country="russia"
            groupData.countryId=0
            groupData.category="plane"
            groupData.coalition="red"
            groupData.uncontrolled=false
            groupData.hidden=false
            local _template = VeafAirUnitTemplate:new():setName(_groupName):setGroupData(groupData)
            veafSpawn.airUnitTemplates[_groupName:upper()] = _template
        end
    end

end

function veafSpawn.listAllCAP(unitName)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.listAllCAP(unitName=%s)",unitName)
    local sorted = {}
    for name, template in pairs(veafSpawn.airUnitTemplates) do
        local _name = template:getName():sub(veafSpawn.AirUnitTemplatesPrefix:len()+1)
        table.insert(sorted, _name)
    end
    table.sort(sorted)
    local text = ""
    for _, name in pairs(sorted) do
        text = text .. name .. "\n"
    end
    if text == "" then
        veaf.outTextForUnit(unitName, "No CAP available for spawn", 10)
    else
        veaf.outTextForUnit(unitName, text, 30)
    end
end

function veafSpawn.dumpSpawnablePlanesList(export_path)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.dumpSpawnablePlanesList(export_path=%s)", export_path)

    local jsonify = function(key, value)
-- LOGGING DISABLED WHEN COMPILING(string.format("jsonify(%s)", veaf.p(value)))
        if veaf.json then
            return veaf.json.stringify(value)
        else
            return ""
        end
    end

    -- sort the spawnable planes alphabetically
    local sortedSpawnablePlanesNames = {}
    for _, spawnablePlane in pairs(veafSpawn.airUnitTemplates) do
        local _name = spawnablePlane:getName():sub(veafSpawn.AirUnitTemplatesPrefix:len()+1)
        table.insert(sortedSpawnablePlanesNames, _name)
    end
    table.sort(sortedSpawnablePlanesNames)
-- LOGGING DISABLED WHEN COMPILING("sortedSpawnablePlanesNames=%s", veaf.p(sortedSpawnablePlanesNames))

    local _filename = "SpawnablePlanes.json"
    if veaf.config.MISSION_NAME then
        _filename = "SpawnablePlanesList_" .. veaf.config.MISSION_NAME .. ".json"
    end
    if not(veaf.DO_NOT_EXPORT_JSON_FILES) then
        veaf.exportAsJson(sortedSpawnablePlanesNames, "spawnablePlanes", jsonify, _filename, export_path or veaf.config.MISSION_EXPORT_PATH)
    end
end

function veafSpawn.spawnAFAC(spawnSpot, name, country, altitude, speed, hdg, frequency, mod, code, immortal, silent, hiddenOnMFD)

    local coalition = veaf.getCoalitionForCountry(country, true)
    if not coalition then
        veaf.loggers.get(veafSpawn.Id):error("No country/coalition for AFAC !")
        return nil
    end

    -- find template amongst the existing templates (name can be a regex)
    local groupName = veafSpawn.findSpawnableAircraftGroupname(name)
    if not groupName then
        local message = string.format("The AFAC aircraft template could not be found for \"%s\"", veaf.p(name))
        veaf.loggers.get(veafSpawn.Id):info(message)
        trigger.action.outTextForCoalition(coalition, message, 15)
        return nil
    end
-- LOGGING DISABLED WHEN COMPILING("found template=%s",groupName)

    if not veafSpawn.AFAC.numberSpawned[coalition] then
        veafSpawn.AFAC.numberSpawned[coalition] = 1
    elseif veafSpawn.AFAC.numberSpawned[coalition] > veafSpawn.AFAC.maximumAmount then
        veaf.loggers.get(veafSpawn.Id):info("The limit for AFACs was reached, one needs to be destroyed")
        if not silent then trigger.action.outTextForCoalition(coalition, "The limit for AFACs was reached, one needs to be destroyed", 15) end
        return false
    end

    veaf.loggers.get(veafSpawn.Id):info(string.format("number of AFAC spawned : %s", veaf.p(veafSpawn.AFAC.numberSpawned[coalition])))

    local AFAC_num = veafSpawn.AFAC.numberSpawned[coalition]
    local newGroupName = veafSpawn.AFAC.callsigns[coalition][AFAC_num].name
    for i = 1, veafSpawn.AFAC.maximumAmount do
        if veafSpawn.AFAC.callsigns[coalition][i].taken == false then
            newGroupName = veafSpawn.AFAC.callsigns[coalition][i].name
            AFAC_num = i
            break
        end
    end
-- LOGGING DISABLED WHEN COMPILING("newGroupName=%s",newGroupName)
-- LOGGING DISABLED WHEN COMPILING("AFAC_num=%s",AFAC_num)
-- LOGGING DISABLED WHEN COMPILING("AFAC coalition=%s",coalition)

    --essentially the same counter but for the template group itself, not for all AFACs
    if not veafSpawn.spawnedNamesIndex[groupName] then
        veafSpawn.spawnedNamesIndex[groupName] = 1
    end

    local codeDigit = {}
    codeDigit = veaf.laserCodeToDigit(code)

    local altitude = altitude or 15000
    if altitude <= 8000 then
        altitude = 15000 -- ft
    end

    local speed = speed or 150 -- kn
    -- convert speed to m/s
    speed = speed/1.94384

    -- convert altitude to meters
    altitude = altitude * 0.3048 -- meters

    --convert heading to radians
    if hdg then
        hdg = hdg * math.pi / 180
    else
        hdg = 0
    end

    local distanceFromTeleport = 3000 --distance between the orbit point and the teleport point in meters

    --calculate DCS radio frequency based on which AFAC out of 8 this is
    local dcsFrequency = veafSpawn.AFAC.baseAFACfrequency[coalition]+(AFAC_num-1)*50000 -- .05 MHz increments

-- LOGGING DISABLED WHEN COMPILING("spawnSpot=%s", veaf.p(spawnSpot))
-- LOGGING DISABLED WHEN COMPILING("name=%s", veaf.p(name))
-- LOGGING DISABLED WHEN COMPILING("country=%s", veaf.p(country))
-- LOGGING DISABLED WHEN COMPILING("altitude (m)=%s", veaf.p(altitude))
-- LOGGING DISABLED WHEN COMPILING("speed (m/s)=%s", veaf.p(speed))
-- LOGGING DISABLED WHEN COMPILING("frequency=%s", veaf.p(frequency))
-- LOGGING DISABLED WHEN COMPILING("dcsFrequency=%s", veaf.p(dcsFrequency))
-- LOGGING DISABLED WHEN COMPILING("code=%s", veaf.p(code))
-- LOGGING DISABLED WHEN COMPILING("mod=%s", veaf.p(mod))
-- LOGGING DISABLED WHEN COMPILING("silent=%s", veaf.p(silent))
-- LOGGING DISABLED WHEN COMPILING("hiddenOnMFD=%s", veaf.p(hiddenOnMFD))

    local teleportSpot = {}
    teleportSpot.x = spawnSpot.x - distanceFromTeleport*math.cos(hdg) --teleport spot is 3km south of the orbit point
    teleportSpot.y = spawnSpot.z - distanceFromTeleport*math.sin(hdg)
    teleportSpot.alt = altitude
    teleportSpot.speed = speed

    --define 2 point route + teleport Waypoint
    local WP = {}
    WP.one = {}
    WP.two = {}
    WP.three = {}
    WP.one.x = teleportSpot.x
    WP.one.y = teleportSpot.y
    WP.two.x = spawnSpot.x - distanceFromTeleport*math.cos(hdg)/2
    WP.two.y = spawnSpot.z - distanceFromTeleport*math.sin(hdg)/2
    WP.three.x = spawnSpot.x
    WP.three.y = spawnSpot.z

-- LOGGING DISABLED WHEN COMPILING(veafSpawn.traceMarkerId, "AFAC", "teleportPoint", WP.one)
-- LOGGING DISABLED WHEN COMPILING(veafSpawn.traceMarkerId, "AFAC", "setupPoint", WP.two)
-- LOGGING DISABLED WHEN COMPILING(veafSpawn.traceMarkerId, "AFAC", "orbitPoint", WP.three)

	local newRoute = {
        ["points"] = {
            -- first point
            [1] = {
                ["type"] = "Turning Point",
                ["action"] = "Turning Point",
                ["x"] = WP.two.x, --1500m south of the orbit point
                ["y"] = WP.two.y,
                ["alt"] = altitude, -- in meters
                ["alt_type"] = "BARO",
                ["speed"] = speed,  -- speed in m/s
                ["speed_locked"] = true,
                ["task"] = {
                    ["id"] = "ComboTask",
                    ["params"] = {
                        ["tasks"] = {
                            [1] = {
                                ["id"]="FAC",
                                ["params"] = {
                                    ["frequency"]=dcsFrequency,
                                    ["modulation"]=0, --0 is AM, 1 is FM
                                    ["callname"]=AFAC_num,
                                    ["number"]=7+coalition, --number x as in it's callsign Springfield x-1 for example
                                    ["priority"]=0,
                                }
                            } -- end of [1]
                        } -- end of tasks
                    } -- end of params
                } -- end of task
            }, -- end of waypoint 1
            [2] = {
                ["type"] = "Turning Point",
                ["action"] = "Turning Point",
                ["x"] = WP.three.x,
                ["y"] = WP.three.y,
                ["alt"] = altitude, -- in meters
                ["alt_type"] = "BARO",
                ["speed"] = speed,  -- speed in m/s
                ["speed_locked"] = true,
                ["task"] = {
                    ["id"] = "ComboTask",
                    ["params"] = {
                        ["tasks"] = {
                            [1] = {
                                ["id"] = "Orbit",
                                ["params"] =
                                {
                                    ["altitude"] = altitude, -- in meters,
                                    ["pattern"] = "Circle",
                                    ["speed"] = speed,  -- speed in m/s
                                }, -- end of ["params"]
                            } -- end of [1]
                        } -- end of ["tasks"]
                    } -- end of ["params"]
                } -- end of ["task"]
            } -- end of waypoint 2
        }
    }

    -- (re)spawn group
    local vars = {}
    vars.gpName = groupName
    vars.name = groupName
    --vars.groupData = _template:getGroupData()
    --replace the callsign to prevent interractions
    vars.route = newRoute
    vars.action = 'clone'
    vars.point = teleportSpot
    vars.newGroupName = newGroupName

    local newGroup = mist.teleportToPoint(vars, true)
    if not newGroup then
        veaf.loggers.get(veafSpawn.Id):error("cannot respawn group %s",veaf.p(vars.name))
        return nil
    end
    if country and #country > 0 then
        newGroup.coalition = coalition
        newGroup.countryId = veaf.getCountryId(country)
    end
    --newGroup.task = "AFAC"
-- LOGGING DISABLED WHEN COMPILING("newGroup=%s", veaf.p(newGroup, nil, {"route", "payload"}))

    --setup of the new group
    local unit = newGroup.units[1]
    if not unit then
        veaf.loggers.get(veafSpawn.Id):error("cannot get first unit of group %s",veaf.p(newGroup:getName()))
        return nil
    end

    unit.skill = "Excellent"
    newGroup.hidden=false
    newGroup.name = newGroupName
    newGroup.hiddenOnMFD = hiddenOnMFD

    local unitName = newGroupName
-- LOGGING DISABLED WHEN COMPILING("unitName=%s",unitName)
    unit.unitName = unitName
    unit.name = unitName
    newGroup.sameName = true

    unit.alt = teleportSpot.alt

-- LOGGING DISABLED WHEN COMPILING("newGroup=%s", veaf.p(newGroup, nil, {"route", "payload"}))
    local _spawnedGroup = mist.dynAdd(newGroup)

    if _spawnedGroup then
-- LOGGING DISABLED WHEN COMPILING("_spawnedGroup=%s", veaf.p(_spawnedGroup, nil, {"route", "payload"}))
-- LOGGING DISABLED WHEN COMPILING("_spawnedGroup.name=%s",_spawnedGroup.name)
        --mist.goRoute(_spawnedGroup.name, newRoute)

        _spawnedGroup.category = "AIRPLANE"
        _spawnedGroup.country = country
-- LOGGING DISABLED WHEN COMPILING("_spawnedGroup=%s", veaf.p(_spawnedGroup))
        veafSpawn.AFAC.missionData[coalition][AFAC_num] = _spawnedGroup --since MIST does not store cloned group data, this is a bit of trickery to allow teleporting AFACs

        -- start lasing 
        if ctld then
            ctld.cleanupJTAC(_spawnedGroup.name)
            local radioData = {freq=frequency, mod=mod, name=_spawnedGroup.name}
            veafSpawn.JTACAutoLase(_spawnedGroup.name, code, radioData)
        end

        local humanFrequency = dcsFrequency/1000000
        local text = "AFAC " .. string.format(veafSpawn.AFAC.numberSpawned[coalition]) .. "/" .. string.format(veafSpawn.AFAC.maximumAmount) .. " - " .. string.format(_spawnedGroup.name) .. " (" .. string.format(country) .. ") - on " .. string.format(humanFrequency) .. "AM (DCS AFAC) or " .. string.format(frequency) .. string.upper(mod) .. " (SRS)"
        veaf.loggers.get(veafSpawn.Id):info(text)
        if not silent then trigger.action.outTextForCoalition(coalition, text, 15) end

        local _dcsSpawnedGroup = Group.getByName(_spawnedGroup.name)
        local controller = _dcsSpawnedGroup:getController()

        if immortal then
-- LOGGING DISABLED WHEN COMPILING("AFAC immortalized")
            -- JTAC needs to be invisible and immortal
            local _setImmortal = {
                id = 'SetImmortal',
                params = {
                    value = true
                }
            }
            -- invisible to AI, Shagrat
            local _setInvisible = {
                id = 'SetInvisible',
                params = {
                    value = true
                }
            }

            Controller.setCommand(controller, _setImmortal)
            Controller.setCommand(controller, _setInvisible)
        end

        --set the callsign to avoid desyncs in the DCS JTAC menu
        local _setCallsign = {
            id = 'SetCallsign',
            params = {
            callname = AFAC_num,
            number = 9,
            }
        }

        Controller.setCommand(controller, _setCallsign)

        if veafNamedPoints and not silent then
            text = "AFAC" .. " - " .. string.format(_spawnedGroup.name) .. " - " .. string.format(humanFrequency) .. "AM (DCS) or " .. string.format(frequency) .. string.upper(mod) .. " (SRS)"
            veafNamedPoints.namePoint({x=spawnSpot.x, y=altitude, z=spawnSpot.z}, text, veaf.getCoalitionForCountry(country, true), true)
        end

        veafSpawn.afacWatchdog(newGroupName, AFAC_num, coalition, text)
        veafSpawn.AFAC.callsigns[coalition][AFAC_num].taken = true
        veafSpawn.spawnedNamesIndex[groupName] = veafSpawn.spawnedNamesIndex[groupName] + 1
        veafSpawn.AFAC.numberSpawned[coalition] = veafSpawn.AFAC.numberSpawned[coalition] + 1

        return _spawnedGroup.name
    else
        veaf.loggers.get(veafSpawn.Id):error("MIST could not add AFAC")
        return nil
    end
end

function veafSpawn.afacWatchdog(afacGroupName, AFAC_num, coalition, markName)
    if afacGroupName and not Group.getByName(afacGroupName) then
        veaf.loggers.get(veafSpawn.Id):info(string.format("AFAC named=%s is KIA, removing mark (if it exists) and allowing it to be spawned again", veaf.p(afacGroupName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("markName=%s", veaf.p(markName)))

        if veafNamedPoints and markName then
            local existingPoint = veafNamedPoints.getPoint(markName)
-- LOGGING DISABLED WHEN COMPILING(string.format("existingPoint=%s", veaf.p(existingPoint)))
            if existingPoint and existingPoint.markerId then
                -- delete the existing point
                trigger.action.removeMark(existingPoint.markerId)
            end
        end

        --Make the callsign index available again for spawn
-- LOGGING DISABLED WHEN COMPILING(string.format("AFAC_num=%s", veaf.p(AFAC_num)))
        veafSpawn.AFAC.callsigns[coalition][AFAC_num].taken = false
        veafSpawn.AFAC.numberSpawned[coalition] = veafSpawn.AFAC.numberSpawned[coalition] - 1
        mist.DBs.unitsByName[afacGroupName] = nil --MIST does not do it on it's own, I highly recommend looking for an alternative, this is to spawn the AFAC once again with the unit name equal to the group name
        mist.DBs.groupsByName[afacGroupName] = nil
        veafSpawn.AFAC.missionData[coalition][AFAC_num] = nil
    else
-- LOGGING DISABLED WHEN COMPILING(string.format("AFAC named=%s is alive", veaf.p(afacGroupName)))

        --update the mark if the AFAC moves
        if veafNamedPoints and markName then
            local existingPoint = veafNamedPoints.getPoint(markName)
-- LOGGING DISABLED WHEN COMPILING(string.format("existingAFACmarker=%s", veaf.p(existingPoint)))
            if existingPoint and existingPoint.markerId then
                local AFAC_points = veafSpawn.AFAC.missionData[coalition][AFAC_num].route.points
                local orbitPoint = AFAC_points[#AFAC_points]
                if existingPoint.x ~= orbitPoint.x and existingPoint.z ~= orbitPoint.y then
                    -- delete the existing point
-- LOGGING DISABLED WHEN COMPILING(string.format("Marker needs updating, AFAC moved, newAFACmarker=%s", veaf.p(orbitPoint)))
                    trigger.action.removeMark(existingPoint.markerId)
                    veafNamedPoints.namePoint({x=orbitPoint.x, y=orbitPoint.alt, z=orbitPoint.y}, markName, coalition, true)
                end
            end
        end

        mist.scheduleFunction(veafSpawn.afacWatchdog, {afacGroupName, AFAC_num, coalition, markName}, timer.getTime()+120)
    end
end

function veafSpawn.findSpawnableAircraftGroupname(name)
    -- find template amongst the existing templates (name can be a regex)
    local nameUpper = (name or ""):upper()
    local regexNameUpper = ".*" .. (nameUpper or ".*") .. ".*"
    if not name then
        regexNameUpper = ".*"
    end
    local escapedNameUpper = veaf.escapeRegex(nameUpper)
-- LOGGING DISABLED WHEN COMPILING("nameUpper=%s", veaf.p(nameUpper))
    local templatesNamesToChooseFrom = {}
    local chosenTemplateName = nil
    for templateNameUpper, templateData in pairs(veafSpawn.airUnitTemplates) do
-- LOGGING DISABLED WHEN COMPILING("templateNameUpper=%s", veaf.p(templateNameUpper))
        if templateNameUpper:match(regexNameUpper) or templateNameUpper:match(escapedNameUpper) then
            local templateName = templateData.name
-- LOGGING DISABLED WHEN COMPILING("templateName=%s", veaf.p(templateName))
            table.insert(templatesNamesToChooseFrom, templateName)
        end
    end
    if templatesNamesToChooseFrom and #templatesNamesToChooseFrom > 0 then
        chosenTemplateName = veaf.randomlyChooseFrom(templatesNamesToChooseFrom)
    else
        local message = string.format("The CAP aircraft template could not be found for \"%s\"", veaf.p(name))
        veaf.loggers.get(veafSpawn.Id):info(message)
        trigger.action.outText(message, 15)
        return nil
    end
-- LOGGING DISABLED WHEN COMPILING("templatesNamesToChooseFrom=%s", veaf.p(templatesNamesToChooseFrom))
    local chosenTemplateData = veaf.getGroupData(chosenTemplateName)
-- LOGGING DISABLED WHEN COMPILING("found template=%s",chosenTemplateData)
    return chosenTemplateName, chosenTemplateData
end

function veafSpawn.spawnCombatAirPatrol(spawnSpot, radius, name, country, altitude, altitudeDelta, hdg, distance, speed, capRadius, skill, silent, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.spawnCombatAirPatrol(name=%s)", name)

    -- for compatibility reasons we still have altitude and altitudedelta set to zero by default
    if altitude == 0 then altitude = nil end
    if altitudeDelta == 0 then altitudeDelta = nil end

    local coalition = veaf.getCoalitionForCountry(country, true)
    if not coalition then
        veaf.loggers.get(veafSpawn.Id):error("No country/coalition for CAP !")
        return nil
    end

    -- find template amongst the existing templates (name can be a regex)
    local chosenTemplateName, chosenTemplateData = veafSpawn.findSpawnableAircraftGroupname(name)
    if not chosenTemplateName or not chosenTemplateData then
        veaf.loggers.get(veafSpawn.Id):error("spawnCombatAirPatrol: could not find a template for %s", veaf.p(name))
        return
    end

    local function convertSpeeds(speed, mach, altitude)
        local result = speed
        if not result then
            -- compute ground speed in m/s based on MACH and altitude
            result = veaf.convertMachSpeed(0.3, altitude).TAS_ms
        else
            -- compute ground speed in m/s based on IAS and altitude
            result = veaf.convertIndicatedAirSpeed(speed, altitude).TAS_ms
        end
        return result
    end

    local radius = radius or 5000 -- m
    local altitude = (altitude or 27000) --[[ ft ]] * 0.3048 --[[ meters ]]
    local altitudeDelta = (altitudeDelta or 2000) --[[ ft ]] * 0.3048 --[[ meters ]]
    local hdg = hdg or 0
    local speed0 = convertSpeeds(speed, 0.3, altitude)
    local speed1 = convertSpeeds(speed, 0.5, altitude)
    local speed2 = convertSpeeds(speed, 0.63, altitude)
    local speed3 = convertSpeeds(speed, 0.63, altitude)
    local distance = (distance or 20) --[[ nm ]] * 1852 --[[ meters ]]
    local capRadius = (capRadius or 60) * 1852 --[[ meters ]]
    local skill = skill or "random"

-- LOGGING DISABLED WHEN COMPILING("spawnSpot=%s", veaf.p(spawnSpot))
-- LOGGING DISABLED WHEN COMPILING("radius=%s", veaf.p(radius))
-- LOGGING DISABLED WHEN COMPILING("name=%s", veaf.p(name))
-- LOGGING DISABLED WHEN COMPILING("country=%s", veaf.p(country))
-- LOGGING DISABLED WHEN COMPILING("altitude=%s", veaf.p(altitude))
-- LOGGING DISABLED WHEN COMPILING("altdelta=%s", veaf.p(altitudeDelta))
-- LOGGING DISABLED WHEN COMPILING("hdg=%s", veaf.p(hdg))
-- LOGGING DISABLED WHEN COMPILING("distance=%s", veaf.p(distance))
-- LOGGING DISABLED WHEN COMPILING("speed0=%s", veaf.p(speed0))
-- LOGGING DISABLED WHEN COMPILING("speed1=%s", veaf.p(speed1))
-- LOGGING DISABLED WHEN COMPILING("speed2=%s", veaf.p(speed2))
-- LOGGING DISABLED WHEN COMPILING("speed3=%s", veaf.p(speed3))
-- LOGGING DISABLED WHEN COMPILING("capRadius=%s", veaf.p(capRadius))
-- LOGGING DISABLED WHEN COMPILING("skill=%s", veaf.p(skill))
-- LOGGING DISABLED WHEN COMPILING("silent=%s", veaf.p(silent))
-- LOGGING DISABLED WHEN COMPILING("hiddenOnMFD=%s", veaf.p(hiddenOnMFD))

    local getRoute = function(parameters)
        local newRoute = {
            ["points"] = {
                [1] =
                {
                    ["alt"] = parameters.altitude,
                    ["action"] = "Turning Point",
                    ["alt_type"] = "BARO",
                    ["speed"] = parameters.speed1,
                    ["properties"] =
                    {
                        ["addopt"] =
                        {
                        }, -- end of ["addopt"]
                    }, -- end of ["properties"]
                    ["task"] = parameters.chosenTemplateWp1Task,
                    ["type"] = "Turning Point",
                    ["ETA"] = 10000,
                    ["ETA_locked"] = false,
                    ["y"] = parameters.wp1.y,
                    ["x"] = parameters.wp1.x,
                    ["formation_template"] = "",
                    ["speed_locked"] = true,
                }, -- end of [1]
                [2] =
                {
                    ["alt"] = parameters.altitude,
                    ["action"] = "Turning Point",
                    ["alt_type"] = "BARO",
                    ["speed"] = parameters.speed2,
                    ["properties"] =
                    {
                        ["addopt"] =
                        {
                        }, -- end of ["addopt"]
                    }, -- end of ["properties"]
                    ["task"] =
                    {
                        ["id"] = "ComboTask",
                        ["params"] =
                        {
                            ["tasks"] =
                            {
                                -- [1] = 
                                -- {
                                --     ["number"] = 1,
                                --     ["auto"] = false,
                                --     ["enabled"] = true,
                                --     ["id"] = "EngageTargetsInZone",
                                --     ["params"] = {
                                --         ["noTargetTypes"] = {
                                --             [1] = "Cruise missiles",
                                --             [2] = "Antiship Missiles",
                                --             [3] = "AA Missiles",
                                --             [4] = "AG Missiles",
                                --             [5] = "SA Missiles",
                                --         }, -- end of ["noTargetTypes"]
                                --         ["priority"] = 0,
                                --         ["targetTypes"] = {
                                --             [1] = "Air",
                                --         }, -- end of ["targetTypes"]
                                --         ["value"] = "Air;",
                                --         ["x"] = parameters.targetZone.x,
                                --         ["y"] = parameters.targetZone.y,
                                --         ["zoneRadius"] = parameters.targetZone.radius,
                                --     }, -- end of ["params"]
                                -- }, -- end of [1]                       
                            }, -- end of ["tasks"]
                        }, -- end of ["params"]
                    }, -- end of ["task"]
                    ["type"] = "Turning Point",
                    ["ETA"] = 20000,
                    ["ETA_locked"] = false,
                    ["y"] = parameters.wp2.y,
                    ["x"] = parameters.wp2.x,
                    ["formation_template"] = "",
                    ["speed_locked"] = true,
                }, -- end of [2]
                [3] =
                {
                    ["alt"] = parameters.altitude,
                    ["action"] = "Turning Point",
                    ["alt_type"] = "BARO",
                    ["speed"] = parameters.speed3,
                    ["properties"] =
                    {
                        ["addopt"] =
                        {
                        }, -- end of ["addopt"]
                    }, -- end of ["properties"]
                    ["task"] =
                    {
                        ["id"] = "ComboTask",
                        ["params"] =
                        {
                            ["tasks"] =
                            {
                                [1] =
                                {
                                    ["enabled"] = true,
                                    ["auto"] = false,
                                    ["id"] = "WrappedAction",
                                    ["number"] = 1,
                                    ["params"] =
                                    {
                                        ["action"] =
                                        {
                                            ["id"] = "SwitchWaypoint",
                                            ["params"] =
                                            {
                                                ["goToWaypointIndex"] = 2,
                                                ["fromWaypointIndex"] = 3,
                                            }, -- end of ["params"]
                                        }, -- end of ["action"]
                                    }, -- end of ["params"]
                                }, -- end of [1]
                            }, -- end of ["tasks"]
                        }, -- end of ["params"]
                    }, -- end of ["task"]
                    ["type"] = "Turning Point",
                    ["ETA"] = 30000,
                    ["ETA_locked"] = false,
                    ["y"] = parameters.wp3.y,
                    ["x"] = parameters.wp3.x,
                    ["formation_template"] = "",
                    ["speed_locked"] = true,
                }, -- end of [3]
            }
        }

        return newRoute
    end

    -- find spawn spot
    if altitudeDelta then
        altitude = altitude + math.random(0, altitudeDelta*2) - altitudeDelta
    end
    local position = mist.getRandPointInCircle(spawnSpot, radius)
    position.z = position.y
    position.y = altitude
-- LOGGING DISABLED WHEN COMPILING("final spawn, position=%s",position)

    -- get the template first waypoint's options
-- LOGGING DISABLED WHEN COMPILING("chosenTemplateData=%s", veaf.p(chosenTemplateData))
    local chosenTemplateWp1Task = {}
    if chosenTemplateData then
        local _route = chosenTemplateData.route
-- LOGGING DISABLED WHEN COMPILING("_route=%s", veaf.p(_route))
        if _route then
            local _points = _route.points
-- LOGGING DISABLED WHEN COMPILING("_points=%s", veaf.p(_points))
            if _points then
                local _point1 = _points[1]
-- LOGGING DISABLED WHEN COMPILING("_point1=%s", veaf.p(_point1))
                if _point1 then
                    local _task = _point1.task
-- LOGGING DISABLED WHEN COMPILING("_task=%s", veaf.p(_task))
                    if _task and "ComboTask" == _task.id then
                        local _params = _task.params
-- LOGGING DISABLED WHEN COMPILING("_params=%s", veaf.p(_params))
                        if _params then
                            local _tasks = _params.tasks
-- LOGGING DISABLED WHEN COMPILING("_tasks=%s", veaf.p(_tasks))
                            if _tasks then
                                for _, _taskData in pairs(_tasks) do
                                    if "WrappedAction" == _taskData.id then
                                        table.insert(chosenTemplateWp1Task, mist.utils.deepCopy(_task)) -- if we found a WrappedAction task then we're on the right way, clone the whole task package
                                        break
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
-- LOGGING DISABLED WHEN COMPILING("chosenTemplateWp1Task=%s", veaf.p(chosenTemplateWp1Task))

    -- compute route
    local headingRad = mist.utils.toRadian(hdg)
    local parameters = {
        altitude = altitude,
        speed0 = speed0,
        speed1 = speed1,
        speed2 = speed2,
        speed3 = speed3,
        wp1 = { x = position.x, y = position.z },
        wp1Options = chosenTemplateWp1Task
    }
    parameters.wp2 = { x = parameters.wp1.x + 2500 * math.cos(headingRad), y = parameters.wp1.y + 2500 * math.sin(headingRad) } -- second wp at 2500m in the right direction
    parameters.wp3 = { x = parameters.wp2.x + distance * math.cos(headingRad), y = parameters.wp2.y + distance * math.sin(headingRad) } -- last wp at the right distance in the right direction
    parameters.targetZone = { x = (parameters.wp2.x + parameters.wp3.x) / 2, y = (parameters.wp2.y + parameters.wp3.y) / 2, radius = capRadius } -- target zone at the middle point between wp2 and wp3

-- LOGGING DISABLED WHEN COMPILING("to create route, parameters=%s",parameters)
    local newRoute = getRoute(parameters)

-- LOGGING DISABLED WHEN COMPILING(veafSpawn.traceMarkerId, "CAP", "wp1", parameters.wp1)
-- LOGGING DISABLED WHEN COMPILING(veafSpawn.traceMarkerId, "CAP", "wp2", parameters.wp2)
-- LOGGING DISABLED WHEN COMPILING(veafSpawn.traceMarkerId, "CAP", "wp3", parameters.wp3)
-- LOGGING DISABLED WHEN COMPILING(veafSpawn.traceMarkerId, "CAP", "targetZone", parameters.targetZone, nil, capRadius, {1,0,0,0.15})

    if not veafSpawn.spawnedNamesIndex[chosenTemplateName] then
        veafSpawn.spawnedNamesIndex[chosenTemplateName] = 1
    else
        veafSpawn.spawnedNamesIndex[chosenTemplateName] = veafSpawn.spawnedNamesIndex[chosenTemplateName] + 1
    end
    local newGroupName = string.format("%s #%04d", chosenTemplateName, veafSpawn.spawnedNamesIndex[chosenTemplateName])
-- LOGGING DISABLED WHEN COMPILING("indexed newGroupName=%s",newGroupName)

    -- (re)spawn group
    local vars = {}
    vars.gpName = chosenTemplateName
    vars.name = chosenTemplateName
    --vars.groupData = _template:getGroupData()
    vars.route = newRoute
    vars.action = 'clone'
    vars.point = position
    vars.newGroupName = newGroupName

    local newGroup = mist.teleportToPoint(vars, true)
    if not newGroup then
        veaf.loggers.get(veafSpawn.Id):error("cannot respawn group %s",veaf.p(vars.name))
        return nil
    end
    if country and #country > 0 then
        newGroup.countryId = veaf.getCountryId(country)
    end
    --newGroup.task = "CAP" --needs to be set in the editor
-- LOGGING DISABLED WHEN COMPILING("after preparation by MIST, newGroup=%s", veaf.p(newGroup, nil, {"route", "payload"}))

    newGroup.hidden = false
    newGroup.name = newGroupName
    newGroup.hiddenOnMFD = hiddenOnMFD

    for _, unit in pairs(newGroup.units) do
        unit.skill = skill
        local unitName = unit.unitName or unit.name
-- LOGGING DISABLED WHEN COMPILING("original unitName=%s",unitName)
        if not veafSpawn.spawnedNamesIndex[unitName] then
            veafSpawn.spawnedNamesIndex[unitName] = 1
        else
            veafSpawn.spawnedNamesIndex[unitName] = veafSpawn.spawnedNamesIndex[unitName] + 1
        end
        local spawnedUnitName = string.format("%s #%04d", unitName, veafSpawn.spawnedNamesIndex[unitName])
        unit.name = spawnedUnitName
        unit.alt = position.y
-- LOGGING DISABLED WHEN COMPILING("indexed spawnedUnitName=%s",spawnedUnitName)
    end

-- LOGGING DISABLED WHEN COMPILING("before mist.dynAdd, newGroup=%s", veaf.p(newGroup, nil, {"route", "payload"}))
    local _spawnedGroup = mist.dynAdd(newGroup)
    if not _spawnedGroup then
        veaf.loggers.get(veafSpawn.Id):error("cannot spawn group %s",veaf.p(newGroup.name))
        return nil
    end
-- LOGGING DISABLED WHEN COMPILING("after mist.dynAdd, _spawnedGroup.name=%s",_spawnedGroup.name)
-- LOGGING DISABLED WHEN COMPILING("after mist.dynAdd, _spawnedGroup=%s", veaf.p(_spawnedGroup, nil, {"route", "payload"}))

    local _dcsSpawnedGroup = Group.getByName(_spawnedGroup.name)
-- LOGGING DISABLED WHEN COMPILING("result of dcs side getByName, _dcsSpawnedGroup=%s", veaf.p(_dcsSpawnedGroup, nil, {"route", "payload"}))
-- LOGGING DISABLED WHEN COMPILING("result of dcs side getByName, _dcsSpawnedGroup.name=%s", _dcsSpawnedGroup:getName())
    for index, unit in pairs(_dcsSpawnedGroup:getUnits()) do
-- LOGGING DISABLED WHEN COMPILING("result of dcs side getByName, _dcsSpawnedGroup.unit[%s].name=%s", index, unit:getName())
    end

    local controller = _dcsSpawnedGroup:getController()
    controller:setOption(AI.Option.Air.id.PROHIBIT_AA, true)

-- LOGGING DISABLED WHEN COMPILING("starting CAP target watchdog...")
    mist.scheduleFunction(veafSpawn.startCapWatchdog, {_spawnedGroup.name, coalition, parameters.targetZone}, timer.getTime() + 1)

    local message = string.format("A CAP of %s (%s) has been spawned", name, country)
    veaf.loggers.get(veafSpawn.Id):info(message)
    if not silent then trigger.action.outText(message, 15) end

    return _spawnedGroup.name
end

function veafSpawn.startCapWatchdog(capGroupName, capCoalition, capZone, pTargetsList, pNumberOfTasksAddedByWatchdog)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.startCapWatchdog(capGroupName=%s)", veaf.p(capGroupName))
-- LOGGING DISABLED WHEN COMPILING("capZone=%s", veaf.p(capZone))

    if capGroupName == nil then
        veaf.loggers.get(veafSpawn.Id):error("veafSpawn.startCapWatchdog; capGroupName is mandatory !")
        return
    end

    if capCoalition == nil then
        veaf.loggers.get(veafSpawn.Id):error("veafSpawn.startCapWatchdog; capCoalition is mandatory !")
        return
    end

    local capGroup = Group.getByName(capGroupName)
    if not capGroup then
-- LOGGING DISABLED WHEN COMPILING("CAP group %s is nowhere to be found, stopping watchdog", veaf.p(capGroupName))
        return
    end
    local capGroupPosition = veaf.getAveragePosition(capGroup)
    if not capGroupPosition then
        veaf.loggers.get(veafSpawn.Id):error("CAP group %s has no position!", veaf.p(capGroupName))
        return
    end

-- LOGGING DISABLED WHEN COMPILING("Looking in CAP zone for targets...")
    local timestamp = timer.getTime()
    local targetsList = pTargetsList or {}
    local numberOfTasksAddedByWatchdog = pNumberOfTasksAddedByWatchdog or 0
-- LOGGING DISABLED WHEN COMPILING("targetsList=%s", veaf.p(targetsList))
-- LOGGING DISABLED WHEN COMPILING("numberOfTasksAddedByWatchdog=%s", veaf.p(numberOfTasksAddedByWatchdog))

    -- check CAP group for state and position
    local capLanded = true
    local capInZone = false
    for _,unit in pairs(capGroup:getUnits()) do
        if unit and unit:inAir() then
            capLanded = false
            local isUnitInZone = veaf.isUnitInZone(unit, capZone)
-- LOGGING DISABLED WHEN COMPILING("unitName=%s, isUnitInZone=%s", veaf.p(unit:getName()), veaf.p(isUnitInZone))
            if isUnitInZone then
                capInZone = true
                -- unit is in the zone, and in the air, let's test the targets it can see
                local detectedTargets = unit:getController():getDetectedTargets()
                if detectedTargets and #detectedTargets > 0 then
                    -- process each target and compute its priority, then add it to the targets list
                    for _, detectedTarget in pairs(detectedTargets) do
                        local target = detectedTarget.object
                        local targetId = target:getID()
                        local targetGroup = target:getGroup()
                        local targetGroupName = targetGroup:getName()
                        local targetName = target:getName()
-- LOGGING DISABLED WHEN COMPILING("Checking targetGroupName=%s, targetName=%s, targetId=%s", veaf.p(targetGroupName), veaf.p(targetName), veaf.p(targetId))
                        local targetIsAirborne = target:isActive() and target:inAir()
                        local targetCoalition = target:getCoalition()
                        local targetGroupCategory = targetGroup:getCategory()
-- LOGGING DISABLED WHEN COMPILING("targetIsAirborne=%s", veaf.p(targetIsAirborne))
-- LOGGING DISABLED WHEN COMPILING("targetCoalition=%s", veaf.p(targetCoalition))
-- LOGGING DISABLED WHEN COMPILING("targetGroupCategory=%s", veaf.p(targetGroupCategory))

                        if  targetIsAirborne ~= nil and targetGroupCategory ~= nil and targetCoalition ~= nil
                            and targetCoalition ~= capCoalition
                            and (targetGroupCategory == Group.Category.AIRPLANE or targetGroupCategory == Group.Category.HELICOPTER) then

                            local targetPosition = target:getPosition().p
                            local targetDistanceFromCapZoneCenter = mist.utils.get2DDist(targetPosition, capZone)
-- LOGGING DISABLED WHEN COMPILING("targetPosition=%s", veaf.p(targetPosition))
-- LOGGING DISABLED WHEN COMPILING("targetDistanceFromCapZoneCenter=%s", veaf.p(targetDistanceFromCapZoneCenter))
                            if targetDistanceFromCapZoneCenter <= capZone.radius then
                                -- consider only the targets that are in the CAP zone

                                local targetAttributes = target:getDesc().attributes
                                local targetType = target:getTypeName()
                                local targetDistanceFromCapGroup = mist.utils.get2DDist(targetPosition, capGroupPosition)
-- LOGGING DISABLED WHEN COMPILING("targetType=%s", veaf.p(targetType))
-- LOGGING DISABLED WHEN COMPILING("targetAttributes=%s", veaf.p(targetAttributes))
-- LOGGING DISABLED WHEN COMPILING("targetDistanceFromCapGroup=%s", veaf.p(targetDistanceFromCapGroup))

                                local targetPriority = nil

                                if targetAttributes["Fighters"] or targetAttributes["Multirole fighters"] then
-- LOGGING DISABLED WHEN COMPILING("Target is a Fighter")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/2)
                                elseif targetAttributes["Strategic bombers"] then
-- LOGGING DISABLED WHEN COMPILING("Target is a strategic bomber")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/1.5) + 10000
                                elseif targetAttributes["Bombers"] then
-- LOGGING DISABLED WHEN COMPILING("Target is a bomber")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/1) + 15000
                                elseif targetAttributes["UAVs"] and targetType ~= "Yak-52" then --wtf ED, Yak-52 UAV master race
-- LOGGING DISABLED WHEN COMPILING("Target is a UAV (except the Yak-52, that shit is not a UAV ED)")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/0.5) + 15000
                                elseif targetAttributes["AWACS"] then
-- LOGGING DISABLED WHEN COMPILING("Target is an AWACS")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/0.5) + 15000
                                elseif targetAttributes["Transports"] then
-- LOGGING DISABLED WHEN COMPILING("Target is a Transport")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/0.5) + 15000
                                elseif targetAttributes["Battle airplanes"] or targetAttributes["Battleplanes"] then
-- LOGGING DISABLED WHEN COMPILING("Target is a generic Battleplane")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/0.25) + 15000
                                elseif targetAttributes["Helicopters"] or targetAttributes["Attack helicopters"] or targetAttributes["Transport helicopters"] then
-- LOGGING DISABLED WHEN COMPILING("Target is a Helicopter")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/0.1) + 20000
                                else
-- LOGGING DISABLED WHEN COMPILING("Target has unknown attributes, calculating generic priority")
                                    targetPriority = math.floor(targetDistanceFromCapGroup/0.25) + 15000
                                end
                                -- https://www.geogebra.org/calculator if you want to visualize, type in functions y=x/factor + offset and set points on each curve. y is the priority, x the distance

-- LOGGING DISABLED WHEN COMPILING("priority=%s", veaf.p(targetPriority))


                                local targetData = targetsList[targetId]
                                if targetData then
                                    -- this target has already been detected; was it in the same run, by another plane from the CAP group ?
                                    if targetData.seenAt == timestamp then
                                        -- yes, we can only increase the priority (never decrease it)
-- LOGGING DISABLED WHEN COMPILING("redetection (same run) of targetName=%s", veaf.p(targetName))
                                        if targetData.priority < targetPriority then
-- LOGGING DISABLED WHEN COMPILING("increasing priority of targetName=%s to %s", veaf.p(targetName), veaf.p(targetPriority))
                                            targetData.priority = targetPriority
                                        end
                                    else
                                        -- no, it's an old target, let's mark it as old
-- LOGGING DISABLED WHEN COMPILING("redetection (previous run) of targetName=%s", veaf.p(targetName))
                                        targetData.isNew = false
                                    end
                                else
                                    -- new target! register in into the target list
-- LOGGING DISABLED WHEN COMPILING("new detection of targetName=%s, priority=%s", veaf.p(targetName), veaf.p(targetPriority))
                                    targetsList[targetId] = {isNew = true, seenAt = timestamp, priority = targetPriority, targetId = targetId, unit = unit}
                                end
                            end
                        end
                    end
                end
            end
        end
    end

    if capLanded then
        capGroup:destroy()
-- LOGGING DISABLED WHEN COMPILING("CAP group %s is landed, destroying it and stopping watchdog", veaf.p(capGroupName))
        return
    end

    local controller = capGroup:getController()
    if capInZone then
-- LOGGING DISABLED WHEN COMPILING("CAP group is still in the CAP zone...")
        if not controller then
            veaf.loggers.get(veafSpawn.Id):error("cannot find controller for CAP group!")
            return
        end

        controller:setOption(AI.Option.Air.id.PROHIBIT_AA, false)
        controller:setOption(0,0) --weapons free
        --sort the list in reverse priority order so that the last task to be pushed in spot #1 is the one with the lowest priority, couldn't quite figure out which way works best, since this one makes the least sense it seems appropriate for DCS
        table.sort(targetsList, function(a,b) return a.priority < b.priority end)
-- LOGGING DISABLED WHEN COMPILING("targetsList=%s", veaf.p(targetsList))
        local foundTargets = false
        for targetId, targetData in pairs(targetsList) do
            if not foundTargets then
                -- only write that once!
-- LOGGING DISABLED WHEN COMPILING("Watchdog has targets ! Allowing AA for CAP")
                foundTargets = true
            end
            if not Unit.isExist(targetData.unit) or not targetData.unit:inAir() or timestamp > targetData.seenAt + veafSpawn.CAP_WATCHDOG_DELAY*2 then
-- LOGGING DISABLED WHEN COMPILING("Target is outdated, landed or doesn't exist, removing it from the list")
                targetsList[targetId] = nil
            else
-- LOGGING DISABLED WHEN COMPILING("Engaging target!")
                local engageUnit = {
                    id = 'EngageUnit',
                    params = {
                        unitId = targetId,
                        weaponType = "ALL",
                        priority = targetData.priority,
                    }
                }
                controller:pushTask(engageUnit)
                numberOfTasksAddedByWatchdog = numberOfTasksAddedByWatchdog + 1
            end
        end

        if not foundTargets then
            -- no targets, let's remove all the tasks we added (taking care not to remove the original task, which is to fly along the route)
-- LOGGING DISABLED WHEN COMPILING("Watchdog found no targets, removing all tasks and prohibiting AA for CAP")
            while controller:hasTask() and numberOfTasksAddedByWatchdog > 0 do
-- LOGGING DISABLED WHEN COMPILING("numberOfTasksAddedByWatchdog=%s", veaf.p(numberOfTasksAddedByWatchdog))
                controller:resetTask()
-- LOGGING DISABLED WHEN COMPILING("resetTask() called")
                numberOfTasksAddedByWatchdog = numberOfTasksAddedByWatchdog - 1
            end
            controller:setOption(AI.Option.Air.id.PROHIBIT_AA, true)
            controller:setOption(0,3) --return fire
        end
    else
-- LOGGING DISABLED WHEN COMPILING("CAP is outside of its area ! Discarding targets...")
        controller:setOption(AI.Option.Air.id.PROHIBIT_AA, true)
        controller:setOption(0,3) --return fire
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("Rescheduling watchdog in %s seconds", veafSpawn.CAP_WATCHDOG_DELAY))
-- LOGGING DISABLED WHEN COMPILING("===============================================================================")
    mist.scheduleFunction(veafSpawn.startCapWatchdog, {capGroupName, capCoalition, capZone, targetsList, numberOfTasksAddedByWatchdog}, timer.getTime() + veafSpawn.CAP_WATCHDOG_DELAY)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Mission master features
-------------------------------------------------------------------------------------------------------------------------------------------------------------
veafSpawn.missionMasterRunnables = {}
veafSpawn.missionMasterRunnables.__silent = true

function veafSpawn.missionMasterSetMessagingMode(silent, toGroupId)
    veafSpawn.missionMasterRunnables.__silent = silent
    veafSpawn.missionMasterRunnables.__toGroupId = toGroupId
end

function veafSpawn.missionMasterOutText(message)
    -- don't send the message if __silent is true
    if not(veafSpawn.missionMasterRunnables.__silent) then
        if (veafSpawn.missionMasterRunnables.__toGroupId) then
            -- send to a group
            trigger.action.outTextForGroup(veafSpawn.missionMasterRunnables.__toGroupId, message, 5)
        else
            -- send to all
            trigger.action.outText(message, 5)
        end
    end
end

function veafSpawn.missionMasterAddRunnable(name, code, parameters)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.missionMasterAddRunnable(name=%s)",name)
    veafSpawn.missionMasterRunnables[veaf.ifnn(name, "upper")] = { code, parameters }
end

function veafSpawn.missionMasterRun(name)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.missionMasterRun(name=%s)",name)
    if not name or #name == 0 then
        local message = "Mission Master, `run` requires the name of the code to be run"
        veaf.loggers.get(veafSpawn.Id):warn(message)
        veafSpawn.missionMasterOutText(message)
        return
    end

    local code, parameters = veaf.safeUnpack(veafSpawn.missionMasterRunnables[veaf.ifnn(name, "upper")])
    if code then
        local sta, res = pcall(code, parameters)
        if sta then
            local message = string.format("Mission Master, the runnable [%s] was successfully run and returned : %s", name, veaf.p(res))
            veaf.loggers.get(veafSpawn.Id):warn(message)
            veafSpawn.missionMasterOutText(message)
        else
            local message = string.format("Mission Master, the runnable [%s] returned an error : %s", name, veaf.p(res))
            veaf.loggers.get(veafSpawn.Id):warn(message)
            veafSpawn.missionMasterOutText(message)
        end
    else
        local message = string.format("Mission Master, the runnable [%s] does not exist", name)
        veaf.loggers.get(veafSpawn.Id):warn(message)
        veafSpawn.missionMasterOutText(message)
    end
end

function veafSpawn.missionMasterSetFlagFromTable(parameters)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.missionMasterSetFlagFromTable(parameters=%s)", parameters)
    local name, value = veaf.safeUnpack(parameters)
    return veafSpawn.missionMasterSetFlag(name, value)
end

function veafSpawn.missionMasterIncrementFlagValue(name)
    veafSpawn.missionMasterAddValueToFlag(name, 1)
end

function veafSpawn.missionMasterDecrementFlagValue(name)
    veafSpawn.missionMasterAddValueToFlag(name, -1)
end

function veafSpawn.missionMasterAddValueToFlag(name, increment)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.missionMasterIncrementFlagValue(name=%s, increment=%s)", name, increment)
    if not name then
        local message = "Mission Master, `setFlag` requires the name or number of the flag"
        veaf.loggers.get(veafSpawn.Id):warn(message)
        veafSpawn.missionMasterOutText(message)
        return
    end
    local value = trigger.misc.getUserFlag(name)
    if not value then value = 0 end
    trigger.action.setUserFlag(name , value + increment)
end

function veafSpawn.missionMasterSetFlag(name, value)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.missionMasterSetFlag(name=%s, value=%s)", name, value)
    if not name then
        local message = "Mission Master, `setFlag` requires the name or number of the flag"
        veaf.loggers.get(veafSpawn.Id):warn(message)
        veafSpawn.missionMasterOutText(message)
        return
    end
    trigger.action.setUserFlag(name , value)
end

function veafSpawn.missionMasterGetFlag(name)
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.missionMasterGetFlag(name=%s)", name)
    if not name then
        local message = "Mission Master, `getFlag` requires the name or number of the flag"
        veaf.loggers.get(veafSpawn.Id):warn(message)
        veafSpawn.missionMasterOutText(message)
        return
    end
    local value = trigger.misc.getUserFlag(name)
    local message = string.format("Mission Master, flag [%s] has value [%s]", name, veaf.p(value))
    veaf.loggers.get(veafSpawn.Id):info(message)
    veafSpawn.missionMasterOutText(message)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Build the initial radio menu
function veafSpawn.buildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSpawn.buildRadioMenu() hideMenu%s", veaf.p(veafSpawn.HideRadioMenu)))
    if not veafSpawn.HideRadioMenu then
        veafSpawn.rootPath = veafRadio.addSubMenu(veafSpawn.RadioMenuName)
        veafRadio.addCommandToSubmenu("Available Aircraft spawns", veafSpawn.rootPath, veafSpawn.listAllCAP, nil, veafRadio.USAGE_ForAll)
        veafRadio.addCommandToSubmenu("Info on all convoys", veafSpawn.rootPath, veafSpawn.infoOnAllConvoys, nil, veafRadio.USAGE_ForGroup)
        local menuPath = veafRadio.addSubMenu("Mark closest convoy route", veafSpawn.rootPath)
        veafRadio.addCommandToSubmenu("Mark closest convoy route" , menuPath, veafSpawn.markClosestConvoyRouteWithSmoke, nil, veafRadio.USAGE_ForGroup)
        local menuPath = veafRadio.addSubMenu("Mark closest convoy", veafSpawn.rootPath)
        veafRadio.addCommandToSubmenu("Mark closest convoy" , menuPath, veafSpawn.markClosestConvoyWithSmoke, nil, veafRadio.USAGE_ForGroup)
        local menuPath = veafRadio.addSubMenu("Stop closest convoy", veafSpawn.rootPath)
        veafRadio.addCommandToSubmenu("Stop closest convoy" , menuPath, veafSpawn.stopClosestConvoy, nil, veafRadio.USAGE_ForGroup)
        local menuPath = veafRadio.addSubMenu("Makes closest convoy move", veafSpawn.rootPath)
        veafRadio.addCommandToSubmenu("Make closest convoy move" , menuPath, veafSpawn.moveClosestConvoy, nil, veafRadio.USAGE_ForGroup)
        veafRadio.addSecuredCommandToSubmenu('Cleanup all convoys', veafSpawn.rootPath, veafSpawn.cleanupAllConvoys)
        veafRadio.refreshRadioMenu()
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafSpawn.initialize()
    veafSpawn.buildRadioMenu()
    veafSpawn.initializeAirUnitTemplates()
    veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafSpawn.onEventMarkChange)
    veafSpawn.dumpSpawnablePlanesList()
end

veaf.loggers.get(veafSpawn.Id):info(string.format("Loading version %s", veafSpawn.Version))

------------------ END script veafSpawn.lua  ------------------


------------------ START script veafSecurity.lua  ------------------

------------------------------------------------------------------
-- VEAF security function library for DCS World
-- By zip (2019)
--
-- Features:
-- ---------
-- * Checks if the user is part of an authorized users shortlist
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafSecurity = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafSecurity.Id = "SECURITY"

--- Version.
veafSecurity.Version = "1.3.3"

-- trace level, specific to this module
--veafSecurity.LogLevel = "trace"

veaf.loggers.new(veafSecurity.Id, veafSecurity.LogLevel)

--- Key phrase to look for in the mark text which triggers the command.
veafSecurity.Keyphrase = "_auth"

veafSecurity.authDuration = 10

veafSecurity.RemoteCommandParser = "([[a-zA-Z0-9]+)%s?(.*)"

veafSecurity.LEVEL_L0 = 90
veafSecurity.LEVEL_L1 = 10
veafSecurity.LEVEL_L9 = 1

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafSecurity.password_L0 = {}
veafSecurity.password_L1 = {}
veafSecurity.password_L9 = {}
veafSecurity.password_MM = {}

-- list the security passwords common to all missions below
veafSecurity.PASSWORD_L0 = "47c7808d1079fd20add322bbd5cf23b93ad1841e"
veafSecurity.PASSWORD_L1 = "bdc82f5ef92369919a3a53515023ce19f68656cc"
veafSecurity.password_L0[veafSecurity.PASSWORD_L0] = true
veafSecurity.password_L1[veafSecurity.PASSWORD_L1] = true

veafSecurity.authenticated = veaf.SecurityDisabled

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- SHA-1 pure LUA implementation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- $Revision: 1.5 $
-- $Date: 2014-09-10 16:54:25 $

-- This module was originally taken from http://cube3d.de/uploads/Main/sha1.txt.

-------------------------------------------------------------------------------
-- SHA-1 secure hash computation, and HMAC-SHA1 signature computation,
-- in pure Lua (tested on Lua 5.1)
-- License: MIT
--
-- Usage:
-- local hashAsHex = sha1.hex(message) -- returns a hex string
-- local hashAsData = sha1.bin(message) -- returns raw bytes
--
-- local hmacAsHex = sha1.hmacHex(key, message) -- hex string
-- local hmacAsData = sha1.hmacBin(key, message) -- raw bytes
--
--
-- Pass sha1.hex() a string, and it returns a hash as a 40-character hex string.
-- For example, the call
--
-- local hash = sha1.hex("iNTERFACEWARE")
--
-- puts the 40-character string
--
-- "e76705ffb88a291a0d2f9710a5471936791b4819"
--
-- into the variable 'hash'
--
-- Pass sha1.hmacHex() a key and a message, and it returns the signature as a
-- 40-byte hex string.
--
--
-- The two "bin" versions do the same, but return the 20-byte string of raw
-- data that the 40-byte hex strings represent.
--
-------------------------------------------------------------------------------
--
-- Description
-- Due to the lack of bitwise operations in 5.1, this version uses numbers to
-- represents the 32bit words that we combine with binary operations. The basic
-- operations of byte based "xor", "or", "and" are all cached in a combination
-- table (several 64k large tables are built on startup, which
-- consumes some memory and time). The caching can be switched off through
-- setting the local cfg_caching variable to false.
-- For all binary operations, the 32 bit numbers are split into 8 bit values
-- that are combined and then merged again.
--
-- Algorithm: http://www.itl.nist.gov/fipspubs/fip180-1.htm
--
-------------------------------------------------------------------------------

sha1 = {}

-- set this to false if you don't want to build several 64k sized tables when
-- loading this file (takes a while but grants a boost of factor 13)
local cfg_caching = false

-- local storing of global functions (minor speedup)
local floor,modf = math.floor,math.modf
local char,format,rep = string.char,string.format,string.rep

-- merge 4 bytes to an 32 bit word
local function bytes_to_w32 (a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end
-- split a 32 bit word into four 8 bit numbers
local function w32_to_bytes (i)
    return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100
end

-- shift the bits of a 32 bit word. Don't use negative values for "bits"
local function w32_rot (bits,a)
    local b2 = 2^(32-bits)
    local a,b = modf(a/b2)
    return a+b*b2*(2^(bits))
end

-- caching function for functions that accept 2 arguments, both of values between
-- 0 and 255. The function to be cached is passed, all values are calculated
-- during loading and a function is returned that returns the cached values (only)
local function cache2arg (fn)
    if not cfg_caching then return fn end
    local lut = {}
    for i=0,0xffff do
      local a,b = floor(i/0x100),i%0x100
      lut[i] = fn(a,b)
    end
    return function (a,b)
      return lut[a*0x100+b]
    end
end

-- splits an 8-bit number into 8 bits, returning all 8 bits as booleans
local function byte_to_bits (b)
    local b = function (n)
      local b = floor(b/n)
      return b%2==1
    end
    return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128)
end

-- builds an 8bit number from 8 booleans
local function bits_to_byte (a,b,c,d,e,f,g,h)
    local function n(b,x) return b and x or 0 end
    return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128)
end

-- debug function for visualizing bits in a string
local function bits_to_string (a,b,c,d,e,f,g,h)
    local function x(b) return b and "1" or "0" end
    return ("%s%s%s%s %s%s%s%s"):format(x(a),x(b),x(c),x(d),x(e),x(f),x(g),x(h))
end

-- debug function for converting a 8-bit number as bit string
local function byte_to_bit_string (b)
    return bits_to_string(byte_to_bits(b))
end

-- debug function for converting a 32 bit number as bit string
local function w32_to_bit_string(a)
    if type(a) == "string" then return a end
    local aa,ab,ac,ad = w32_to_bytes(a)
    local s = byte_to_bit_string
    return ("%s %s %s %s"):format(s(aa):reverse(),s(ab):reverse(),s(ac):reverse(),s(ad):reverse()):reverse()
end

-- bitwise "and" function for 2 8bit number
local band = cache2arg (function(a,b)
      local A,B,C,D,E,F,G,H = byte_to_bits(b)
      local a,b,c,d,e,f,g,h = byte_to_bits(a)
      return bits_to_byte(
          A and a, B and b, C and c, D and d,
          E and e, F and f, G and g, H and h)
    end)

-- bitwise "or" function for 2 8bit numbers
local bor = cache2arg(function(a,b)
      local A,B,C,D,E,F,G,H = byte_to_bits(b)
      local a,b,c,d,e,f,g,h = byte_to_bits(a)
      return bits_to_byte(
          A or a, B or b, C or c, D or d,
          E or e, F or f, G or g, H or h)
    end)

-- bitwise "xor" function for 2 8bit numbers
local bxor = cache2arg(function(a,b)
      local A,B,C,D,E,F,G,H = byte_to_bits(b)
      local a,b,c,d,e,f,g,h = byte_to_bits(a)
      return bits_to_byte(
          A ~= a, B ~= b, C ~= c, D ~= d,
          E ~= e, F ~= f, G ~= g, H ~= h)
    end)

-- bitwise complement for one 8bit number
local function bnot (x)
    return 255-(x % 256)
end

-- creates a function to combine to 32bit numbers using an 8bit combination function
local function w32_comb(fn)
    return function (a,b)
      local aa,ab,ac,ad = w32_to_bytes(a)
      local ba,bb,bc,bd = w32_to_bytes(b)
      return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd))
    end
end

-- create functions for and, xor and or, all for 2 32bit numbers
local w32_and = w32_comb(band)
local w32_xor = w32_comb(bxor)
local w32_or = w32_comb(bor)

-- xor function that may receive a variable number of arguments
local function w32_xor_n (a,...)
    local aa,ab,ac,ad = w32_to_bytes(a)
    for i=1,select('#',...) do
      local ba,bb,bc,bd = w32_to_bytes(select(i,...))
      aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd)
    end
    return bytes_to_w32(aa,ab,ac,ad)
end

-- combining 3 32bit numbers through binary "or" operation
local function w32_or3 (a,b,c)
    local aa,ab,ac,ad = w32_to_bytes(a)
    local ba,bb,bc,bd = w32_to_bytes(b)
    local ca,cb,cc,cd = w32_to_bytes(c)
    return bytes_to_w32(
      bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd))
    )
end

-- binary complement for 32bit numbers
local function w32_not (a)
    return 4294967295-(a % 4294967296)
end

-- adding 2 32bit numbers, cutting off the remainder on 33th bit
local function w32_add (a,b) return (a+b) % 4294967296 end

-- adding n 32bit numbers, cutting off the remainder (again)
local function w32_add_n (a,...)
    for i=1,select('#',...) do
      a = (a+select(i,...)) % 4294967296
    end
    return a
end
-- converting the number to a hexadecimal string
local function w32_to_hexstring (w) return format("%08x",w) end

-- calculating the SHA1 for some text
function sha1.hex(msg)
    local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
    local msg_len_in_bits = #msg * 8

    local first_append = char(0x80) -- append a '1' bit plus seven '0' bits

    local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length
    local current_mod = non_zero_message_bytes % 64
    local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or ""

    -- now to append the length as a 64-bit number.
    local B1, R1 = modf(msg_len_in_bits / 0x01000000)
    local B2, R2 = modf( 0x01000000 * R1 / 0x00010000)
    local B3, R3 = modf( 0x00010000 * R2 / 0x00000100)
    local B4 = 0x00000100 * R3

    local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits
    .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits

    msg = msg .. first_append .. second_append .. L64

    assert(#msg % 64 == 0)

    local chunks = #msg / 64

    local W = { }
    local start, A, B, C, D, E, f, K, TEMP
    local chunk = 0

    while chunk < chunks do
      --
      -- break chunk up into W[0] through W[15]
      --
      start,chunk = chunk * 64 + 1,chunk + 1

      for t = 0, 15 do
          W[t] = bytes_to_w32(msg:byte(start, start + 3))
          start = start + 4
      end

      --
      -- build W[16] through W[79]
      --
      for t = 16, 79 do
          -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16).
          W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16]))
      end

      A,B,C,D,E = H0,H1,H2,H3,H4

      for t = 0, 79 do
          if t <= 19 then
            -- (B AND C) OR ((NOT B) AND D)
            f = w32_or(w32_and(B, C), w32_and(w32_not(B), D))
            K = 0x5A827999
          elseif t <= 39 then
            -- B XOR C XOR D
            f = w32_xor_n(B, C, D)
            K = 0x6ED9EBA1
          elseif t <= 59 then
            -- (B AND C) OR (B AND D) OR (C AND D
            f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D))
            K = 0x8F1BBCDC
          else
            -- B XOR C XOR D
            f = w32_xor_n(B, C, D)
            K = 0xCA62C1D6
          end

          -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;
          A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K),
          A, w32_rot(30, B), C, D
      end
      -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
      H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E)
    end
    local f = w32_to_hexstring
    return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4)
end

local function hex_to_binary(hex)
    return hex:gsub('..', function(hexval)
          return string.char(tonumber(hexval, 16))
      end)
end

function sha1.bin(msg)
    return hex_to_binary(sha1.hex(msg))
end

local xor_with_0x5c = {}
local xor_with_0x36 = {}
-- building the lookuptables ahead of time (instead of littering the source code
-- with precalculated values)
for i=0,0xff do
    xor_with_0x5c[char(i)] = char(bxor(i,0x5c))
    xor_with_0x36[char(i)] = char(bxor(i,0x36))
end

local blocksize = 64 -- 512 bits

function sha1.hmacHex(key, text)
    assert(type(key) == 'string', "key passed to hmacHex should be a string")
    assert(type(text) == 'string', "text passed to hmacHex should be a string")

    if #key > blocksize then
      key = sha1.bin(key)
    end

    local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), blocksize - #key)
    local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), blocksize - #key)

    return sha1.hex(key_xord_with_0x5c .. sha1.bin(key_xord_with_0x36 .. text))
end

function sha1.hmacBin(key, text)
    return hex_to_binary(sha1.hmacHex(key, text))
end
----------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- remote interface
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- execute command from the remote interface
function veafSecurity.executeCommandFromRemote(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSecurity.executeCommandFromRemote()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("parameters= %s", veaf.p(parameters)))
    local _pilot, _pilotName, _unitName, _command = unpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilot= %s", veaf.p(_pilot)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilotName= %s", veaf.p(_pilotName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_unitName= %s", veaf.p(_unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_command= %s", veaf.p(_command)))
    if not _pilot or not _command then
        return false
    end

    if _command then
        -- parse the command
        local _action, _parameters = _command:match(veafSecurity.RemoteCommandParser)
-- LOGGING DISABLED WHEN COMPILING(string.format("_action=%s",veaf.p(_action)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_parameters=%s",veaf.p(_parameters)))
        if _action and _action:lower() == "login" then
            if _pilot.level >= veafSecurity.LEVEL_L1 then
                veaf.loggers.get(veafSecurity.Id):info(string.format("[%s] is unlocking the mission",veaf.p(_pilotName)))
                veafSecurity.authenticate(_parameters, _unitName)
                return true
            else
                veaf.loggers.get(veafSecurity.Id):warn(string.format("[%s] has not the required level to unlock the mission",veaf.p(_pilotName)))
                return false
            end
        elseif _action and _action:lower() == "logout" then
            if _pilot.level >= veafSecurity.LEVEL_L1 then
                local _silent = _parameters and _parameters:lower() == "silent"
                veaf.loggers.get(veafSecurity.Id):info(string.format("[%s] is locking the mission",veaf.p(_pilotName)))
                veafSecurity.logout(not _silent, _unitName)
                return true
            else
                veaf.loggers.get(veafSecurity.Id):warn(string.format("[%s] has not the required level to lock the mission",veaf.p(_pilotName)))
                return false
            end
        end
    end
    return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafSecurity.onEventMarkChange(eventPos, event)
  if veafSecurity.executeCommand(eventPos, event.text) then
      -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
      trigger.action.removeMark(event.idx)
  end
end

function veafSecurity.executeCommand(eventPos, eventText, bypassSecurity)
    -- Check if marker has a text and the veafCasMission.keyphrase keyphrase.
    if eventText ~= nil and eventText:lower():find(veafSecurity.Keyphrase) then

      -- Analyse the mark point text and extract the keywords.
      local options = veafSecurity.markTextAnalysis(eventText)

      if options then
          -- Check options commands
          if options.login then
              -- check password
              if not (bypassSecurity or veafSecurity.checkPassword_L1(options.password)) then
                trigger.action.outText("password was not set or was not correct", 5)
                return false
              end
              veafSecurity.authenticate()
              return true
          elseif options.logout then
              veafSecurity.logout(true)
              return true
          end
      end
  end
  return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the mark text and extract keywords.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Extract keywords from mark text.
function veafSecurity.markTextAnalysis(text)

  -- Option parameters extracted from the mark text.
  local switch = {}

  switch.login = false

  switch.logout = false

  -- password
  switch.password = nil

  -- Check for correct keywords.
  local pos = text:lower():find(veafSecurity.Keyphrase)
  if not pos then
      return nil
  end

  -- the logout command or the password should follow a space
  local text = text:sub(pos+string.len(veafSecurity.Keyphrase)+1)

  if text and text:lower() == "logout" then
      switch.logout = true
  else
      switch.password = text
      switch.login = true
-- LOGGING DISABLED WHEN COMPILING(string.format("switch.password=[%s]",switch.password))
  end

  return switch

end

function veafSecurity.logout(withMessage, unitName)
  if not veafSecurity.authenticated and withMessage then
    veaf.outTextForUnit(unitName, "The system was already locked down", 5)
    return
  end
  veafSecurity.authenticated = false
  if withMessage then
    veaf.outTextForUnit(unitName, "The system has been locked down", 5)
  end
  veafRadio.refreshRadioMenu()
  if veafSecurity.logoutWatchdog then
    mist.removeFunction(veafSecurity.logoutWatchdog)
  end
end

--- authenticate all radios for a short time
function veafSecurity.authenticate(minutes, unitName)
    local actualMinutes = minutes or veafSecurity.authDuration
    if type(actualMinutes) == "string" and not(actualMinutes:match("%d+")) then
        actualMinutes = veafSecurity.authDuration
    end
    if not veafSecurity.authenticated then
        veaf.outTextForUnit(unitName, string.format("The system is authenticated for %d minutes", actualMinutes), 15)
        veafSecurity.authenticated = true
        veafRadio.refreshRadioMenu()
        if veafSecurity.logoutWatchdog then
            mist.removeFunction(veafSecurity.logoutWatchdog)
        end
        veafSecurity.logoutWatchdog = mist.scheduleFunction(veafSecurity.logout,{true},timer.getTime()+actualMinutes*60)
    end
end

function veafSecurity._checkPassword(password, level)
  if password == nil then
    return false
  end
-- LOGGING DISABLED WHEN COMPILING(string.format("checkPassword(password = %s)",password))
  local hash = sha1.hex(password)
-- LOGGING DISABLED WHEN COMPILING(string.format("hash = [%s]",hash))
  if level[hash] ~= nil then
-- LOGGING DISABLED WHEN COMPILING("user authenticated")
      return true
  else
-- LOGGING DISABLED WHEN COMPILING("user not found")
      return false
  end
end

function veafSecurity.checkPassword_L0(password)
  return
    veaf.SecurityDisabled
    or
    veafSecurity._checkPassword(password, veafSecurity.password_L0)
end

function veafSecurity.checkPassword_L1(password)
  return
    veaf.SecurityDisabled
    or
    veafSecurity._checkPassword(password, veafSecurity.password_L1)
    or
    veafSecurity._checkPassword(password, veafSecurity.password_L0)
end

function veafSecurity.checkPassword_L9(password)
  return
    veaf.SecurityDisabled
    or
    veafSecurity._checkPassword(password, veafSecurity.password_L9)
    or
    veafSecurity._checkPassword(password, veafSecurity.password_L1)
    or
    veafSecurity._checkPassword(password, veafSecurity.password_L0)
end

function veafSecurity.checkPassword_MM(password)
  return
    veaf.SecurityDisabled
    or
    veafSecurity._checkPassword(password, veafSecurity.password_MM)
end

function veafSecurity.getMarkerSecurityLevel(markId)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSecurity.getMarkerSecurityLevel([%s])",veaf.p(markId)))
  local _author = nil
  for _, panel in pairs(world.getMarkPanels( )) do
-- LOGGING DISABLED WHEN COMPILING("panel=%s", veaf.p(panel))
    if panel.idx == markId then
      _author = panel.author
    end
  end
  if _author == nil then
    -- markId may actually be the username if called from veafRemote - yes I know it's ugly
    _author = markId
  end
-- LOGGING DISABLED WHEN COMPILING("_author=%s",_author)
  local _user = veafRemote.getRemoteUser(_author)
-- LOGGING DISABLED WHEN COMPILING(string.format("_user = [%s]",veaf.p(_user)))
  if _user then
    return _user.level
  end
  return -1
end

function veafSecurity.checkSecurity_L0(password, markId)
    -- don't check the password if already logged in
  if veafSecurity.isAuthenticated() then return true end
  if veafSecurity.getMarkerSecurityLevel(markId) < veafSecurity.LEVEL_L0 and not veafSecurity.checkPassword_L0(password) then
    veaf.loggers.get(veafSecurity.Id):warn("You have to give the correct L0 password to do this")
    trigger.action.outText("Please use the ', password <L0 password>' option", 5)
    return false
  end
  return true
end

function veafSecurity.checkSecurity_L1(password, markId)
  -- don't check the password if already logged in
  if veafSecurity.isAuthenticated() then return true end
  if veafSecurity.getMarkerSecurityLevel(markId) < veafSecurity.LEVEL_L1 and not veafSecurity.checkPassword_L1(password) then
    veaf.loggers.get(veafSecurity.Id):warn("You have to give the correct L1 password to do this")
    trigger.action.outText("Please use the ', password <L1 password>' option", 5)
    return false
  end
  return true
end

function veafSecurity.checkSecurity_L9(password, markId)
  -- don't check the password if already logged in
  if veafSecurity.isAuthenticated() then return true end
  if veafSecurity.getMarkerSecurityLevel(markId) < veafSecurity.LEVEL_L9 and not veafSecurity.checkPassword_L9(password) then
    veaf.loggers.get(veafSecurity.Id):warn("You have to give the correct L9 password to do this")
    trigger.action.outText("Please use the ', password <L9 password>' option", 5)
    return false
  end
  return true
end

function veafSecurity.checkSecurity_MM(password)
  if not veafSecurity.checkPassword_MM(password) then
    veaf.loggers.get(veafSecurity.Id):warn("You have to give the correct Mission Master password to do this")
    trigger.action.outText("Please use the ', password <MM password>' option", 5)
    return false
  end
  return true
end

function veafSecurity.isAuthenticated()
  return veafSecurity.authenticated or veafSecurity.SecurityDisabled
end

function veafSecurity.initialize()
  veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafSecurity.onEventMarkChange)
  veafSecurity.authenticated = veaf.SecurityDisabled
end

veaf.loggers.get(veafSecurity.Id):info(string.format("Loading version %s", veafSecurity.Version))


------------------ END script veafSecurity.lua  ------------------


------------------ START script veafShortcuts.lua  ------------------

------------------------------------------------------------------
-- VEAF shortcuts supporting functions for DCS World
-- By zip (2020)
--
-- Features:
-- ---------
-- * This module offers support for commands aliases and radio menu shortcuts
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafShortcuts = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafShortcuts.Id = "SHORTCUTS"

--- Version.
veafShortcuts.Version = "1.37.0"

-- trace level, specific to this module
--veafShortcuts.LogLevel = "trace"

veaf.loggers.new(veafShortcuts.Id, veafShortcuts.LogLevel)

veafShortcuts.RadioMenuName = "SHORTCUTS"

veafShortcuts.AliasStarter = "-"

veafShortcuts.RemoteCommandParser = "([a-zA-Z0-9:\\.-]+)%s(.*)"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- Aliases list (table of VeafAlias objects)
veafShortcuts.aliases = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafAlias object
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafAlias = {}

function VeafAlias:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self
    
    -- init the new object
    
    -- name
    objectToCreate.name = nil
    -- description
    objectToCreate.description = nil
    -- hidden from HELP
    objectToCreate.hidden = false
    -- the command that must be substituted to the alias
    objectToCreate.veafCommand = nil
    -- list of parameters that will be randomized if not present
    objectToCreate.randomParameters = {}
    -- if TRUE, security is bypassed
    objectToCreate.bypassSecurity = false
    -- if set, the alias will actually be a batch of aliases to execute in order
    objectToCreate.batchAliases = nil
    -- if set, the alias is password protected with a specific password
    objectToCreate.password = nil
    -- if set, we'll consider that the alias ends with a comma (to easily add the first parameter)
    objectToCreate.endsWithComma = true

    return objectToCreate
end

---
--- setters and getters
---

function VeafAlias:setName(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafAlias[%s]:setName([%s])", veaf.p(self.name) or "", value or ""))
    self.name = value
    return self
end

function VeafAlias:getName()
    return self.name
end

function VeafAlias:setVeafCommand(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafAlias[%s]:setVeafCommand([%s])", veaf.p(self.name), value or ""))
    self.veafCommand = value
    return self
end

function VeafAlias:getVeafCommand()
    return self.veafCommand
end

function VeafAlias:setEndsWithComma(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafAlias[%s]:setEndsWithComma([%s])", veaf.p(self.name), value or ""))
    self.endsWithComma = value
    return self
end

function VeafAlias:isEndsWithComma()
    return self.endsWithComma
end

function VeafAlias:addRandomParameter(name, low, high)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafAlias[%s]:addRandomParameter([%s], %s, %s)", veaf.p(self.name), name or "", low or "", high or ""))
    table.insert(self.randomParameters, { name = name, low = low or 1, high = high or 6})
    return self
end

function VeafAlias:getRandomParameters()
    return self.randomParameters
end

function VeafAlias:dontEndWithComma()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafAlias[%s]:dontEndWithComma()", veaf.p(self.name)))
    self:setEndsWithComma(false)
    return self
end

function VeafAlias:setDescription(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafAlias[%s]:setDescription([%s])", veaf.p(self.name), value or ""))
    self.description = value
    return self
end

function VeafAlias:getDescription()
    return self.description
end

function VeafAlias:setBypassSecurity(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafAlias[%s]:setBypassSecurity([%s])", veaf.p(self.name), tostring(value) or ""))
    self.bypassSecurity = value
    return self
end

function VeafAlias:isBypassSecurity()
    return self.bypassSecurity
end

function VeafAlias:setHidden(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafAlias[%s]:setHidden([%s])", veaf.p(self.name), tostring(value) or ""))
    self.hidden = value
    return self
end

function VeafAlias:isHidden()
    return self.hidden
end

function VeafAlias:setBatchAliases(value)
-- LOGGING DISABLED WHEN COMPILING("VeafAlias[%s]:setBatchAliases([%s])", veaf.p(self.name), veaf.p(value))
    self.batchAliases = value
    -- by default, batches are hidden and have a L1 password
    self:setPassword(veafSecurity.PASSWORD_L1)
    self:setHidden(true)
    return self
end

function VeafAlias:getBatchAliases()
    return self.batchAliases
end

function VeafAlias:setPassword(value)
-- LOGGING DISABLED WHEN COMPILING("VeafAlias[%s]:setPassword([%s])", veaf.p(self.name), veaf.p(value))
    self.password = {}
    self.password[value] = true
    return self
end

function VeafAlias:hasPassword(value)
    return self.password and self.password[value]
end

function VeafAlias:execute(remainingCommand, position, coalition, markId, bypassSecurity, spawnedGroups, route)
    local function logDebug(message)
-- LOGGING DISABLED WHEN COMPILING(message)
        return true
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("markId=[%s]",veaf.p(markId)))

    local command = self:getVeafCommand()
    for _, parameter in pairs(self:getRandomParameters()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("randomizing [%s]",parameter.name or ""))
        local value = math.random(parameter.low, parameter.high)
-- LOGGING DISABLED WHEN COMPILING(string.format("got [%d]",value))
        command = string.format("%s, %s %d",command, parameter.name, value)
    end
    if self:isEndsWithComma() then
-- LOGGING DISABLED WHEN COMPILING("adding a comma")
        command = command .. ", "
    end

    local _bypassSecurity = bypassSecurity or self:isBypassSecurity()

    local command = command .. (remainingCommand or "")
-- LOGGING DISABLED WHEN COMPILING(string.format("command = [%s]",command or ""))

    if logDebug("checking in veafShortcuts") and veafShortcuts.executeCommand(position, command, coalition, markId, _bypassSecurity, spawnedGroups, route) then
        return true
    elseif logDebug("checking in veafSpawn") and veafSpawn.executeCommand(position, command, coalition, markId, _bypassSecurity, spawnedGroups, nil, nil, route) then
        return true
    elseif logDebug("checking in veafNamedPoints") and veafNamedPoints.executeCommand(position, {text=command, coalition=-1}, _bypassSecurity) then
        return true
    elseif logDebug("checking in veafCasMission") and veafCasMission.executeCommand(position, command, coalition, _bypassSecurity) then
        return true
    elseif logDebug("checking in veafSecurity") and veafSecurity.executeCommand(position, command, _bypassSecurity) then
        return true
    elseif logDebug("checking in veafMove") and veafMove.executeCommand(position, command, _bypassSecurity) then
        return true
    elseif logDebug("checking in veafRadio") and veafRadio.executeCommand(position, command, coalition, _bypassSecurity) then
        return true
    elseif logDebug("checking in veafGroundAI") and veafGroundAI.executeCommand(position, command, coalition, _bypassSecurity) then
        return true
    elseif logDebug("checking in veafRemote") and veafRemote.executeCommand(position, command) then
        return true
    else
        return false
    end
end

---
--- other methods
---

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafAliasForCombatMission object
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafAliasForCombatMission = {}
VeafAliasForCombatMission.__index = VeafAliasForCombatMission

function VeafAliasForCombatMission:new()
    local self = setmetatable(mist.utils.deepCopy(VeafAlias:new()), VeafAliasForCombatMission)
    self:setPassword(veafSecurity.PASSWORD_L1)
    self:setHidden(true)
    return self
end

setmetatable(VeafAliasForCombatMission, {__index = VeafAlias})

---
--- overloaded members
---

function VeafAliasForCombatMission:execute(remainingCommand, position, coalition, markId, bypassSecurity, spawnedGroups)
-- LOGGING DISABLED WHEN COMPILING("VeafAliasForCombatMission[%s]:execute([%s])", veaf.p(self.name), veaf.p(remainingCommand))

    local command = self:getVeafCommand()
    for _, parameter in pairs(self:getRandomParameters()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("randomizing [%s]",parameter.name or ""))
        local value = math.random(parameter.low, parameter.high)
-- LOGGING DISABLED WHEN COMPILING(string.format("got [%d]",value))
        command = string.format("%s, %s %d",command, parameter.name, value)
    end
    if self:isEndsWithComma() then
-- LOGGING DISABLED WHEN COMPILING("adding a comma")
        command = command .. ", "
    end

    local _bypassSecurity = bypassSecurity or self:isBypassSecurity()

    local command = command .. (remainingCommand or "")
-- LOGGING DISABLED WHEN COMPILING("command=%s", veaf.p(command))

    local keywords = veaf.split(command, ",")

    local silent = false
    local missionName = nil
    local password = nil
    for _, keyphrase in pairs(keywords) do
        local str = veaf.breakString(veaf.trim(keyphrase), " ")
        local key = str[1]
        local val = str[2] or ""

        if key:lower() == "silent" then
            silent = true
        end

        if key:lower() == "name" then
            missionName = val
        end

        if key:lower() == "password" then
            password = val
        end
    end

    if not (bypassSecurity or veafSecurity.isAuthenticated()) then
-- LOGGING DISABLED WHEN COMPILING("password=%s", veaf.p(password))
        local hash = nil
        if password then 
            hash = sha1.hex(password)
        end
        if not(self:hasPassword(hash)) then
            veaf.loggers.get(veafShortcuts.Id):warn("You have to give the correct alias password for %s to do this", self:getName())
            trigger.action.outText("Please use the ', password <alias password>' option", 5)
            return false
        end
    end

-- LOGGING DISABLED WHEN COMPILING("missionName=%s", veaf.p(missionName))
-- LOGGING DISABLED WHEN COMPILING("silent=%s", veaf.p(silent))

    if not missionName or #missionName == 0 then
        local msg = string.format("VeafAliasForCombatMission: mission name is mandatory")
        veaf.loggers.get(veafShortcuts.Id):warn(msg)
        trigger.action.outText(msg, 5)
        return false
    end

    local mission = veafCombatMission.GetMission(missionName)
    if not mission then 
        local msg = string.format("VeafAliasForCombatMission: mission %s does not exist", veaf.p(missionName))
        veaf.loggers.get(veafShortcuts.Id):warn(msg)
        trigger.action.outText(msg, 5)
        return false
    end

-- LOGGING DISABLED WHEN COMPILING("mission=%s", veaf.p(mission))

    if command:lower():sub(1, 5) == "start" then
        local result = veafCombatMission.ActivateMission(missionName, silent)
        return result
    elseif command:lower():sub(1, 4) == "stop" then
        local result = veafCombatMission.DesactivateMission(missionName, silent)
        return result
    end

end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafAliasForCombatZone object
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafAliasForCombatZone = VeafAlias:new()

function VeafAliasForCombatZone:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object
    self:setPassword(veafSecurity.PASSWORD_L1)
    self:setHidden(true)  

    return objectToCreate
  end

---
--- overloaded members
---

function VeafAliasForCombatZone:execute(remainingCommand, position, coalition, markId, bypassSecurity, spawnedGroups)
-- LOGGING DISABLED WHEN COMPILING("VeafAliasForCombatZone[%s]:execute([%s])", veaf.p(self.name), veaf.p(remainingCommand))

    local command = self:getVeafCommand()
    for _, parameter in pairs(self:getRandomParameters()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("randomizing [%s]",parameter.name or ""))
        local value = math.random(parameter.low, parameter.high)
-- LOGGING DISABLED WHEN COMPILING(string.format("got [%d]",value))
        command = string.format("%s, %s %d",command, parameter.name, value)
    end
    if self:isEndsWithComma() then
-- LOGGING DISABLED WHEN COMPILING("adding a comma")
        command = command .. ", "
    end

    local _bypassSecurity = bypassSecurity or self:isBypassSecurity()

    local command = command .. (remainingCommand or "")
-- LOGGING DISABLED WHEN COMPILING("command=%s", veaf.p(command))

    local keywords = veaf.split(command, ",")

    local silent = false
    local zoneName = nil
    local password = nil
    for _, keyphrase in pairs(keywords) do
        local str = veaf.breakString(veaf.trim(keyphrase), " ")
        local key = str[1]
        local val = str[2] or ""

        if key:lower() == "silent" then
            silent = true
        end

        if key:lower() == "name" then
            zoneName = val
        end

        if key:lower() == "password" then
            password = val
        end
    end

    if not (bypassSecurity or veafSecurity.isAuthenticated()) then
-- LOGGING DISABLED WHEN COMPILING("password=%s", veaf.p(password))
        local hash = nil
        if password then 
            hash = sha1.hex(password)
        end
        if not(self:hasPassword(hash)) then
            veaf.loggers.get(veafShortcuts.Id):warn("You have to give the correct alias password for %s to do this", self:getName())
            trigger.action.outText("Please use the ', password <alias password>' option", 5)
            return false
        end
    end

-- LOGGING DISABLED WHEN COMPILING("zoneName=%s", veaf.p(zoneName))
-- LOGGING DISABLED WHEN COMPILING("silent=%s", veaf.p(silent))

    if not zoneName or #zoneName == 0 then
        local msg = string.format("VeafAliasForCombatZone: zone name is mandatory")
        veaf.loggers.get(veafShortcuts.Id):warn(msg)
        trigger.action.outText(msg, 5)
        return false
    end

    local zone = veafCombatZone.GetZone(zoneName)
    if not zone then 
        local msg = string.format("VeafAliasForCombatZone: zone %s does not exist", veaf.p(zoneName))
        veaf.loggers.get(veafShortcuts.Id):warn(msg)
        trigger.action.outText(msg, 5)
        return false
    end

-- LOGGING DISABLED WHEN COMPILING("zone=%s", veaf.p(zone))

    if command:lower():sub(1, 5) == "start" then
        local result = veafCombatZone.ActivateZone(zoneName, silent)
        return result
    elseif command:lower():sub(1, 4) == "stop" then
        local result = veafCombatZone.DesactivateZone(zoneName, silent)
        return result
    end

end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- global functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- search for an alias
function veafShortcuts.GetAlias(aliasName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafShortcuts.GetAlias([%s])",aliasName or ""))
-- LOGGING DISABLED WHEN COMPILING(string.format("Searching for alias with name [%s]", aliasName))

    -- find the desired alias in the aliases list
    local alias = veafShortcuts.aliases[aliasName:lower()]

    if not alias then 
        local message = string.format("VeafAlias [%s] was not found !",aliasName)
        veaf.loggers.get(veafShortcuts.Id):error(message)
        trigger.action.outText(message,5)
    end

    return alias
end

-- add an alias
function veafShortcuts.AddAlias(alias)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafShortcuts.AddAlias([%s])",alias:getName() or ""))
    veafShortcuts.aliases[alias:getName():lower()] = alias
    return alias
end

-- execute an alias command
function veafShortcuts.ExecuteAlias(aliasName, delay, remainingCommand, position, coalition, markId, bypassSecurity, spawnedGroups, route)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafShortcuts.ExecuteAlias([%s],[%s],[%s],[%s],[%s])", veaf.p(aliasName), veaf.p(delay), veaf.p(remainingCommand), veaf.p(position), veaf.p(coalition)))
-- LOGGING DISABLED WHEN COMPILING(string.format("markId=[%s]",veaf.p(markId)))
-- LOGGING DISABLED WHEN COMPILING(string.format("bypassSecurity=[%s]",veaf.p(bypassSecurity)))
-- LOGGING DISABLED WHEN COMPILING(string.format("route=[%s]",veaf.p(route)))

    local alias = veafShortcuts.GetAlias(aliasName)
    if alias then 
-- LOGGING DISABLED WHEN COMPILING(string.format("found VeafAlias[%s]",alias:getName() or ""))
        if alias:getBatchAliases() then -- no alias to actually execute, but instead run a batch
            -- the batch aliases are always password protected by a Mission Master password, so search for one
            local password = nil
            local keywords = veaf.split(remainingCommand, ",")

            for _, keyphrase in pairs(keywords) do
                local str = veaf.breakString(veaf.trim(keyphrase), " ")
                local key = str[1]
                local val = str[2] or ""

                if key:lower() == "password" then
                    password = val
                end
            end
            
            if not (bypassSecurity or veafSecurity.isAuthenticated()) then
-- LOGGING DISABLED WHEN COMPILING("password=%s", veaf.p(password))
                local hash = nil
                if password then 
                    hash = sha1.hex(password)
                end
                if not(alias:hasPassword(hash)) then
                    veaf.loggers.get(veafShortcuts.Id):warn("You have to give the correct alias password for %s to do this", alias:getName())
                    trigger.action.outText("Please use the ', password <alias password>' option", 5)
                    return false
                end
            end

            local _msg = string.format("running batch alias [%s] : %s", alias:getName(), alias:getDescription())
            veaf.loggers.get(veafShortcuts.Id):info(_msg)
            trigger.action.outText(_msg, 10)
    
            -- run the batch
            for index, textToExecute in ipairs(alias:getBatchAliases()) do
                veafShortcuts.executeCommand(position, textToExecute, coalition, markId, true, spawnedGroups, route)
            end
        else       
            if delay and delay ~= "" then
                mist.scheduleFunction(VeafAlias.execute, {alias, remainingCommand, position, coalition, markId, bypassSecurity, spawnedGroups, route}, timer.getTime() + delay)
            else
                alias:execute(remainingCommand, position, coalition, markId, bypassSecurity, spawnedGroups, route)
            end
        end
        return true
    else
        veaf.loggers.get(veafShortcuts.Id):error(string.format("veafShortcuts.ExecuteAlias : cannot find alias [%s]",aliasName or ""))
    end
    return false
end

-- execute an alias command
function veafShortcuts.ExecuteBatchAliasesList(aliasBatchList, delay, coalition, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafShortcuts.ExecuteBatchAliasesList([%s],[%s],[%s],[%s])", veaf.p(aliasBatchList), veaf.p(delay), veaf.p(coalition), veaf.p(silent)))
    if aliasBatchList and #aliasBatchList > 0 then -- run a batch

        local _msg = string.format("running batch list [%s]", veaf.p(aliasBatchList))
        veaf.loggers.get(veafShortcuts.Id):info(_msg)
        if not(silent) then trigger.action.outText(_msg, 10) end

        -- run the batch
        for index, textToExecute in ipairs(aliasBatchList) do
            veafShortcuts.executeCommand(nil, textToExecute, coalition, nil, true)
        end
        return true
    else
        veaf.loggers.get(veafShortcuts.Id):error(string.format("veafShortcuts.ExecuteBatchAliasesList : batch list is empty"))
    end
    return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafShortcuts.onEventMarkChange(eventPos, event)

    -- choose by default the coalition opposing the player who triggered the event
    local invertedCoalition = 1
    if event.coalition == 1 then
        invertedCoalition = 2
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("event.idx  = %s", veaf.p(event.idx)))

    if veafShortcuts.executeCommand(eventPos, event.text, invertedCoalition, event.idx) then 
        
        -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
        trigger.action.removeMark(event.idx)

    end
end

function veafShortcuts.executeCommand(eventPos, eventText, eventCoalition, markId, bypassSecurity, spawnedGroups, route)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafShortcuts.executeCommand(eventText=[%s])", eventText))

    -- Check if marker has a text and contains an alias
    if eventText ~= nil then
        
        -- Analyse the mark point text and extract the keywords.
        local alias, coords, delay, remainder = veafShortcuts.markTextAnalysis(eventText)

        if alias then
            local position = eventPos

            if coords and #coords > 0 then
                local _lat, _lon = veaf.computeLLFromString(coords)
-- LOGGING DISABLED WHEN COMPILING(string.format("_lat=%s",veaf.p(_lat)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_lon=%s",veaf.p(_lon)))
                if _lat and _lon then 
                    position = coord.LLtoLO(_lat, _lon)
-- LOGGING DISABLED WHEN COMPILING(string.format("position=%s",veaf.p(position)))
                else
                    local _msg = string.format("unable to decode coordinates [%s]", veaf.p(coords))
                    veaf.loggers.get(veafShortcuts.Id):warn(_msg)
                    trigger.action.outText(_msg, 5)
                    return
                end
            end
    
            -- do the magic
            return veafShortcuts.ExecuteAlias(alias, delay, remainder, position, eventCoalition, markId, bypassSecurity, spawnedGroups, route)
        end
        return false
    end

    -- None of the keywords matched.
    return false
end

--- Extract keywords from mark text.
function veafShortcuts.markTextAnalysis(text)
    if text then 
  
-- LOGGING DISABLED WHEN COMPILING(string.format("veafShortcuts.markTextAnalysis(text=[%s])", text))
    
        -- check for the alias starter
        if text:sub(1,1) == veafShortcuts.AliasStarter then
-- LOGGING DISABLED WHEN COMPILING("found veafShortcuts.AliasStarter")

            -- extract alias and remainder
            local alias, coords, delay, remainder = text:match("(-[^#^!^ ^,]+)#?([^!^,^%s]*)!?(%d*)(.*)")
-- LOGGING DISABLED WHEN COMPILING(string.format("alias=[%s]", veaf.p(alias)))
-- LOGGING DISABLED WHEN COMPILING(string.format("coords=[%s]", veaf.p(coords)))
-- LOGGING DISABLED WHEN COMPILING(string.format("delay=[%s]", veaf.p(delay)))
-- LOGGING DISABLED WHEN COMPILING(string.format("remainder=[%s]", veaf.p(remainder)))
            if alias then
-- LOGGING DISABLED WHEN COMPILING(string.format("alias = [%s]", alias))
                return alias, coords, delay, remainder
            end
        end

    end
    return nil
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- default aliases list
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafShortcuts.buildDefaultList()
    -- generic sam groups
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-samLR")
            :setDescription("Random long range SAM battery")
            :setVeafCommand("_spawn samgroup, skynet true")
            :addRandomParameter("defense", 4, 5)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-samSR")
            :setDescription("Random short range SAM battery")
            :setVeafCommand("_spawn samgroup, skynet true")
            :addRandomParameter("defense", 2, 3)
            :setBypassSecurity(false)
    )
    -- specific air defenses groups and units  
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-hq7")
            :setDescription("HQ-7 (Red Banner) battery")
            :setVeafCommand("_spawn group, name hq7, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-hq7_single")
            :setDescription("HQ-7 (Red Banner) launcher")
            :setVeafCommand("_spawn group, name hq7_single, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-hq7noew")
            :setDescription("HQ-7 (Red Banner) battery without EWR")
            :setVeafCommand("_spawn group, name hq7-noew, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-hq7eo")
            :setDescription("HQ-7EO (Red Banner) battery")
            :setVeafCommand("_spawn group, name hq7eo, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-hq7eo_single")
            :setDescription("HQ-7EO (Red Banner) launcher")
            :setVeafCommand("_spawn group, name hq7eo_single, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-hq7eo_noew")
            :setDescription("HQ-7EO (Red Banner) battery without EWR")
            :setVeafCommand("_spawn group, name hq7eo-noew, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa2")
            :setDescription("SA-2 Guideline (S-75 Dvina) battery")
            :setVeafCommand("_spawn group, name sa2, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa5")
            :setDescription("SA-5 Gammon (S-200 Dubna) battery")
            :setVeafCommand("_spawn group, name sa5, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa3")
            :setDescription("SA-3 Goa (S-125 Neva/Pechora) battery")
            :setVeafCommand("_spawn group, name sa3, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa6")
            :setDescription("SA-6 Gainful (2K12 Kub) battery")
            :setVeafCommand("_spawn group, name sa6, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa8")
            :setDescription("SA-8 Osa (9K33 Osa) sam vehicle")
            :setVeafCommand("_spawn group, name sa8_squad, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa9")
            :setDescription("SA-9 Strela-1 (9K31 Strela-1) sam vehicle")
            :setVeafCommand("_spawn unit, name Strela-1 9P31")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa9_squad")
            :setDescription("SA-9 Strela-1 (9K31 Strela-1) sam vehicle and logistic")
            :setVeafCommand("_spawn group, name sa9_squad")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa10")
            :setDescription("SA-10 Grumble (S-300) battery")
            :setVeafCommand("_spawn group, name sa10, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa11")
            :setDescription("SA-11 Gadfly (9K37 Buk) battery")
            :setVeafCommand("_spawn group, name sa11, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa13")
            :setDescription("SA-13 Strela (9A35M3) sam vehicle")
            :setVeafCommand("_spawn unit, name Strela-10M3")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa13_squad")
            :setDescription("SA-13 Strela (9A35M3) sam vehicle and logistic")
            :setVeafCommand("_spawn group, name sa13_squad")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa15")
            :setDescription("SA-15 Gauntlet (9K330 Tor) sam vehicle")
            :setVeafCommand("_spawn group, name sa15_squad, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-insurgent_manpad")
            :setDescription("Insurgent SA-18 manpad squad")
            :setVeafCommand("_spawn group, name ins_manpad")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa18")
            :setDescription("SA-18 manpad squad")
            :setVeafCommand("_spawn group, name sa18_squad")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa18s")
            :setDescription("SA-18S manpad squad")
            :setVeafCommand("_spawn group, name sa18s_squad")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sa19")
            :setDescription("SA-19 Tunguska (2K22 Tunguska) sam vehicle and logistic")
            :setVeafCommand("_spawn group, name sa19_squad, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-shilka")
            :setDescription("ZSU-23-4 Shilka AAA vehicle")
            :setVeafCommand("_spawn unit, name ZSU-23-4 Shilka")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-zu23")
            :setDescription("ZU-23 AAA vehicle")
            :setVeafCommand("_spawn unit, name Ural-375 ZU-23")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-manpads")
            :setDescription("mutiple SA-18S manpad soldier peppered in a wide radius")
            :setVeafCommand("_spawn unit, name SA-18 Igla-S manpad, radius 5000")
            :addRandomParameter("multiplier", 3, 6)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-roland")
            :setDescription("Roland battery with EWR (US by default)")
            :setVeafCommand("_spawn group, name roland, country USA, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-rolandnoew")
            :setDescription("Roland battery without EWR (US by default)")
            :setVeafCommand("_spawn group, name roland-noew, country USA, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-nasams")
            :setDescription("NASAMS battery with 120C (US by default)")
            :setVeafCommand("_spawn group, name nasams_c, country USA, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-nasams_b")
            :setDescription("NASAMS battery with 120B (US by default)")
            :setVeafCommand("_spawn group, name nasams_b, country USA, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-hawk")
            :setDescription("Hawk battery (US by default)")
            :setVeafCommand("_spawn group, name hawk, country USA, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-patriot")
            :setDescription("Patriot battery (US by default)")
            :setVeafCommand("_spawn group, name patriot, country USA, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-stinger")
            :setDescription("Stinger manpad squad (US by default)")
            :setVeafCommand("_spawn group, name stinger_squad, country USA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-avenger")
            :setDescription("Avenger SAM (US by default)")
            :setVeafCommand("_spawn unit, name avenger, country USA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-avenger_squad")
            :setDescription("Avenger SAM (US by default) and logistic")
            :setVeafCommand("_spawn group, name avenger_squad, country USA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-dogear")
            :setDescription("Dogear Radar")
            :setVeafCommand("_spawn unit, name dogear, skynet true, ewr")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-blue_ewr")
            :setDescription("F-117 Domed EWR (US by default)")
            :setVeafCommand("_spawn group, name blue_ewr, country USA, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-ewr")
            :setDescription("55G6 Mast EWR")
            :setVeafCommand("_spawn group, name ewr, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-burke")
            :setDescription("USS Arleigh Burke IIa destroyer (US by default)")
            :setVeafCommand("_spawn unit, name USS_Arleigh_Burke_IIa, country USA")
            :setBypassSecurity(false)
    )  
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-perry")
            :setDescription("O.H. Perry destroyer (US by default)")
            :setVeafCommand("_spawn unit, name PERRY, country USA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-ticonderoga")
            :setDescription("Ticonderoga frigate (US by default)")
            :setVeafCommand("_spawn unit, name TICONDEROG, country USA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-rezky")
            :setDescription("FF 1135M Rezky frigate (RU by default)")
            :setVeafCommand("_spawn unit, name REZKY, country RUSSIA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-pyotr")
            :setDescription("CGN 1144.2 Pyotr Velikiy (RU by default)")
            :setVeafCommand("_spawn unit, name PIOTR, country RUSSIA")
            :setBypassSecurity(false)
    )
    -- convoys
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-hv_convoy_red")
            :setDescription("Red High Value Attack convoy")
            :setVeafCommand("_spawn group, name hv_convoy_red, country RUSSIA, skynet false, alarm 0") --Alarm is set to 0, meaning Alarm state 0 (AUTO) for proper movement of the scud
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-attack_convoy_red")
            :setDescription("Red Attack convoy")
            :setVeafCommand("_spawn group, name convoy_red, country RUSSIA, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-QRC_red")
            :setDescription("Quick Reaction Convoy red") --it's fast
            :setVeafCommand("_spawn group, name QRC_red, country RUSSIA, skynet true, speed 90, spacing 2")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-civilian_convoy_red")
            :setDescription("Red Civilian convoy")
            :setVeafCommand("_spawn group, name civilian_convoy_red, country RUSSIA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-QRC_blue")
            :setDescription("Quick Reaction Convoy blue") --it's fast
            :setVeafCommand("_spawn group, name QRC_blue, country USA, skynet true, speed 90, spacing 2")
            :setBypassSecurity(false)
    )

    -- shortcuts to commands
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-point")
            :setDescription("Name a point on the map")
            :setVeafCommand("_name point")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the point name 
            :setBypassSecurity(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-destroy")
            :setDescription("Destroy any unit within 100m")
            :setVeafCommand("_destroy, radius 100")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-login")
            :setDescription("Unlock the system")
            :setHidden(true)
            :setVeafCommand("_auth")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the password 
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-logout")
            :setDescription("Lock the system")
            :setHidden(true)
            :setVeafCommand("_auth logout")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    -- shortcuts to specific groups
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-mortar")
            :setDescription("Mortar team")
            :setVeafCommand("_spawn group, name mortar, country USA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty")
            :setDescription("M-109 artillery battery")
            :setVeafCommand("_spawn group, name M-109, country USA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-msta")
            :setDescription("Msta artillery battery")
            :setVeafCommand("_spawn group, name msta")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-plz05")
            :setDescription("PLZ-05 artillery battery")
            :setVeafCommand("_spawn group, name plz05")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-mlrs")
            :setDescription("MLRS artillery battery")
            :setVeafCommand("_spawn group, name mlrs, country USA")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-smerch_he")
            :setDescription("Smerch HE artillery battery")
            :setVeafCommand("_spawn group, name smerchhe")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-smerch_cm")
            :setDescription("Smerch CM artillery battery")
            :setVeafCommand("_spawn group, name smerchcm")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-uragan")
            :setDescription("Uragan artillery battery")
            :setVeafCommand("_spawn group, name uragan")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-grad")
            :setDescription("Grad artillery battery")
            :setVeafCommand("_spawn group, name grad")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-cargoships")
            :setDescription("Cargo ships")
            :setVeafCommand("_spawn group, name cargoships-nodef, country RUSSIA, offroad, speed 60, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-escortedcargoships")
            :setDescription("Cargo ships (escorted)")
            :setVeafCommand("_spawn group, name cargoships-escorted, country RUSSIA, offroad, speed 60, skynet true")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-combatships")
            :setDescription("Combat ships")
            :setVeafCommand("_spawn group, name combatships, country RUSSIA, offroad, speed 60, skynet true")
            :setBypassSecurity(false)
    )
    -- shortcuts to dynamic groups
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-sam")
            :setDescription("Random SAM battery")
            :setVeafCommand("_spawn samgroup, skynet true")
            :addRandomParameter("defense", 1, 5)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-aaa")
            :setDescription("Random AAA battery")
            :setVeafCommand("_spawn samgroup, skynet true, spacing 1")
            :addRandomParameter("defense", 1, 2)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-armor")
            :setDescription("Dynamic armor group")
            :setVeafCommand("_spawn armorgroup")
            :addRandomParameter("defense", 1, 3)
            :addRandomParameter("armor", 2, 4)
            :addRandomParameter("size", 4, 8)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-infantry")
            :setDescription("Dynamic infantry section")
            :setVeafCommand("_spawn infantrygroup")
            :addRandomParameter("defense", 0, 5)
            :addRandomParameter("armor", 0, 5)
            :addRandomParameter("size", 4, 8)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-transport")
            :setDescription("Dynamic transport company")
            :setVeafCommand("_spawn transportgroup")
            :addRandomParameter("defense", 0, 3)
            :addRandomParameter("size", 10, 25)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-combat")
            :setDescription("Dynamic combat group")
            :setVeafCommand("_spawn combatgroup")
            :addRandomParameter("defense", 1, 3)
            :addRandomParameter("armor", 2, 4)
            :addRandomParameter("size", 1, 4)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-cas")
            :setDescription("Generate a random CAS group for training")
            :setVeafCommand("_cas, disperse")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-cargo")
            :setDescription("Generate a cargo for sling loading")
            :setVeafCommand("_spawn cargo, side blue, radius 0")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-refuel")
            :setDescription("spawns an US refuel group")
            :setVeafCommand("_spawn group, name US-refuel, country usa, radius 0")
            :setBypassSecurity(false)
    )
    -- radio shortcuts
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-send")
            :setDescription("Send radio message - needs \"MESSAGE\"")
            :setVeafCommand("_radio transmit, message")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the message content 
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-play")
            :setDescription("Play sound over radio - needs \"FILENAME\"")
            :setVeafCommand("_radio play, path")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the message content 
            :setBypassSecurity(false)
    )
    -- other shortcuts
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-convoy")
            :setDescription("Convoy - needs \", dest POINTNAME\"")
            :setVeafCommand("_spawn convoy")
            :addRandomParameter("defense", 0, 3)
            :addRandomParameter("armor", 0, 4)
            :addRandomParameter("size", 6, 15)
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-jtac")
            :setDescription("JTAC humvee")
            :setVeafCommand("_spawn jtac")
            :setBypassSecurity(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-afac")
            :setDescription("AFAC MQ-9 Reaper")
            :setVeafCommand("_spawn afac")
            :setBypassSecurity(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-afachere")
            :setDescription("move an afac to a specific location ; must follow with the afac group name ; can also set speed, alt and hdg")
            :setVeafCommand("_move afac, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    -- Ground unit AI
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-ai_set")
            :setDescription("Sets an AI handler for a group of units")
            :setVeafCommand("_ground set")
    )
    -- ARTY-1 commands
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty1")
            :setDescription("Spawns ARTY-1")
            :setBatchAliases({"-arty, unitname arty-1", "-ai_set, name arty-1, groupname arty-1"})
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty1_aim")
            :setDescription("Orders ARTY-1 to fire for aim")
            :setVeafCommand("_ground order, name arty-1, order aim;radius 15-30; target")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the target coordinates
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty1_fire")
            :setDescription("Orders ARTY-1 to fire for effect")
            :setVeafCommand("_ground order, name arty-1, order fire; radius 50-150; shells 40-80; target")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the target coordinates
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty1_stop")
            :setDescription("Orders ARTY-1 to stop listening for orders")
            :setVeafCommand("_ground stop, name arty-1")
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty1_start")
            :setDescription("Orders ARTY-1 to start listening for orders")
            :setVeafCommand("_ground start, name arty-1")
    )

    -- ARTY-2 commands
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty2")
            :setDescription("Spawns ARTY-2")
            :setBatchAliases({"-arty, unitname arty-2", "-ai_set, name arty-2, groupname arty-2"})
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty2_aim")
            :setDescription("Orders ARTY-2 to fire for aim")
            :setVeafCommand("_ground order, name arty-2, order aim;radius 15-30; target")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the target coordinates
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty2_fire")
            :setDescription("Orders ARTY-2 to fire for effect")
            :setVeafCommand("_ground order, name arty-2, order fire; radius 50-150; shells 40-80; target")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the target coordinates
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty2_stop")
            :setDescription("Orders ARTY-2 to stop listening for orders")
            :setVeafCommand("_ground stop, name arty-2")
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty2_start")
            :setDescription("Orders ARTY-2 to start listening for orders")
            :setVeafCommand("_ground start, name arty-2")
    )

    -- ARTY-3 commands
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty3")
            :setDescription("Spawns ARTY-3")
            :setBatchAliases({"-arty, unitname arty-3", "-ai_set, name arty-3, groupname arty-3"})
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty3_aim")
            :setDescription("Orders ARTY-3 to fire for aim")
            :setVeafCommand("_ground order, name arty-3, order aim;radius 15-30; target")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the target coordinates
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty3_fire")
            :setDescription("Orders ARTY-3 to fire for effect")
            :setVeafCommand("_ground order, name arty-3, order fire; radius 50-150; shells 40-80; target")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the target coordinates
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty3_stop")
            :setDescription("Orders ARTY-3 to stop listening for orders")
            :setVeafCommand("_ground stop, name arty-3")
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arty3_start")
            :setDescription("Orders ARTY-3 to start listening for orders")
            :setVeafCommand("_ground start, name arty-3")
    )

    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-cesar")
            :setDescription("Artillery precision shelling of a zone with a few low-yield HE")
            :setVeafCommand("_spawn bomb")
            :addRandomParameter("shells", 2, 5)
            :addRandomParameter("radius", 15, 30)
            :addRandomParameter("power", 10, 50)
            :setHidden(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-shell")
            :setDescription("Artillery shelling of a small zone with lots of low-yield HE")
            :setVeafCommand("_spawn bomb")
            :addRandomParameter("shells", 2, 5)
            :addRandomParameter("radius", 100, 300)
            :addRandomParameter("power", 10, 50)
            :addRandomParameter("multiplier", 5, 10)
            :setHidden(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-flak")
            :setDescription("Anti-air Artillery shelling of a zone with flak")
            :setVeafCommand("_spawn bomb, alt 6000")
            :addRandomParameter("shells", 10, 15)
            :addRandomParameter("radius", 1000, 1500)
            :addRandomParameter("power", 500, 750)
            :addRandomParameter("altdelta", 800, 1000)
            :addRandomParameter("multiplier", 6, 10)
            :setHidden(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-light")
            :setDescription("Illumination by artillery shelling of a zone")
            :setVeafCommand("_spawn flare, radius 1500")
            :addRandomParameter("shells", 10, 15)
            :setBypassSecurity(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-smoke")
            :setDescription("Spawn a single white smoke")
            :setVeafCommand("_spawn smoke, color white, shells 1, radius 1")
            :setBypassSecurity(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-longsmoke")
            :setDescription("Spawn a single white smoke, renewed every 5 minutes for 30 minutes")
            :setVeafCommand("_spawn smoke, color white, shells 1, radius 1, repeat 5, delay 300")
            :setBypassSecurity(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-signal")
            :setDescription("Spawn a single signal flare")
            :setVeafCommand("_spawn signal, color green, shells 1, radius 1")
            :setBypassSecurity(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-tankerhere")
            :setDescription("move a tanker to a specific location ; must follow with the tanker group name ; can also set speed, alt, hdg and distance")
            :setVeafCommand("_move tanker, teleport, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-tanker")
            :setDescription("alias for '-tankerhere'")
            :setVeafCommand("-tankerhere")
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-tankerlow")
            :setDescription("sets the closest tanker to FL120 at 200 KIAS")
            :setVeafCommand("_move tankermission, alt 12000, speed 250")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-tankerhigh")
            :setDescription("sets the closest tanker to FL220 at 300 KIAS")
            :setVeafCommand("_move tankermission, alt 22000, speed 450")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-tacan")
            :setDescription("create a portable TACAN beacon")
            :setVeafCommand("_spawn tacan, band X, channel 99")
            :setBypassSecurity(true)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-farp")
            :setDescription("create a new FARP")
            :setVeafCommand("_spawn farp, side blue, radius 0, name ")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the FARP name 
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-farpNoMarker")
            :setDescription("create a new invisible FARP, skipping the special vehicles that mark its position")
            :setVeafCommand("_spawn farp, side blue, radius 0, noFarpMarkers, name ")
            :dontEndWithComma() -- !! don't end with a comma, because we'd break setting the FARP name 
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-fob")
            :setDescription("create a new FOB")
            :setVeafCommand("_spawn fob, side blue, radius 0")
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-draw")
            :setDescription("start a drawing on the map, or add a point to an existing drawing ; name is mandatory")
            :setVeafCommand("_drawing add, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-arrow")
            :setDescription("start drawing an arrow on the map, or add a point to an existing arrow ; name is mandatory")
            :setVeafCommand("_drawing add, arrow, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-square")
            :setDescription("add a square to the map; name is mandatory")
            :setVeafCommand("_drawing square, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-circle")
            :setDescription("add a circle to the map; name is mandatory")
            :setVeafCommand("_drawing circle, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-erasedrawing")
            :setDescription("erase a drawing from the map ; name is mandatory")
            :setVeafCommand("_drawing erase, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-cap")
            :setDescription("Dynamic combat air patrol")
            :setVeafCommand("_spawn cap, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-flag")
            :setDescription("Mission Master : get flag value")
            :setVeafCommand("_mm getflag, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-flagon")
            :setDescription("Mission Master : set flag value to ON")
            :setVeafCommand("_mm flagon, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-flagoff")
            :setDescription("Mission Master : set flag value to OFF")
            :setVeafCommand("_mm flagoff, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAlias:new()
            :setName("-run")
            :setDescription("Mission Master : run runnable")
            :setVeafCommand("_mm run, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAliasForCombatMission:new()
            :setName("-airstart")
            :setDescription("Run a combat mission")
            :setVeafCommand("start, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAliasForCombatMission:new()
            :setName("-airstop")
            :setDescription("Stop a combat mission")
            :setVeafCommand("stop, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAliasForCombatZone:new()
            :setName("-zonestart")
            :setDescription("Activate a combat zone")
            :setVeafCommand("start, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
    veafShortcuts.AddAlias(
        VeafAliasForCombatZone:new()
            :setName("-zonestop")
            :setDescription("Desactivate a combat zone")
            :setVeafCommand("stop, name")
            :dontEndWithComma()
            :setBypassSecurity(false)
    )
end

function veafShortcuts.dumpAliasesList(export_path)

    local jsonify = function(key, value)
-- LOGGING DISABLED WHEN COMPILING(string.format("jsonify(%s)", veaf.p(value)))
        if veaf.json then
            return veaf.json.stringify(veafShortcuts.GetAlias(value))
        else
            return ""
        end
    end

    -- sort the aliases alphabetically
    local sortedAliases = {}
    for _, alias in pairs(veafShortcuts.aliases) do
        table.insert(sortedAliases, alias:getName())
    end
    table.sort(sortedAliases)
-- LOGGING DISABLED WHEN COMPILING(string.format("sortedAliases=%s", veaf.p(sortedAliases)))

    local _filename = "AliasesList.json"
    if veaf.config.MISSION_NAME then
        _filename = "AliasesList_" .. veaf.config.MISSION_NAME .. ".json"
    end
    if not(veaf.DO_NOT_EXPORT_JSON_FILES) then
        veaf.exportAsJson(sortedAliases, "aliases", jsonify, _filename, export_path or veaf.config.MISSION_EXPORT_PATH)
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- remote interface
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- execute command from the remote interface
function veafShortcuts.executeCommandFromRemote(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafShortcuts.executeCommandFromRemote()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("parameters= %s", veaf.p(parameters)))
    local _pilot, _pilotName, _unitName, _command = unpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilot= %s", veaf.p(_pilot)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilotName= %s", veaf.p(_pilotName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_unitName= %s", veaf.p(_unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_command= %s", veaf.p(_command)))
    if not _pilot or not _command then 
        return false
    end

    if _command then
        local _lat, _lon, _alias = nil, nil, nil
        local _coa = coalition.side.BLUE
        local _unit = Unit.getByName(_unitName)
        if _unit then 
            _coa = _unit:getCoalition()
-- LOGGING DISABLED WHEN COMPILING("_coa=s",veaf.p(_coa))
        end
        -- choose by default the coalition opposing the player who triggered the event
        local invertedCoalition = 1
        if _coa == 1 then
            invertedCoalition = 2
        end
        if _command:sub(1,1) == veafShortcuts.AliasStarter or _command:sub(2,2) == veafShortcuts.AliasStarter then
            -- there is only the command
            _alias = _command
-- LOGGING DISABLED WHEN COMPILING(string.format("_alias=%s",veaf.p(_alias)))
        else
            -- parse the command
            local _coords, __alias = _command:match(veafShortcuts.RemoteCommandParser)
            _alias = __alias
-- LOGGING DISABLED WHEN COMPILING(string.format("_coords=%s",veaf.p(_coords)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_alias=%s",veaf.p(_alias)))
            if _coords then
                _lat, _lon = veaf.computeLLFromString(_coords)
-- LOGGING DISABLED WHEN COMPILING(string.format("_lat=%s",veaf.p(_lat)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_lon=%s",veaf.p(_lon)))
            end
        end
        if _alias then
            if _lat and _lon then 
                local _pos = coord.LLtoLO(_lat, _lon)
-- LOGGING DISABLED WHEN COMPILING(string.format("_pos=%s",veaf.p(_pos)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_coa=%s",veaf.p(_coa)))
                veaf.loggers.get(veafShortcuts.Id):info(string.format("[%s] is running an alias at position [%s] for coalition [%s] : [%s]",veaf.p(_pilot.name), veaf.p(_pos), veaf.p(_coa), veaf.p(_alias)))
                veafShortcuts.executeCommand(_pos, _alias, invertedCoalition, _pilot.name)
                return true
            else
                veaf.loggers.get(veafShortcuts.Id):info(string.format("[%s] is running an alias with no specific position for coalition [%s] : [%s]",veaf.p(_pilot.name), veaf.p(_coa), veaf.p(_alias)))
                veafShortcuts.executeCommand(nil, _alias, invertedCoalition, _pilot.name)
            end
        end
    end
    return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafShortcuts.initialize()
    veaf.loggers.get(veafShortcuts.Id):info("Initializing module")
    veafShortcuts.buildDefaultList()
    veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafShortcuts.onEventMarkChange)
    veafShortcuts.dumpAliasesList()
end

veaf.loggers.get(veafShortcuts.Id):info(string.format("Loading version %s", veafShortcuts.Version))


------------------ END script veafShortcuts.lua  ------------------


------------------ START script veafAirbases.lua  ------------------

------------------------------------------------------------------
-- VEAF airbases information
-- By Flogas (2024)
--
-- Features:
-- ---------
-- * Extraction and normalization of airbase an runway data from DCS APIs
------------------------------------------------------------------
veafAirbases = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global module settings
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafAirbases.Id = "AIRBASES"

--- Version.
veafAirbases.Version = "1.1.0"

-- trace level, specific to this module
--veafAirbases.LogLevel = "trace"
veaf.loggers.new(veafAirbases.Id, veafAirbases.LogLevel)

veafAirbases.Airbases = nil

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Local constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------
local _manualRunwayNumberCorrections =
{
    -- Syria --
    ["Beirut-Rafic Hariri"] = { [34] = 3, [168] = 16 }, -- Correct RWYs: 17/35, 03/21, 16/34 ; API gives: RWY17 for 178° (ok), RWY35 for 034°, RWY03 for 168°
    ["Khalkhalah"] = { [151] = 15, [76] = 7 }, -- Correct RWYs: 15/33, 07/25 ; API gives: RWY03 for 151°, RWY03 for 076° ; note: runways have no painted numbers, 15/33 in DCS F10 airport window
    ["Minakh"] = { [42] = -1}, -- Correct RWY: 10/28 ; API gives: RWY10 for 101° (ok), RWY28 for 042° ; note: runway oriented to 042° is closed
    ["Ramat David"] = { [110] = 11, [88] = 9 }, -- Correct RWYs: 15/33, 11/29, 09/27 ; API gives: RWY15 for 146° (ok), RWY33 for 110°, RWY11 for 088°
    ["Nicosia"] = { [91] = 9 }, -- Correct RWYs: 14/32, 09/27 ; API gives: RWY32 for 328° (ok), RWY14 for 091°
    ["H3"] = { [64] = 6 }, -- Correct RWYs: 11/29, 06/24 ; API gives: RWY11 for 111° (ok), RWY29 for 064°
    ["Muwaffaq Salti"] = { [312] = 31 }, -- Correct RWYs: 08/26, 13/31 ; API gives: RWY26 for 261° (ok), RWY08 for 312°
    ["Tel Nof"] = { [184] = 18, [332] = 33 }, -- Correct RWYs: 15/33(2), 18/36 ; API gives: RWY33 for 332° (ok), RWY15 for 184°, RWY18 for 332°
    ["Ben Gurion"] = { [210] = 21, [262] = 26 }, -- Correct RWYs: 12/30, 03/21, 08/26 ; API gives: RWY12 for 123° (ok), RWY30 for 210°, RWY21 for 262°
    ["Hatzor"] = { [113] = 11 }, -- Correct RWYs: 05/23, 11/29(2) ; API gives: RWY23 for 236° (ok), RWY05 for 113°, RWY11 for 293° (ok)
    -- Nevada --
    ["Creech"] = { [145] = 13 }, -- Correct RWYs: 08/26, 13/31 ; API gives: RWY08 for 091° (ok), RWY26 for 145°
    ["Boulder City"] = { [278] = 27 }, -- Correct RWYs: 15/33, 09/27 ; API gives: RWY33 for 344° (ok), RWY15 for 278°
    ["North Las Vegas"] = { [313] = 30 }, -- Correct RWYs: 07/25, 12/30(2) ; API gives: RWY25 for 267° (ok), RWY07 for 313°, RWY30 for 133° (ok)
    ["Tonopah"] = { [345] = 33 }, -- Correct RWYs: 11/29, 15/33 ; API gives: RWY29 for 305° (ok), RWY11 for 345°
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
---  Static methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafAirbases.initialize(bReset)
    bReset = bReset or false
    
    if (bReset) then
        veafAirbases.Airbases = nil
    end

    if (veafAirbases.Airbases) then
        return
    end

    veafAirbases.Airbases = {}

    local dcsAirBases = world.getAirbases()
    for i = 1, #dcsAirBases do
        local dcsAirbase = dcsAirBases[i]
--[[
        local s = string.format("%s  %s  %s  %s",
        veaf.p(dcsAirbase:getCallsign()), veaf.p(dcsAirbase:getUnit()), veaf.p(dcsAirbase:getID()), veaf.p(dcsAirbase:getCategoryEx())
        )
-- LOGGING DISABLED WHEN COMPILING(s)
-- LOGGING DISABLED WHEN COMPILING(veaf.p(dcsAirbase:getDesc()))
]]
        local veafAirbase = veafAirbase:create(dcsAirbase)
        if (veafAirbase) then
            table.insert(veafAirbases.Airbases, veafAirbase)
        end
    end
-- LOGGING DISABLED WHEN COMPILING("Airbases and runways initialized for theater " .. env.mission.theatre)
end

function veafAirbases.getAirbaseByName(sAirbaseName)
    veafAirbases.initialize()
    
    for _, veafAirbase in pairs(veafAirbases.Airbases) do
        if (veafAirbase.Name == sAirbaseName) then
            return veafAirbase
        end
    end

    return nil
end

function veafAirbases.getAirbaseFromDcsAirbase(dcsAirbase)
    if (dcsAirbase == nil) then
        return nil
    end

    return veafAirbases.getAirbaseByName(dcsAirbase:getName())
end

function veafAirbases.getNearestAirbaseList(dcsUnit, iCount)
    veafAirbases.initialize()
    
    iCount = iCount or 1
    local vec3Unit = dcsUnit:getPoint()
    local iMinDistance = nil
    local nearestList = {}

    local function Sort(a, b)
        if (a == nil and b == nil) then
            return false
        elseif (a == nil) then
            return false
        elseif (b == nil) then
            return true
        else
            return a[2] < b[2]
        end
    end

    for _, veafAirbase in pairs(veafAirbases.Airbases) do
        local vec3Airbase = veafAirbase.DcsAirbase:getPoint()
        local iDistance = mist.utils.get2DDist(vec3Unit, vec3Airbase)
        local bAdded = false

        -- first fill all the nil positions
        for i = 1, iCount, 1 do
            if (nearestList[i] == nil) then
                nearestList[i] = { veafAirbase, iDistance }
                bAdded = true
                break
            end
        end

        if (not bAdded) then
            -- then, replace the farthest one if the current one is closer
            for i = iCount, 1, -1 do
                if (iDistance < nearestList[i][2]) then
                    nearestList[i] = { veafAirbase, iDistance}
                    bAdded = true
                    break
                end
            end
        end

        if (bAdded) then
            table.sort(nearestList, Sort)
        end
        
    end

    return nearestList
end

function veafAirbases.getNearestAirbase(dcsUnit)
    local nearestList = veafAirbases.getNearestAirbaseList(dcsUnit, 1)
    if (nearestList and #nearestList >= 1) then
        local veafAirbase = nearestList[1][1]
-- LOGGING DISABLED WHEN COMPILING(string.format("Nearest airbase for [ %s ]: [ %s ] at %dm", dcsUnit:getName(), veafAirbase:toString(), nearestList[1][2]))
        return nearestList[1][1]
    else
-- LOGGING DISABLED WHEN COMPILING(string.format("No near airbase for [ %s ]", dcsUnit:getName()))
        return nil
    end
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  Airbase descriptor class
---  Holds normalized information describing a DCS airbase
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
veafAirbase = {}
veafAirbase.__index = veafAirbase

---------------------------------------------------------------------------------------------------
---  CTOR
function veafAirbase:create(dcsAirbase)
    if (dcsAirbase == nil) then
        return nil
    end
    
    local sDisplayName = dcsAirbase:getName()
    local iCategory = dcsAirbase:getCategoryEx()
    local sTypeName = dcsAirbase:getTypeName()
    if (iCategory == Airbase.Category.SHIP and string.find(sTypeName, "FARP")) then
        iCategory = Airbase.Category.HELIPAD -- remediation for "FARP_SINGLE_01" or "VAP FARP" which are miscategorized
    end

    if (iCategory == Airbase.Category.SHIP) then
        local sShipType = sTypeName
        local dcsUnit = dcsAirbase:getUnit()
        if (dcsUnit) then
            local dcsUnitDesc = dcsUnit:getDesc()
            local sDcsUnitType = dcsUnit:getTypeName()

            if (dcsUnitDesc and not veaf.isNullOrEmpty(dcsUnitDesc.displayName)) then
                sShipType = dcsUnitDesc.displayName
            elseif (not veaf.isNullOrEmpty(sDcsUnitType)) then
                sShipType = sDcsUnitType
            end
        end    
        
        sDisplayName = string.format("%s(%s)", sDisplayName, sShipType)
        --[[       
        if (dcsUnit) then
            local dcsUnitName = dcsUnit:getName()
            local dcsUnitType = dcsUnit:getTypeName()
-- LOGGING DISABLED WHEN COMPILING(string.format(">>>>>>> unit - %s - type=%s", dcsUnitName, dcsUnitType))
-- LOGGING DISABLED WHEN COMPILING(veaf.p(dcsUnit:getDesc()))
            local desc = dcsUnit:getDesc()
-- LOGGING DISABLED WHEN COMPILING(desc.Kmax)
-- LOGGING DISABLED WHEN COMPILING(desc.displayName)
        end
        ]]        
    elseif (iCategory == Airbase.Category.HELIPAD) then
        sDisplayName = sDisplayName .. "(FARP)"
    end
    
-- LOGGING DISABLED WHEN COMPILING(string.format(">> Airbase - %s created with display name  %s", dcsAirbase:getName(), sDisplayName))


    local veafRunways = {}
    if (iCategory == Airbase.Category.AIRDROME) then
        local dcsRunways = dcsAirbase:getRunways()
        for iRunwayReportOrder, dcsRunway in pairs(dcsRunways) do
            local veafRunway = veafAirbaseRunway:create(dcsAirbase, dcsRunway, iRunwayReportOrder)
            if (veafRunway) then
                table.insert(veafRunways, veafRunway)
            end
        end
    end

    local this =
    {
        Name = dcsAirbase:getName(),
        DisplayName = sDisplayName,
        Category = iCategory,
        DcsAirbase = dcsAirbase,
        Runways = veafRunways
    }

    setmetatable(this, veafAirbase)

    return this
end

---------------------------------------------------------------------------------------------------
---  Methods
function veafAirbase:getRunwayInService(iWindDirectionTrue)
    local function _getHeadwind(iWindDirection, nRunwayHeading)
        local nAngle = math.abs(iWindDirection - nRunwayHeading)
    
        if (nAngle > 180) then
            nAngle = 360 - nAngle
        end
    
        return math.cos(math.rad(nAngle))
    end

    local bestRunwayEnd = nil
    local nBestHeadwind = -math.huge -- Start with lowest possible value
    
    for i, veafRunway in ipairs(self.Runways) do
        for _, veafRunwayEnd in ipairs(veafRunway) do
            local nHeadwind = _getHeadwind(iWindDirectionTrue, veafRunwayEnd.Heading)
            
            if nHeadwind > nBestHeadwind then
                nBestHeadwind = nHeadwind
                bestRunwayEnd = veafRunwayEnd
            end
        end
    end

    local sLog = string.format("Runway in service for [ %s ] with wind from [ %03dT ] -->", self:toString(), iWindDirectionTrue)
    if (bestRunwayEnd) then
        sLog = sLog .. string.format(" [ %02d ]", bestRunwayEnd.Number)
    else
        sLog = sLog .. " none identified"
    end

-- LOGGING DISABLED WHEN COMPILING(sLog)
    return bestRunwayEnd
end

function veafAirbase:getRunwayInServiceString(iWindDirectionTrue)
    local veafRunwayEnd = self:getRunwayInService(iWindDirectionTrue)
    if (veafRunwayEnd) then
        return string.format("%02d", veafRunwayEnd.Number)
    else
        return nil
    end
end


function veafAirbase:toString()
    local s = self.Name
    
    for _, veafRunway in pairs(self.Runways) do
        s = s .. string.format(" | %s", veafRunway:toString())
    end

    return s
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  Runway descriptor class
---  Holds normalized information describing a DCS airbase runway
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
veafAirbaseRunway = {}
veafAirbaseRunway.__index = veafAirbaseRunway

---------------------------------------------------------------------------------------------------
---  CTOR
function veafAirbaseRunway:create(dcsAirbase, dcsRunway, iReportOrder)
    if (dcsAirbase == nil or dcsRunway == nil) then
        return nil
    end

    local function _normalizeHeading(nHeading)
        nHeading = nHeading % 360
        if (nHeading == 0) then nHeading = 360 end
        return nHeading
    end

    local function _normalizeNumber(iNumber)
        iNumber = iNumber % 36
        if (iNumber == 0) then iNumber = 36 end
        return iNumber
    end

    local function _numberFromHeading(nHeading)
        return mist.utils.round(nHeading / 10)
    end

    local function _numbersOffest(iOffset1, iOffest2)
        -- Calculate the absolute difference between angles
        local iOffset = math.abs(iOffset1 - iOffest2)
        local iOffsetOpposite = math.abs(36 - iOffset)
        return math.min(iOffset, iOffsetOpposite)
    end

    local sAirbaseName = dcsAirbase:getName()
    local iDcsNumber = tonumber(dcsRunway.Name)
    local nDcsHeading = _normalizeHeading(math.deg(-dcsRunway.course))

    -- correction for special misreported runways
    -- first reported runway is assumed always ok
    -- and we need to assume that since sometimes it has the same reported heading as another incorrect one (Tel Nof in Syria for ex)
    if (iReportOrder and iReportOrder > 1) then
        local manualAirbaseCorrections = _manualRunwayNumberCorrections[sAirbaseName]
        if (manualAirbaseCorrections) then
            local iCorrectedNumber = manualAirbaseCorrections[math.floor(nDcsHeading)]
            if (iCorrectedNumber and iCorrectedNumber > 0) then
                iDcsNumber = iCorrectedNumber
            elseif (iCorrectedNumber and iCorrectedNumber <= 0) then
                return nil -- for closed runways
            end
        end
    end

    --[[
    if (sAirbaseName == "Pahute Mesa") then
-- LOGGING DISABLED WHEN COMPILING("=======")
-- LOGGING DISABLED WHEN COMPILING(string.format("%d, %.2f", iDcsNumber, nDcsHeading))
-- LOGGING DISABLED WHEN COMPILING(veaf.p(dcsRunway))
    end
    ]]

    if (iDcsNumber == nil) then
        iDcsNumber = _numberFromHeading(nDcsHeading - veaf.getMagneticDeclination())
    end

    -- Calculate standard runway number (first two digits of heading)
    local iNumberFromHeading = _numberFromHeading(nDcsHeading)

    -- Calculate the opposite heading
    local nOppositeHeading = _normalizeHeading(nDcsHeading + 180)
    local iOppositeNumberFromHeading =  _numberFromHeading(nOppositeHeading)
    
    local iPrimaryNumber = iDcsNumber
    local iSecondaryNumber = _normalizeNumber(iDcsNumber + 18)
    local nPrimaryHeading, nSecondaryHeading

    local iFlipThreshold = 4
    if (_numbersOffest(iDcsNumber, iOppositeNumberFromHeading) <= iFlipThreshold or _numbersOffest(iDcsNumber, iNumberFromHeading) > iFlipThreshold) then
        -- If DCS number if close to computed opposite heading, or far from the DCS heading, flip the heading
        nPrimaryHeading = nOppositeHeading
        nSecondaryHeading = nDcsHeading
    else
        nPrimaryHeading = nDcsHeading
        nSecondaryHeading = nOppositeHeading
    end

    -- order the runway in ascending numbers
    local iNumber1 = iPrimaryNumber
    local nHeading1 = nPrimaryHeading
    local iNumber2 = iSecondaryNumber
    local nHeading2 = nSecondaryHeading

    if (iPrimaryNumber > iSecondaryNumber) then
        iNumber1 = iSecondaryNumber
        nHeading1 = nSecondaryHeading
        iNumber2 = iPrimaryNumber
        nHeading2 = nPrimaryHeading        
    end

    local this =
    {
        [1] = { Number = iNumber1, Heading = nHeading1 },
        [2] = { Number = iNumber2, Heading = nHeading2 },
    }

    setmetatable(this, veafAirbaseRunway)

    return this
end

---------------------------------------------------------------------------------------------------
---  Methods
function veafAirbaseRunway:toString()
    return string.format("RWY %02d(%.2fT) / %02d(%.2fT)", self[1].Number, self[1].Heading, self[2].Number, self[2].Heading)
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  MODULE TESTS
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
--[[
veafAirbases.initialize()
-- LOGGING DISABLED WHEN COMPILING("Airbases and runways initialized for theater " .. env.mission.theatre)
for _, veafAirbase in pairs(veafAirbases.Airbases) do
-- LOGGING DISABLED WHEN COMPILING(veafAirbase:toString())
end
]]

------------------ END script veafAirbases.lua  ------------------


------------------ START script veafAirWaves.lua  ------------------

------------------------------------------------------------------
-- VEAF Air Waves for DCS World
-- By Zip (2023)
--
-- Features:
-- ---------
-- * Define zones that are defended by waves of AI flights
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafAirWaves = {}

--- Identifier. All output in the log will start with this.
veafAirWaves.Id = "AIRWAVES - "

--- Version.
veafAirWaves.Version = "1.7.7"

-- trace level, specific to this module
--veafAirWaves.LogLevel = "trace"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veaf.loggers.new(veafAirWaves.Id, veafAirWaves.LogLevel)

veafAirWaves.zones = {}

veafAirWaves.WATCHDOG_DELAY = 1

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- AirWave class methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

AirWaveZone = {}
function AirWaveZone.init(object)
  -- technical name (AirWave instance name)
  object.name = nil
  -- description for the messages
  object.description = nil
  -- trigger zone name (if set, we'll use a DCS trigger zone)
  object.triggerZoneName = nil
  -- center (point in the center of the circle, when not using a DCS trigger zone)
  object.zoneCenter = nil
  -- radius (size of the circle, when not using a zone) - in meters
  object.zoneRadius = nil
  -- draw the zone on screen
  object.drawZone = false
  -- default position for respawns (im meters, lat/lon, relative to the zone center)
  object.respawnDefaultOffset = {latDelta=0, lonDelta=0}
  -- radius of the waves groups spawn
  object.respawnRadius = 250
  -- coalitions of the players (only human units from these coalitions will be monitored)
  object.playerCoalitions = {}
  -- player units (if they die, reset the zone)
  object.playerUnitsNames = {}
  -- aircraft groups forming the waves
  object.waves = {}
  -- groups that have been spawned (the current wave)
  object.spawnedGroupsNames = {}
  -- silent means no message is emitted
  object.silent = false
  -- message when the zone is activated
  object.messageStart = veafAirWaves.DEFAULT_MESSAGE_START
  -- event when the zone is activated
  object.onStart = nil
  -- message when the zone is waiting for more players
  object.messageWaitForHumans = veafAirWaves.DEFAULT_MESSAGE_WAIT_FOR_HUMANS
  -- event when the zone is waiting for more players
  object.onWaitForHumans = nil
  -- message when a wave will be triggered
  object.messageWaitToDeploy = veafAirWaves.DEFAULT_MESSAGE_WAIT_TO_DEPLOY
  -- event when a wave will be triggered
  object.onWaitToDeploy = nil
  -- message when a wave is triggered
  object.messageDeploy = veafAirWaves.DEFAULT_MESSAGE_DEPLOY
  -- message to each players in the zone when a wave is triggered
  object.messageDeployPlayers = veafAirWaves.DEFAULT_MESSAGE_DEPLOY_PLAYERS
  -- event when a wave is triggered
  object.onDeploy = nil
  -- message when a player is outside of zone
  object.messageOutsideOfZone = veafAirWaves.DEFAULT_MESSAGE_OUTSIDE_OF_ZONE_PLAYERS
  -- event when a player is outside of zone
  object.onOutsideOfZone = nil
  -- message when a wave is destroyed
  object.messageDestroyed = veafAirWaves.DEFAULT_MESSAGE_DESTROYED
  -- event when a wave is destroyed
  object.onDestroyed = nil
  -- message when all waves are finished
  object.messageWon = veafAirWaves.DEFAULT_MESSAGE_WON
  -- event when all waves are finished
  object.onWon = nil
  -- message when the zone is lost
  object.messageLost = veafAirWaves.DEFAULT_MESSAGE_LOST
  -- event when all players are dead
  object.onLost = nil
  -- message when the zone is deactivated
  object.messageStop = veafAirWaves.DEFAULT_MESSAGE_STOP
  -- event when the zone is deactivated
  object.onStop = nil
  -- default delay in seconds between waves of enemy planes
  object.delayBetweenWaves = 0
  -- the delay after this wave, and before the next one (either set in the wave definition, or it's the default delayBetweenWaves)
  object.delayBeforeNextWave = nil
  -- the time when the next wave is supposed to spawn (used to know when to actually spawn when in the STATUS_WAITING_FOR_NEXTWAVE state)
  object.timeOfNextWave = nil
  -- delay in seconds between the first human in zone and the actual activation of the zone
  object.delayBeforeActivation = 0
  -- the time when the zones is supposed to be activated (used to know when to actually activate when in the STATUS_WAITING_FOR_MORE_HUMANS state)
  object.timeOfActivation = nil
  -- if true, the zone will reset when player dies
  object.resetWhenDying = true
  -- human units that are being watched
  object.playerHumanUnits = nil
  -- names of the human units that are being watched
  object.playerHumanUnitsNames = nil
  -- IA units that are being watched
  object.unitsInZone = {}
  -- players in the zone will only be detected below this altitude (in feet)
  object.minimumAltitude = -999999
  -- players in the zone will only be detected above this altitude (in feet)
  object.maximumAltitude = 999999
  -- players staying out of the zone for more that this number of seconds will be destroyed
  object.maxSecondsOutsideOfZonePlayers = veafAirWaves.MAX_SECONDS_OUTSIDE_OF_ZONE_PLAYERS
  -- IA staying out of the zone for more that this number of seconds will be destroyed
  object.maxSecondsOutsideOfZoneIA = veafAirWaves.MAX_SECONDS_OUTSIDE_OF_ZONE_IA
  -- the function that decides if a wave is dead or not (as a set of groups and units)
  object.isEnemyWaveDeadCallback = AirWaveZone.isEnemyWaveDead
  -- the function that decides if IA ennemy groups are dead (individually)
  object.isEnemyGroupDeadCallback = AirWaveZone.isEnemyGroupDead
  -- the minimum percentage of life that an AI unit is supposed to have to be considered alive
  object.minimumLifeForAiInPercent = veafAirWaves.MINIMUM_LIFE_FOR_AI_IN_PERCENT
  -- the function that handles crippled enemy units
  object.handleCrippledEnemyUnitCallback = AirWaveZone.handleCrippledEnemyUnit
  -- current wave number
  object.currentWaveIndex = 0
  -- the drawing object that has been used to draw the zone
  object.zoneDrawing = nil
  -- the scheduled state of the :check() function
  object.checkFunctionSchedule = nil
  -- the time humans exited the zone
  object.timestampsOutOfZone = {}

end

function veafAirWaves.statusToString(status)
  if status == veafAirWaves.STATUS_READY then return "STATUS_READY" end
  if status == veafAirWaves.STATUS_WAITING_FOR_MORE_HUMANS then return "STATUS_WAITING_FOR_MORE_HUMANS" end
  if status == veafAirWaves.STATUS_ACTIVE then return "STATUS_ACTIVE" end
  if status == veafAirWaves.STATUS_WAITING_FOR_NEXTWAVE then return "STATUS_WAITING_FOR_NEXTWAVE" end
  if status == veafAirWaves.STATUS_NEXTWAVE then return "STATUS_NEXTWAVE" end
  if status == veafAirWaves.STATUS_OVER then return "STATUS_OVER" end
  return ""
end
veafAirWaves.STATUS_READY = 1
veafAirWaves.STATUS_WAITING_FOR_MORE_HUMANS = 1.5
veafAirWaves.STATUS_ACTIVE = 2
veafAirWaves.STATUS_WAITING_FOR_NEXTWAVE = 2.5
veafAirWaves.STATUS_NEXTWAVE = 3
veafAirWaves.STATUS_OVER = 4

veafAirWaves.MINIMUM_LIFE_FOR_AI_IN_PERCENT = 0

veafAirWaves.MAX_SECONDS_OUTSIDE_OF_ZONE_PLAYERS = nil -- no outside of zone mechanism by default for players
veafAirWaves.MAX_SECONDS_OUTSIDE_OF_ZONE_IA = 30
veafAirWaves.DEFAULT_MESSAGE_START = "%s - online"
veafAirWaves.DEFAULT_MESSAGE_WAIT_FOR_HUMANS = "%s - waiting %s seconds for more players"
veafAirWaves.DEFAULT_MESSAGE_WAIT_TO_DEPLOY = "%s - waiting %s seconds before next wave"
veafAirWaves.DEFAULT_MESSAGE_DEPLOY = "%s - deploying wave %s"
veafAirWaves.DEFAULT_MESSAGE_DEPLOY_PLAYERS = "Wave %s deploying, %s"
veafAirWaves.DEFAULT_MESSAGE_OUTSIDE_OF_ZONE_PLAYERS = "%s - you've been outside of the zone for %s seconds; go back inside, or you'll be destroyed after %s seconds."
veafAirWaves.DEFAULT_MESSAGE_DESTROYED = "%s - wave %s has been destroyed"
veafAirWaves.DEFAULT_MESSAGE_WON = "%s - won (no more waves)"
veafAirWaves.DEFAULT_MESSAGE_LOST = "%s - lost (no more players)"
veafAirWaves.DEFAULT_MESSAGE_STOP = "%s - offline"

function AirWaveZone:new(objectToCopy)
-- LOGGING DISABLED WHEN COMPILING("AirWave:new()")
  local objectToCreate = objectToCopy or {} -- create object if user does not provide one
  setmetatable(objectToCreate, self)
  self.__index = self

  -- init the new object
  AirWaveZone.init(objectToCreate)

  return objectToCreate
end

function AirWaveZone:setName(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[]:setName(%s)", veaf.p(value))
  self.name = value
  return veafAirWaves.add(self) -- add the zone to the list as soon as a name is available to index it
end

function AirWaveZone:getName()
  return self.name or self.description
end

function AirWaveZone:setTriggerZone(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setTriggerZone(%s)", veaf.p(self.name), veaf.p(value))
  self.triggerZoneName = value
  local triggerZone = veaf.getTriggerZone(value)
  if triggerZone then
    self:setZoneCenter({ x=triggerZone.x, y=triggerZone.y})
    self:setZoneRadius(triggerZone.radius)
  else
    veaf.loggers.get(veafAirWaves.Id):error("AirWaveZone[%s]:setTriggerZone(): trigger zone [%s] does not exist", veaf.p(self.name), veaf.p(value))
  end
  return self
end


function AirWaveZone:setZoneCenter(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setZoneCenter(%s)", veaf.p(self.name), veaf.p(value))
  self.zoneCenter = value
  return self
end

function AirWaveZone:setZoneCenterFromCoordinates(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setZoneCenterFromCoordinates(%s)", veaf.p(self.name), veaf.p(value))
  local _lat, _lon = veaf.computeLLFromString(value)
  local vec3 = coord.LLtoLO(_lat, _lon)
  return self:setZoneCenter(vec3)
end

function AirWaveZone:setZoneRadius(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setZoneRadius(%s)", veaf.p(self.name), veaf.p(value))
  self.zoneRadius = value
  return self
end

function AirWaveZone:setDrawZone(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setDrawZone(%s)", veaf.p(self.name), veaf.p(value))
  self.drawZone = value or false
  return self
end

function AirWaveZone:setDescription(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setDescription(%s)", veaf.p(self.name), veaf.p(value))
  self.description = value
  return veafAirWaves.add(self) -- add the zone to the list as soon as a description is available to index it
end

function AirWaveZone:getDescription()
  return self.description or self.name
end

---adds a wave of enemy planes
---parameters are very flexible: they can be:
--- a table containing the following fields:
---     - groups a list of groups or VEAF commands; VEAF commands can be prefixed with [lat, lon], specifying the location of their spawn relative to the center of the zone; default value is set with "setRespawnDefaultOffset"
---     - number how many of these groups will actually be spawned (can be multiple times the same group!); it can be a "randomizable number", e.g., "2-6" for "between 2 and 6"
---     - bias shifts the random generator to the right of the list; it can be a "randomizable number" too
---     - delay the delay between this wave and the next one - if negative, then the next wave is spawned instantaneously (no waiting for this wave to be completed); it can be a "randomizable number" too
--- or a list of strings (the groups or VEAF commands)
--- or almost anything in between; we'll take a string as if it were a table containing one string, anywhere
--- examples:
---   :addWave("group1")
---   :addWave("group1", "group2")
---   :addWave({"group1", "group2"})
---   :addWave({ groups={"group1", "group2"}, number = 2})
---   :addWave({ groups="group1", number = 2})
---returns self
function AirWaveZone:addWave(...)
-- LOGGING DISABLED WHEN COMPILING(string.format("AirWaveZone[%s]:addWave() : %s", veaf.p(self.name), veaf.p(arg)))
---@diagnostic disable-next-line: undefined-field this is a field defined in the vararg api
  local nArgs = arg.n or 0
  if arg and nArgs > 0 then
    local groups = {}
    local number = 1
    local bias = 0
    local delay = nil
    for i = 1, nArgs, 1 do
      local parameter = arg[i]
      if type(parameter) == "string" then
        table.insert(groups, parameter)
      elseif type(parameter) == "table" then
        if parameter.groups then
          -- this is a parameters table, let's use it
          if type(parameter.groups) == "string" then
            -- we need a table
            groups = { parameter.groups }
          else
            groups = parameter.groups
          end
          number = parameter.number
          bias = parameter.bias
          delay = parameter.delay
          break
        else
          for j = 1, #parameter, 1 do
            local s = parameter[j]
            if type(s) == "string" then
              table.insert(groups, parameter)
            end
          end
          break
        end
      end
    end
    if not self.waves then
      self.waves = {}
    end
    table.insert(self.waves, {groups=groups, number=number or 1, bias=bias or 0, delay=delay})
  end
  return self
end

---reset the waves table to zero; useful when deep copying a zone to reset the waves and set something different
---@return table self
function AirWaveZone:resetWaves()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:resetWaves()", veaf.p(self.name))
  self.waves = {}
  return self
end

function AirWaveZone:setMessageStart(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageStart()", veaf.p(self.name))
  self.messageStart = value
  return self
end

---Set the onStart callback
---@param value function takes 2 parameters: the zone name (string), the monitored player units (table)
---@return table self
function AirWaveZone:setOnStart(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnStart()", veaf.p(self.name))
  self.onStart = value
  return self
end

function AirWaveZone:setMessageWaitForHumans(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageWaitForHumans()", veaf.p(self.name))
  self.messageWaitForHumans = value
  return self
end

---Set the onWaitForHumans callback
---@param value function takes 3 parameters: the zone name (string), the wave index (int), the monitored player units (table)
---@return table self
function AirWaveZone:setOnWaitForHumans(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnWaitForHumans()", veaf.p(self.name))
  self.onWaitForHumans = value
  return self
end

function AirWaveZone:setMessageWaitToDeploy(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageWaitToDeploy()", veaf.p(self.name))
  self.messageWaitToDeploy = value
  return self
end

---Set the onWaitToDeploy callback
---@param value function takes 3 parameters: the zone name (string), the wave index (int), the monitored player units (table)
---@return table self
function AirWaveZone:setOnWaitToDeploy(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnWaitToDeploy()", veaf.p(self.name))
  self.onWaitToDeploy = value
  return self
end

function AirWaveZone:setMessageDeploy(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageDeploy()", veaf.p(self.name))
  self.messageDeploy = value
  return self
end

function AirWaveZone:setMessageDeployPlayers(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageDeployPlayers()", veaf.p(self.name))
  self.messageDeployPlayers = value
  return self
end

---Set the onDeploy callback
---@param value function takes 3 parameters: the zone name (string), the wave index (int), the monitored player units (table)
---@return table self
function AirWaveZone:setOnDeploy(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnDeploy()", veaf.p(self.name))
  self.onDeploy = value
  return self
end

function AirWaveZone:setMessageDestroyed(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageDestroyed()", veaf.p(self.name))
  self.messageDestroyed = value
  return self
end

---Set the onDestroyed callback
---@param value function takes 3 parameters: the zone name (string), the wave index (int), the monitored player units (table)
---@return table self
function AirWaveZone:setOnDestroyed(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnDestroyed()", veaf.p(self.name))
  self.onDestroyed = value
  return self
end

function AirWaveZone:setMessageOutsideOfZone(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageOutsideOfZone()", veaf.p(self.name))
  self.messageOutsideOfZone = value
  return self
end

---Set the onOutsideOfZone callback
---@param value function takes 3 parameters: the zone name (string), the wave index (int), the monitored player units (table)
---@return table self
function AirWaveZone:setOnOutsideOfZone(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnOutsideOfZone()", veaf.p(self.name))
  self.onOutsideOfZone = value
  return self
end

function AirWaveZone:setMessageWon(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageWon()", veaf.p(self.name))
  self.messageWon = value
  return self
end

---Set the onWon callback
---@param value function takes 2 parameters: the zone name (string), the monitored player units (table)
---@return table self
function AirWaveZone:setOnWon(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnWon()", veaf.p(self.name))
  self.onWon = value
  return self
end

function AirWaveZone:setMessageLost(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageLost()", veaf.p(self.name))
  self.messageLost = value
  return self
end

---Set the onLost callback
---@param value function takes 2 parameters: the zone name (string), the monitored player units (table)
---@return table self
function AirWaveZone:setOnLost(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnLost()", veaf.p(self.name))
  self.onLost = value
  return self
end

function AirWaveZone:setMessageStop(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMessageStop()", veaf.p(self.name))
  self.messageStop = value
  return self
end

---Set the onStop callback
---@param value function takes 2 parameters: the zone name (string), the monitored player units (table)
---@return table self
function AirWaveZone:setOnStop(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setOnStop()", veaf.p(self.name))
  self.onStop = value
  return self
end

function AirWaveZone:setSilent(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setSilent(%s)", veaf.p(self.name), veaf.p(value))
  self.silent = value or false
  return self
end

function AirWaveZone:setRespawnRadius(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setRespawnRadius(%s)", veaf.p(self.name), veaf.p(value))
  self.respawnRadius = value
  if self.respawnRadius < 250 then self.respawnRadius = 250 end
  return self
end

---set the default respawn offset (in meters, relative to the zone center)
---@param defaultOffsetLatitude any in meters
---@param defaultOffsetLongitude any in meters
---@return table self
function AirWaveZone:setRespawnDefaultOffset(defaultOffsetLatitude, defaultOffsetLongitude)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setRespawnDefaultOffset(%s, %s)", veaf.p(self.name), veaf.p(defaultOffsetLatitude), veaf.p(defaultOffsetLongitude))
    self.respawnDefaultOffset = { latDelta = defaultOffsetLatitude, lonDelta = defaultOffsetLongitude}
    return self
end

function AirWaveZone:addPlayerCoalition(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:addPlayerCoalition(%s)", veaf.p(self.name), veaf.p(value))
  self.playerCoalitions[value] = value
  return self
end

function AirWaveZone:getPlayerCoalition()
  local result = nil
  for coalition, _ in pairs(self.playerCoalitions) do
    result = coalition
    break
  end
  return result
end

---Sets the default delay in seconds between waves of enemy planes
---@param value number a delay in seconds
---@return table self
function AirWaveZone:setDelayBetweenWaves(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setDelayBetweenWaves(%s)", veaf.p(self.name), veaf.p(value))
  self.delayBetweenWaves = value
  return self
end

---Sets the delay in seconds between the first human in zone and the actual activation of the zone
---@param value number a delay in seconds
---@return table self
function AirWaveZone:setDelayBeforeActivation(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setDelayBeforeActivation(%s)", veaf.p(self.name), veaf.p(value))
  self.delayBeforeActivation = value
  return self
end

function AirWaveZone:setResetWhenDying(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setResetWhenDying(%s)", veaf.p(self.name), veaf.p(value))
  self.resetWhenDying = value
  return self
end

function AirWaveZone:setMinimumAltitudeInFeet(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMinimumAltitudeInFeet(%s)", veaf.p(self.name), veaf.p(value))
  self.minimumAltitude = value * 0.3048 -- convert from feet
  return self
end

function AirWaveZone:getMinimumAltitudeInMeters()
  return self.minimumAltitude
end

function AirWaveZone:setMaximumAltitudeInFeet(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMaximumAltitudeInFeet(%s)", veaf.p(self.name), veaf.p(value))
  self.maximumAltitude = value * 0.3048 -- convert from feet
  return self
end

function AirWaveZone:getMaximumAltitudeInMeters()
  return self.maximumAltitude
end

---Sets the maximum number of seconds an IA can stay out of its zone before being destroyed
---@param value number a delay in seconds
---@return table self
function AirWaveZone:setMaxSecondsOutsideOfZoneIA(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMaxSecondsOutsideOfZoneIA(%s)", veaf.p(self.name), veaf.p(value))
  self.maxSecondsOutsideOfZoneIA = value
  return self
end

---Disables the check for IA out of zone.
---@return table self
function AirWaveZone:disableOutsideOfZoneIA()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:disableOutsideOfZoneIA()", veaf.p(self.name))
  self.maxSecondsOutsideOfZoneIA = nil
  return self
end

---Sets the maximum number of seconds a player can stay out of its zone before being destroyed; players will be messaged as soon as they exit the zone, and every check
---@param value number a delay in seconds
---@return table self
function AirWaveZone:setMaxSecondsOutsideOfZonePlayers(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMaxSecondsOutsideOfZonePlayers(%s)", veaf.p(self.name), veaf.p(value))
  self.maxSecondsOutsideOfZonePlayers = value
  return self
end

---Disables the check for players out of zone.
---@return table self
function AirWaveZone:disableOutsideOfZonePlayers()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:disableOutsideOfZonePlayers()", veaf.p(self.name))
  self.maxSecondsOutsideOfZonePlayers = nil
  return self
end

function AirWaveZone:_setState(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:_setState(%s)", veaf.p(self.name), veaf.p(veafAirWaves.statusToString(value)))
  self.state = value
  return self
end

---the function that decides if a wave is dead or not (as a set of groups and units)
---@param callback function the callback function will be called with 3 parameters: a zone, the wave index number, the spawned groups names list; it must return a boolean
function AirWaveZone:setIsEnemyWaveDeadCallback(callback)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setIsEnemyWaveDeadCallback()", veaf.p(self.name))
  self.isEnemyWaveDeadCallback = callback
  return self
end

---the function that decides if a group is dead or not (individually)
---@param callback function the callback function will be called with 3 parameters: a zone, the wave index number, a DCS group table; it must return a boolean
function AirWaveZone:setIsEnemyGroupDeadCallback(callback)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setIsEnemyGroupDeadCallback()", veaf.p(self.name))
  self.isEnemyGroupDeadCallback = callback
  return self
end

---Sets the minimum percentage of life that an AI unit is supposed to have to be considered alive
---@param value number percentage
---@return table
function AirWaveZone:setMinimumLifeForAiInPercent(value)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setMinimumLifeForAiInPercent(%s)", veaf.p(self.name), veaf.p(value))
  self.minimumLifeForAiInPercent = value
  return self
end

--- the function that handles crippled enemy units
---@param callback function the callback function will be called with 3 parameters: a zone, the wave index number, a DCS unit table; it must do what it wants with the unit
function AirWaveZone:setHandleCrippledEnemyUnitCallback(callback)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:setHandleCrippledEnemyUnitCallback()", veaf.p(self.name))
  self.handleCrippledEnemyUnitCallback = callback
  return self
end

function AirWaveZone:reset()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:reset()", veaf.p(self.name))

  -- despawn the ennemies
  self:destroyCurrentWave()

  -- reset all the zone properties

  -- player units (if they die, reset the zone)
  self.playerUnitsNames = {}
  -- groups that have been spawned (the current wave)
  self.spawnedGroupsNames = {}
  -- the delay after this wave, and before the next one (either set in the wave definition, or it's the default delayBetweenWaves)
  self.delayBeforeNextWave = nil
  -- the time when the next wave is supposed to spawn (used to know when to actually spawn when in the STATUS_WAITING_FOR_NEXTWAVE state)
  self.timeOfNextWave = nil
  -- the time when the zones is supposed to be activated (used to know when to actually activate when in the STATUS_WAITING_FOR_MORE_HUMANS state)
  self.timeOfActivation = nil
  -- human units that are being watched
  self.playerHumanUnits = nil
  -- names of the human units that are being watched
  self.playerHumanUnitsNames = nil
  -- IA units that are being watched
  self.unitsInZone = {}
  -- current wave number
  self.currentWaveIndex = 0
  -- the drawing object that has been used to draw the zone
  self.zoneDrawing = nil
  -- the time humans exited the zone
  self.timestampsOutOfZone = {}

  -- deschedule the check() function
  if self.checkFunctionSchedule then
    mist.removeFunction(self.checkFunctionSchedule)
    self.checkFunctionSchedule = nil
  end

  return self
end

-- the function that decides if a wave is dead or not (as a set of groups and units)
function AirWaveZone:isEnemyWaveDead(waveNumber, waveGroupsNames)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:isEnemyWaveDead(%s)", veaf.p(self.name), veaf.p(waveNumber))
-- LOGGING DISABLED WHEN COMPILING("waveGroupsNames=%s", veaf.p(waveGroupsNames))

  local currentWaveAlive = false
  for _, groupName in pairs(waveGroupsNames) do
    local group = Group.getByName(groupName)
    if group then
      local groupIsDead = self.isEnemyGroupDeadCallback(self, self.currentWaveIndex, group)
      if not groupIsDead then
        currentWaveAlive = true
      end
    end
  end
  return not currentWaveAlive
end

-- the function that decides if IA ennemy groups are dead (individually)
function AirWaveZone:isEnemyGroupDead(waveNumber, group)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:isEnemyGroupDead(%s)", veaf.p(self.name), veaf.p(waveNumber))
  if not group then return true end
-- LOGGING DISABLED WHEN COMPILING("group:getName()=%s", veaf.p(group:getName()))

  local groupAtLeastOneUnitAlive = false
  local category = group:getCategory()
  local units = group:getUnits()
  if units then
    for _,unit in pairs(units) do
      local unitAlive = false
      local unitLife = unit:getLife()
      local unitLife0 = 0
      if unit.getLife0 then -- statics have no life0
        unitLife0 = unit:getLife0()
      end
      local unitLifePercent = unitLife
      if unitLife0 > 0 then
        unitLifePercent = 100 * unitLife / unitLife0
      end
      if unitLifePercent > self.minimumLifeForAiInPercent then
        if category == 0 --[[airplanes]] or category == 1 --[[helicopters]] then
          if unit:inAir() then
            unitAlive = true
          end
        else
          unitAlive = true
        end
      end
      if not unitAlive then
        self.handleCrippledEnemyUnitCallback(self, self.currentWaveIndex, unit)
      else
        groupAtLeastOneUnitAlive = true
      end
    end
  end
  return not groupAtLeastOneUnitAlive
end

-- the function that handles crippled enemy units
function AirWaveZone:handleCrippledEnemyUnit(waveNumber, unit)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:handleCrippledEnemyUnit(%s)", veaf.p(self.name), veaf.p(waveNumber))
  if not unit then return end
-- LOGGING DISABLED WHEN COMPILING("unit:getName()=%s", veaf.p(unit:getName()))
  -- simply despawn the unit
  unit:destroy()
end

function AirWaveZone:getPlayerUnitsNames()
  if not self.playerHumanUnitsNames then
    self.playerHumanUnitsNames = {}
    for _, unit in pairs(mist.DBs.humansByName) do
      local coalitionId = 0
      if unit.coalition then
        if unit.coalition:lower() == "red" then
          coalitionId = coalition.side.RED
        elseif unit.coalition:lower() == "blue" then
          coalitionId = coalition.side.BLUE
        end
      end
      if self.playerCoalitions[coalitionId] then
        if unit.category then
          if unit.category == "plane" then
            table.insert(self.playerHumanUnitsNames, unit.unitName)
          end
        end
      end
    end
  end
  return self.playerHumanUnitsNames
end

function AirWaveZone:check()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:check() -> self.state=%s", veaf.p(self.name), veaf.p(veafAirWaves.statusToString(self.state)))
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:check() -> timer.getTime()=%s", veaf.p(self.name), veaf.p(timer.getTime()))

  local function getHumansInZone()
    local resultUnitsByName = {}
    local resultUnitsNames = {}
    local resultUnits = {}
    local unitNames = self:getPlayerUnitsNames()
    local triggerZone = veaf.getTriggerZone(self.triggerZoneName)
    local humanUnits = nil
    if triggerZone then
      if triggerZone.type == 0 then -- circular
        humanUnits = mist.getUnitsInZones(unitNames, {self.triggerZoneName})
      elseif triggerZone.type == 2 then -- quad point
        humanUnits = mist.getUnitsInPolygon(unitNames, triggerZone.verticies)
      end
    elseif self.zoneCenter then
      humanUnits = veaf.findUnitsInCircle(self.zoneCenter, self.zoneRadius, false, unitNames)
    else
      veaf.loggers.get(veafAirWaves.Id):error("No triggerzone, and no zone center/radius defined!")
    end
    for _, unit in pairs(humanUnits) do
      -- check the unit altitude against the ceiling and floor
      if unit:inAir() then -- never count a landed aircraft
        local alt = unit:getPoint().y
        if alt >= self:getMinimumAltitudeInMeters() and alt <= self:getMaximumAltitudeInMeters() then
          -- add the unit to the player units list, so that we can monitor it
          local unitName = unit:getName()
          table.insert(resultUnitsNames, unitName)
          table.insert(resultUnits, unit)
          resultUnitsByName[unitName] = unit
        end
      end
    end
    return resultUnits, resultUnitsNames, resultUnitsByName
  end

  local humansInZone, humansInZoneNames, humansInZoneByName = getHumansInZone()

  -- whatever the state, monitor the player units if they're defined
  if self.playerUnitsNames and #self.playerUnitsNames > 0 then
    local atLeastOnePlayerAlive = false
    local atLeastOnePlayerAirborne = false
    for _, unitName in pairs(self.playerUnitsNames) do
      local unit = Unit.getByName(unitName)
      if unit then
        -- check alive
        atLeastOnePlayerAlive = true
        if unit:inAir() then
          atLeastOnePlayerAirborne = true
        end
        -- check in zone
        if humansInZoneByName[unitName] then
          self.timestampsOutOfZone[unitName] = nil
        elseif self.maxSecondsOutsideOfZonePlayers then
          local timestampOutOfZone = timer.getTime()
          if self.timestampsOutOfZone[unitName] then
            timestampOutOfZone = self.timestampsOutOfZone[unitName]
          else
            self.timestampsOutOfZone[unitName] = timestampOutOfZone
          end
          local seconds = timer.getTime() - timestampOutOfZone
          self:signalOutsideOfZone(unitName, seconds)
          local secondsOffend = seconds - self.maxSecondsOutsideOfZonePlayers
          if secondsOffend > 0 then
            -- destroy the player
            if secondsOffend > self.maxSecondsOutsideOfZonePlayers then
-- LOGGING DISABLED WHEN COMPILING("destroy out of zone player unitName=%s", veaf.p(unitName))
              unit:destroy()
            else
-- LOGGING DISABLED WHEN COMPILING("flak out of zone player unitName=%s", veaf.p(unitName))
              local point = unit:getPoint()
              local positionForFlak1 = mist.vec.add(point, mist.vec.scalarMult(unit:getVelocity(), 1))
              local positionForFlak2 = mist.vec.add(point, mist.vec.scalarMult(unit:getVelocity(), 2))
              local positionForFlak3 = mist.vec.add(point, mist.vec.scalarMult(unit:getVelocity(), 3))
              veafSpawn.spawnBomb(positionForFlak1, 50, 5, 25 + seconds - self.maxSecondsOutsideOfZonePlayers, positionForFlak1.y, 50)
              veafSpawn.spawnBomb(positionForFlak2, 50, 5, 25 + seconds - self.maxSecondsOutsideOfZonePlayers, positionForFlak2.y, 50)
              veafSpawn.spawnBomb(positionForFlak3, 50, 5, 25 + seconds - self.maxSecondsOutsideOfZonePlayers, positionForFlak3.y, 50)
            end
          end
        end
      end
    end
    if not (atLeastOnePlayerAlive and atLeastOnePlayerAirborne) then
-- LOGGING DISABLED WHEN COMPILING("player is dead or despawned in %s", veaf.p(self:getName()))
      if self.state ~= veafAirWaves.STATUS_OVER then
        -- signal that all players have been destroyed
        self:signalLost()
      end
      if self.resetWhenDying then
        -- reset the zone
        self:stop()
        self:start()
      end
    end
  end

  if self.state == veafAirWaves.STATUS_READY then
    if humansInZone and #humansInZone > 0 then
      -- store the human units that we're going to monitor
      self.unitsInZone = humansInZone
      self.playerUnitsNames = humansInZoneNames
      self:_setState(veafAirWaves.STATUS_WAITING_FOR_MORE_HUMANS)
      if self.delayBeforeActivation and self.delayBeforeActivation > 0 then
        self:signalWaitForHumans()
      end
      self.timeOfActivation = timer.getTime() + self.delayBeforeActivation
-- LOGGING DISABLED WHEN COMPILING("waiting %s seconds before activation", veaf.p(self.delayBeforeActivation))
-- LOGGING DISABLED WHEN COMPILING("self.timeOfActivation=%s", veaf.p(self.timeOfActivation))
-- LOGGING DISABLED WHEN COMPILING("restart the check immediately")
      -- restart the check immediately (we don't want to wait for the next state to be processed)
      self:check()
    end
  elseif self.state == veafAirWaves.STATUS_WAITING_FOR_MORE_HUMANS then
    -- wait until the delay has passed
    if self.timeOfActivation and timer.getTime() >= self.timeOfActivation then
      -- zone is ready, check for players entering
      local humansInZone, humanInZoneNames = getHumansInZone()
      if humansInZone and #humansInZone > 0 then
        -- store the human units that we're going to monitor
        self.unitsInZone = humansInZone
        self.playerUnitsNames = humanInZoneNames
        -- reset wave index
        self.currentWaveIndex = 0
        self:_setState(veafAirWaves.STATUS_NEXTWAVE)
        -- restart the check immediately (we don't want to wait for the next state to be processed)
        self:check()
      end
    end
  elseif self.state == veafAirWaves.STATUS_NEXTWAVE then
    -- wave has been destroyed, or it's the first time a wave has to be deployed; check if there is a next one and deploy it
    if self.currentWaveIndex < #self.waves then
      if not self.delayBeforeNextWave then
        self.delayBeforeNextWave = self.delayBetweenWaves
      end
      self:_setState(veafAirWaves.STATUS_WAITING_FOR_NEXTWAVE)
      if self.delayBeforeNextWave and self.delayBeforeNextWave > 0 then
        self:signalWaitToDeploy()
      end
      self.timeOfNextWave = timer.getTime() + self.delayBeforeNextWave
-- LOGGING DISABLED WHEN COMPILING("waiting %s seconds before spawning next wave(s)", veaf.p(self.delayBeforeNextWave))
-- LOGGING DISABLED WHEN COMPILING("self.timeOfNextWave=%s", veaf.p(self.timeOfNextWave))
      -- restart the check immediately (we don't want to wait for the next state to be processed)
      self:check()
    else
      self:signalWon()
      self:_setState(veafAirWaves.STATUS_OVER)
    end
  elseif self.state == veafAirWaves.STATUS_WAITING_FOR_NEXTWAVE then
    -- wait until the delay has passed
    if self.timeOfNextWave and timer.getTime() >= self.timeOfNextWave then
      -- deploy the next wave
      local spawnedGroups, delayBeforeNextWave = self:deployWaves()
      if spawnedGroups then
        self.delayBeforeNextWave = delayBeforeNextWave or self.delayBetweenWaves
        self:_setState(veafAirWaves.STATUS_ACTIVE)
      end
    end
  elseif self.state == veafAirWaves.STATUS_ACTIVE then
    -- zone is active
    
    -- check if the current wave is still alive
    local waveIsDead = self.isEnemyWaveDeadCallback(self, self.currentWaveIndex, self.spawnedGroupsNames)
    if waveIsDead then
      -- clean up any eventual remaining group of the wave
      self:destroyCurrentWave()
      -- signal that wave has been destroyed
      self:signalDestroyed()
      -- prepare next wave
      self:_setState(veafAirWaves.STATUS_NEXTWAVE)
      -- restart the check immediately (we don't want to wait for the next state to be processed)
      self:check()
    else
      -- check if any IA wandered out of the zone for longer than it should have (maxSecondsOutsideOfZoneIA)
      local triggerZone = veaf.getTriggerZone(self.triggerZoneName)
      for _, groupName in pairs(self.spawnedGroupsNames) do
        local group = Group.getByName(groupName)
        if group then
          local units = group:getUnits()
          if units then
            for _, unit in pairs(units) do
              local unitName = unit:getName()
              local outOfZone = false
              if self.maxSecondsOutsideOfZoneIA then -- no need to check if feature is disabled
                if triggerZone then
                  outOfZone = not(veaf.isUnitInZone(unit, triggerZone))
                else
                  local pos = unit:getPosition().p
                  if pos then -- you never know O.o
                      local distanceFromCenter = ((pos.x - self.zoneCenter.x)^2 + (pos.z - self.zoneCenter.z)^2)^0.5
                      outOfZone = (distanceFromCenter > self.zoneRadius)
                  end
                end
                if outOfZone then
                  local timestampOutOfZone = timer.getTime()
                  if self.timestampsOutOfZone[unitName] then
                    timestampOutOfZone = self.timestampsOutOfZone[unitName]
                  else
                    self.timestampsOutOfZone[unitName] = timestampOutOfZone
                  end
                  local seconds = timer.getTime() - timestampOutOfZone
                  local secondsOffend = seconds - self.maxSecondsOutsideOfZoneIA
                  if secondsOffend > 0 then
                    -- destroy the IA
-- LOGGING DISABLED WHEN COMPILING("destroy out of zone AI unitName=%s", veaf.p(unitName))
                    unit:destroy()
                  end
                else
                  self.timestampsOutOfZone[unitName] = nil
                end
              end
            end
          end
        end
      end
    end
  elseif self.state == veafAirWaves.STATUS_OVER then
    -- zone has still to be reset to restart
  end
  if self.checkFunctionSchedule then
    -- deschedule if needed
    mist.removeFunction(self.checkFunctionSchedule)
    self.checkFunctionSchedule = nil
  end
  self.checkFunctionSchedule = mist.scheduleFunction(AirWaveZone.check, {self}, timer.getTime() + veafAirWaves.WATCHDOG_DELAY + math.random(0, 2)) -- randomize reschedules so not all zones are working at the same time
end

function AirWaveZone:chooseGroupsToDeploy()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:chooseGroupsToDeploy()", veaf.p(self.name))

  if self.currentWaveIndex <= #self.waves then
    local nextWave = self.waves[self.currentWaveIndex]
    if nextWave then
      -- process a random group definition
      local groupsToChooseFrom = nextWave.groups
      local numberOfGroups = nextWave.number
      local bias = nextWave.bias
      local delay = nextWave.delay
      local result = {}
      if type(numberOfGroups) == "string" then
        -- convert randomizable numeric to number
        numberOfGroups = veaf.getRandomizableNumeric(numberOfGroups)
      end
      if type(bias) == "string" then
        -- convert randomizable numeric to number
        bias = veaf.getRandomizableNumeric(bias)
      end
      if delay ~= nil and type(delay) == "string" then
        -- convert randomizable numeric to number
        delay = veaf.getRandomizableNumeric(delay)
      end
      if groupsToChooseFrom and type(groupsToChooseFrom) == "table" and numberOfGroups and type(numberOfGroups) == "number" and bias and type(bias) == "number" then
        for _ = 1, numberOfGroups do
          local group = veaf.randomlyChooseFrom(groupsToChooseFrom, bias)
          table.insert(result, group)
        end
      end
      return result, delay
    end
  end
end

function AirWaveZone:deployWaves()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:deployWaves()", veaf.p(self.name))
  self.spawnedGroupsNames = {}
  local groupsToDeployForTheseWaves = {}
  local lastDelay
  repeat
    self.currentWaveIndex = self.currentWaveIndex + 1
    local groupsToDeploy, delay = self:chooseGroupsToDeploy()
-- LOGGING DISABLED WHEN COMPILING("groupsToDeploy=%s", veaf.p(groupsToDeploy))
-- LOGGING DISABLED WHEN COMPILING("delay=%s", veaf.p(delay))
    lastDelay = delay
    for _, group in pairs(groupsToDeploy) do
      table.insert(groupsToDeployForTheseWaves, group)
    end
  until not lastDelay or lastDelay >= 0 or self.currentWaveIndex >= #self.waves
  if groupsToDeployForTheseWaves then
    local zoneCenter = {}
    if self.triggerZoneName then
      local triggerZone = veaf.getTriggerZone(self.triggerZoneName)
      zoneCenter.x = triggerZone.x
      zoneCenter.z = triggerZone.y
      zoneCenter.y = 0
    elseif self.zoneCenter then
      zoneCenter = self.zoneCenter
    end
    for _, groupNameOrCommand in pairs(groupsToDeployForTheseWaves) do
      -- check if this is a DCS group or a VEAF command
      if veaf.startsWith(groupNameOrCommand, "[") or veaf.startsWith(groupNameOrCommand, "-") then
        -- this is a command
        local command = groupNameOrCommand
        local latDelta = self.respawnDefaultOffset.latDelta
        local lonDelta = self.respawnDefaultOffset.lonDelta
        if veaf.startsWith(groupNameOrCommand, "[") then
          -- extract relative coordinates and the actual command
          local coords
          coords, command = groupNameOrCommand:match("%[(.*)%](.*)")
          if coords then
            latDelta, lonDelta = coords:match("([%+-%d]+),%s*([%+-%d]+)")
          end
        end
-- LOGGING DISABLED WHEN COMPILING("running command [%s]", veaf.p(command))
        local position = {x = zoneCenter.x - lonDelta, y = zoneCenter.y, z = zoneCenter.z + latDelta}
        local randomPosition = mist.getRandPointInCircle(position, self.respawnRadius)
        local spawnedGroupsNames = {}
        veafInterpreter.execute(command, randomPosition, self.coalition, nil, spawnedGroupsNames)
        for _, newGroupName in pairs(spawnedGroupsNames) do
          table.insert(self.spawnedGroupsNames, newGroupName)
        end
      else
        -- this is a DCS group
        local groupName = groupNameOrCommand
-- LOGGING DISABLED WHEN COMPILING("spawning group [%s]", veaf.p(groupName))
        local groupData = mist.getGroupData(groupName)
-- LOGGING DISABLED WHEN COMPILING("groupData=%s", veaf.p(groupData))
        if not groupData then
          veaf.loggers.get(veafAirWaves.Id):error("group [%s] does not exist in the mission!", veaf.p(groupName))
        else
          local spawnSpot = {x = zoneCenter.x - self.respawnDefaultOffset.lonDelta, y = zoneCenter.y, z = zoneCenter.z + self.respawnDefaultOffset.latDelta}
          -- Try and set the spawn spot at the place the group has been set in the Mission Editor.
          -- Unfortunately this is sometimes not possible because DCS is not returning the group units for some reason.
          -- When this happens we'll default to the default spawn offset (same as spawning with VEAF commands)
          if not groupData.units[1] then
            veaf.loggers.get(veafAirWaves.Id):warn("group [%s] does not have any unit!", veaf.p(groupName))
          else
            spawnSpot =  { x = groupData.units[1].x, y = groupData.units[1].alt, z = groupData.units[1].y }
          end
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=%s", veaf.p(spawnSpot))
          local vars = {}
          vars.point = mist.getRandPointInCircle(spawnSpot, self.respawnRadius)
          vars.point.z = vars.point.y
          vars.point.y = spawnSpot.y
          vars.gpName = groupName
          vars.action = 'clone'
          vars.route = mist.getGroupRoute(groupName, 'task')
-- LOGGING DISABLED WHEN COMPILING("vars=%s", veaf.p(vars))
          local newGroup = mist.teleportToPoint(vars) -- respawn with radius
          if newGroup then
            table.insert(self.spawnedGroupsNames, newGroup.name)
          end
        end
      end
    end
-- LOGGING DISABLED WHEN COMPILING("self.spawnedGroupsNames=%s", veaf.p(self.spawnedGroupsNames))
    self:_setState(veafAirWaves.STATUS_ACTIVE)
  end
  self:signalDeploy()
  return (self.spawnedGroupsNames and #self.spawnedGroupsNames > 0), lastDelay
end

---Sends a message to all players in the zone, but only once per group (because we're actually messaging whole groups, thanks DCS)
---@param msg string the message to be sent
function AirWaveZone:signalToPlayers(msg)
  if self.unitsInZone then
    for _, unitInZone in pairs(self.unitsInZone) do
      local unitName = unitInZone:getName()
      veaf.outTextForUnit(unitName, msg, 15)
    end
  end
end

function AirWaveZone:signalStart()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalStart()", veaf.p(self.name))
  if not self.silent then
    local msg = string.format(self.messageStart, self:getDescription())
    for coalition, _ in pairs(self.playerCoalitions) do
      trigger.action.outTextForCoalition(coalition, msg, 15)
    end
  end
  if self.onStart then
    self.onStart(self.name, self.playerUnitsNames)
  end
end

function AirWaveZone:signalWaitForHumans()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalWaitForHumans()", veaf.p(self.name))
  if not self.silent then
    self:signalToPlayers(string.format(self.messageWaitForHumans, self:getDescription(), self.delayBeforeActivation))
  end
  if self.onWaitForHumans then
    self.onWaitForHumans(self.name, self.playerUnitsNames)
  end
end

function AirWaveZone:signalWaitToDeploy()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalWaitToDeploy()", veaf.p(self.name))
  if not self.silent then
    self:signalToPlayers(string.format(self.messageWaitToDeploy, self:getDescription(), self.delayBeforeNextWave))
  end
  if self.onWaitToDeploy then
    self.onWaitToDeploy(self.name, self.playerUnitsNames)
  end
end

function AirWaveZone:signalDeploy()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalDeploy()", veaf.p(self.name))
  if not self.silent then
    -- messages to all
    local msg = string.format(self.messageDeploy, self:getDescription(), self.currentWaveIndex)
    for coalition, _ in pairs(self.playerCoalitions) do
      trigger.action.outTextForCoalition(coalition, msg, 15)
    end
    -- messages to players with BRAA
    if self.unitsInZone then
      local groupsAlreadyMessaged = {}
      for _, unitInZone in pairs(self.unitsInZone) do
        -- compute BRAA of closest group
        local braa = { bearing = -1, distance = 9999}
        for _, spawnedGroupName in pairs(self.spawnedGroupsNames) do
          local spawnedGroupPosition = mist.getAvgGroupPos(spawnedGroupName)
          local unitPosition = nil
          if unitInZone and unitInZone:getPosition() then
            unitPosition = unitInZone:getPosition().p
          end
          if spawnedGroupPosition and unitPosition then
            local bearing, _, _, distanceInNm = veaf.getBearingAndRangeFromTo(unitPosition, spawnedGroupPosition)
            if braa.distance > distanceInNm then
              -- this is closer than the group we had before
              braa.distance = math.floor(distanceInNm)
              braa.bearing = math.floor(bearing)
            end
          end
        end
        if braa.bearing > -1 then
          -- found a group
          local braaS = string.format("BRA %03d/%02d", braa.bearing, braa.distance)
          if braa.distance < 5 then
            braaS = "MERGED"
          end
          local group = unitInZone:getGroup()
          local groupId = nil
          if group then
              groupId = group:getID()
          end
          if groupId and not groupsAlreadyMessaged[groupId] then
            groupsAlreadyMessaged[groupId] = true
            trigger.action.outTextForGroup(groupId, string.format(self.messageDeployPlayers, self.currentWaveIndex, braaS), 15)
          end
        end
      end
    end
  end
  if self.onDeploy then
    self.onDeploy(self.name, self.currentWaveIndex, self.playerUnitsNames)
  end
end

function AirWaveZone:signalDestroyed()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalDestroyed()", veaf.p(self.name))
  if not self.silent then
    self:signalToPlayers(string.format(self.messageDestroyed, self:getDescription(), self.currentWaveIndex))
  end
  if self.onDestroyed then
    self.onDestroyed(self.name, self.currentWaveIndex, self.playerUnitsNames)
  end
end

function AirWaveZone:signalOutsideOfZone(playerUnitName, seconds)
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalOutsideOfZone(player=%s, seconds=%s)", veaf.p(self.name), veaf.p(playerUnitName), veaf.p(seconds))
  if not self.silent then
    veaf.outTextForUnit(playerUnitName, string.format(self.messageOutsideOfZone, self:getDescription(), seconds, self.maxSecondsOutsideOfZonePlayers), 15)
  end
  if self.onOutsideOfZone then
    self.onOutsideOfZone(self.name, playerUnitName, seconds)
  end
end

function AirWaveZone:signalWon()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalWon()", veaf.p(self.name))
  if not self.silent then
    self:signalToPlayers(string.format(self.messageWon, self:getDescription()))
  end
  if self.onWon then
    self.onWon(self.name, self.playerUnitsNames)
  end
end

function AirWaveZone:signalLost()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalLost()", veaf.p(self.name))
  if not self.silent then
    local msg = string.format(self.messageLost, self:getDescription())
    for coalition, _ in pairs(self.playerCoalitions) do
      trigger.action.outTextForCoalition(coalition, msg, 15)
    end
  end
  if self.onLost then
    self.onLost(self.name, self.playerUnitsNames)
  end
end

function AirWaveZone:signalStop()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:signalStop()", veaf.p(self.name))
  if not self.silent then
    local msg = string.format(self.messageStop, self:getDescription())
    for coalition, _ in pairs(self.playerCoalitions) do
      trigger.action.outTextForCoalition(coalition, msg, 15)
    end
  end
  if self.onStop then
    self.onStop(self.name, self.playerUnitsNames)
  end
end

function AirWaveZone:start()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:start()", veaf.p(self.name))
  self:reset()
  self:_setState(veafAirWaves.STATUS_READY)
  self:check()

  -- draw the zone
  if self.drawZone then
    if self.triggerZoneName then
      self.zoneDrawing = mist.marker.drawZone(self.triggerZoneName, {message=self:getDescription(), readOnly=true})
    else
      self.zoneDrawing = VeafCircleOnMap:new()
      :setName(self:getName())
      :setCoalition(self:getPlayerCoalition())
      :setCenter(self.zoneCenter)
      :setRadius(self.zoneRadius)
      :setLineType("dashed")
      :setColor("white")
      :setFillColor("transparent")
      :draw()
    end
  end

  self:signalStart()
  return self
end

function AirWaveZone:stop()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:stop()", veaf.p(self.name))
  self:reset()
  self:_setState(veafAirWaves.STATUS_STOP)

  -- erase the zone
  if self.zoneDrawing then
    if self.triggerZoneName then
      mist.marker.remove(self.zoneDrawing.markId)
    else
      self.zoneDrawing:erase()
    end
    self.zoneDrawing = nil
  end

  self:signalStop()
  return self
end

function AirWaveZone:destroyCurrentWave()
-- LOGGING DISABLED WHEN COMPILING("AirWaveZone[%s]:destroyCurrentWave()", veaf.p(self.name))
  if self.spawnedGroupsNames then
    for _, _groupName in pairs(self.spawnedGroupsNames) do
      local _group = Group.getByName(_groupName)
      if _group then
        _group:destroy()
      end
    end
  end
  self.spawnedGroupsNames = {}
  return self
end



-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafAirWaves.add(aWaveZone, aName)
  local name = aName or aWaveZone:getName()
  veafAirWaves.zones[name] = aWaveZone
  return aWaveZone
end

function veafAirWaves.get(aNameString)
  return veafAirWaves.zones[aNameString]
end

veaf.loggers.get(veafAirWaves.Id):info(string.format("Loading version %s", veafAirWaves.Version))

------------------ END script veafAirWaves.lua  ------------------


------------------ START script veafAssets.lua  ------------------

------------------------------------------------------------------
-- VEAF assets (important groups in a mission) management functions for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Manages the assets that exist the map (tankers, awacs, ...)
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafAssets = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafAssets.Id = "ASSETS"

--- Version.
veafAssets.Version = "1.8.2"

-- trace level, specific to this module
--veafAssets.LogLevel = "trace"

veaf.loggers.new(veafAssets.Id, veafAssets.LogLevel)

veafAssets.Assets = {
    -- list the assets common to all missions below
}

veafAssets.RadioMenuName = "ASSETS"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafAssets.rootPath = nil

veafAssets.assets = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafAssets._buildAssetRadioMenu(menu, title, element)
    if element.disposable or element.information then -- in this case we need a submenu
        local radioMenu = veafRadio.addSubMenu(element.description, menu)
        veafRadio.addCommandToSubmenu("Respawn "..element.description, radioMenu, veafAssets.respawn, element.name, veafRadio.USAGE_ForAll)
        if element.information then
            veafRadio.addCommandToSubmenu("Get info on "..element.description, radioMenu, veafAssets.info, element.name, veafRadio.USAGE_ForGroup)
        end
        if element.disposable then
            veafRadio.addSecuredCommandToSubmenu("Dispose of "..element.description, radioMenu, veafAssets.dispose, element.name, veafRadio.USAGE_ForAll)
        end
    else
        veafRadio.addCommandToSubmenu("Respawn "..element.description, menu, veafAssets.respawn, element.name, veafRadio.USAGE_ForAll)
    end
end

--- Build the initial radio menu
function veafAssets.buildRadioMenu()
    -- don't create an empty menu
    if veaf.length(veafAssets.assets) == 0 then 
        return
    end

    veafAssets.rootPath = veafRadio.addSubMenu(veafAssets.RadioMenuName)
    if not(veafRadio.skipHelpMenus) then
        veafRadio.addCommandToSubmenu("HELP", veafAssets.rootPath, veafAssets.help, nil, veafRadio.USAGE_ForGroup)
    end
  
    veafRadio.addPaginatedRadioElements(veafAssets.rootPath, veafAssets._buildAssetRadioMenu, veafAssets.assets, "description", "sort")
    veafRadio.refreshRadioMenu()
end

function veafAssets.info(parameters)
    local name, unitName = veaf.safeUnpack(parameters)
-- LOGGING DISABLED WHEN COMPILING("veafAssets.info "..name)
    local theAsset = nil
    for _, asset in pairs(veafAssets.assets) do
        if asset.name == name then
            theAsset = asset
        end
    end
    if theAsset then
        local group = Group.getByName(theAsset.name)
-- LOGGING DISABLED WHEN COMPILING(string.format("assets[%s] = '%s'",theAsset.name, theAsset.description))
        local text = theAsset.description .. " is not active nor alive"
        if group then
-- LOGGING DISABLED WHEN COMPILING("found asset group")
            local nAlive = 0
            for _, unit in pairs(group:getUnits()) do
-- LOGGING DISABLED WHEN COMPILING("unit life = "..unit:getLife())
                if unit:getLife() >= 1 then
                    nAlive = nAlive + 1
                end
            end
            if nAlive > 0 then
                if nAlive == 1 then
                    text = string.format("%s is active ; one unit is alive\n", theAsset.description)
                else
                    text = string.format("%s is active ; %d units are alive\n", theAsset.description, nAlive)
                end
                if theAsset.information then
                    text = text .. theAsset.information
                end
            end
        end 
        veaf.outTextForGroup(unitName, text, 30)
    end
end

function veafAssets.dispose(name)
-- LOGGING DISABLED WHEN COMPILING("veafAssets.dispose "..name)
    local theAsset = nil
    for _, asset in pairs(veafAssets.assets) do
        if asset.name == name then
            theAsset = asset
        end
    end
    if theAsset then
-- LOGGING DISABLED WHEN COMPILING("veafSpawn.destroy "..theAsset.name)
        local group = Group.getByName(theAsset.name)
        if group then
            for _, unit in pairs(group:getUnits()) do
                Unit.destroy(unit)
            end
        end
        local text = "I've disposed of " .. theAsset.description
        trigger.action.outText(text, 30)
    end
end

function veafAssets.respawn(name)
-- LOGGING DISABLED WHEN COMPILING("veafAssets.respawn "..name)
    local theAsset = nil
    for _, asset in pairs(veafAssets.assets) do
        if asset.name == name then
            theAsset = asset
        end
    end
    if theAsset then
        mist.respawnGroup(name, true)
        if theAsset.linked then
-- LOGGING DISABLED WHEN COMPILING(string.format("veafAssets[%s].linked=%s",name, veaf.p(theAsset.linked)))
            -- there are linked groups to respawn
            if type(theAsset.linked) == "string" then
                theAsset.linked = {theAsset.linked}
            end
            for _, linkedGroup in pairs(theAsset.linked) do
-- LOGGING DISABLED WHEN COMPILING(string.format("respawning linked group [%s]",linkedGroup))
                mist.respawnGroup(linkedGroup, true)
            end
        end
        local text = "I've respawned " .. theAsset.description
        if theAsset.jtac then
            if ctld then 
                veafSpawn.JTACAutoLase(name, theAsset.jtac, theAsset)
                text = text .. " lasing with code " .. theAsset.jtac
            end
        end
        trigger.action.outText(text, 30)
    end
end


function veafAssets.help(unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("help(%s)",unitName or ""))
    local text =
        'The radio menu lists all the assets, friendly or enemy\n' ..
        'Use these menus to respawn the assets when needed\n'
        veaf.outTextForGroup(unitName, text, 30)
end

function veafAssets.get(assetName)
    return veafAssets.assets[assetName]
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafAssets.buildAssetsDatabase()
    veafAssets.assets = {}
    for _, asset in ipairs(veafAssets.Assets) do
        veafAssets.assets[asset.name] = asset
    end
end


function veafAssets.initialize()
    veafAssets.buildAssetsDatabase()
    veafAssets.buildRadioMenu()
    -- start any action-bound asset (e.g. jtacs)
    for name, asset in pairs(veafAssets.assets) do
        if asset.jtac then
            if ctld then 
                veafSpawn.JTACAutoLase(name, asset.jtac, asset)
            end
        end
    end
end

veaf.loggers.get(veafAssets.Id):info(string.format("Loading version %s", veafAssets.Version))

--- Enable/Disable error boxes displayed on screen.
env.setErrorMessageBoxEnabled(false)


------------------ END script veafAssets.lua  ------------------


------------------ START script veafCarrierOperations.lua  ------------------

------------------------------------------------------------------
-- VEAF carrier command and functions for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Radio menus allow starting and ending carrier operations. Carriers go back to their initial point when operations are ended
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafCarrierOperations = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafCarrierOperations.Id = "CARRIER"

--- Version.
veafCarrierOperations.Version = "1.12.2"

-- trace level, specific to this module
--veafCarrierOperations.LogLevel = "trace"

veaf.loggers.new(veafCarrierOperations.Id, veafCarrierOperations.LogLevel)

veafCarrierOperations.RadioMenuName = "CARRIER OPS"
veafCarrierOperations.RadioMenuNameBlue = "CARRIER OPS - BLUE"
veafCarrierOperations.RadioMenuNameRed = "CARRIER OPS - RED"
veafCarrierOperations.DisableSecurity = false

veafCarrierOperations.AllCarriers = 
{
    ["LHA_Tarawa"] = { runwayAngleWithBRC = -1, desiredWindSpeedOnDeck = 20},
    ["Stennis"] = { runwayAngleWithBRC = 9.05, desiredWindSpeedOnDeck = 25},
    ["CVN_71"] = { runwayAngleWithBRC = 9.05, desiredWindSpeedOnDeck = 25},
    ["CVN_72"] = { runwayAngleWithBRC = 9.05, desiredWindSpeedOnDeck = 25},
    ["CVN_73"] = { runwayAngleWithBRC = 9.05, desiredWindSpeedOnDeck = 25},
    ["CVN_75"] = { runwayAngleWithBRC = 9.05, desiredWindSpeedOnDeck = 25},
    ["Forrestal"] = { runwayAngleWithBRC = 9.05, desiredWindSpeedOnDeck = 25},
    ["KUZNECOW"] ={ runwayAngleWithBRC = 9, desiredWindSpeedOnDeck = 25},
    ["CV_1143_5"] ={ runwayAngleWithBRC = 9, desiredWindSpeedOnDeck = 25}
}

veafCarrierOperations.ALT_FOR_MEASURING_WIND = 30 -- wind is measured at 30 meters, 10 meters above deck
veafCarrierOperations.ALIGNMENT_MANOEUVER_SPEED = 20 * 0.51445 -- carrier speed when not yet aligned to the wind (in m/s)
veafCarrierOperations.MAX_OPERATIONS_DURATION = 45 -- operations are stopped after (minutes)
veafCarrierOperations.SCHEDULER_INTERVAL = 1 -- scheduler runs every minute
veafCarrierOperations.MIN_WINDSPEED_FOR_CHANGING_HEADING = 4 * 0.51445 -- don't deroute the carrier if the wind speed is lower than this (m/s)
veafCarrierOperations.MIN_CARRIER_SPEED = 4 * 0.51445 -- don't make the carrier steam at less than this speed (m/s)

veafCarrierOperations.RemoteCommandParser = "([[a-zA-Z0-9]+)%s?([^%s]*)%s?(.*)"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Radio menus paths
veafCarrierOperations.rootPath = nil
veafCarrierOperations.rootPathBlue = nil
veafCarrierOperations.rootPathRed = nil

--- Carrier groups data, for Carrier Operations commands
veafCarrierOperations.carriers = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------
veafCarrierOperations.debugMarkersErasedAtEachStep = {}
veafCarrierOperations.traceMarkerId = 2727

function veafCarrierOperations.getDebugMarkersErasedAtEachStep(name)
    if not name then
        return nil
    end
    if not veafCarrierOperations.debugMarkersErasedAtEachStep then 
        veafCarrierOperations.debugMarkersErasedAtEachStep = {}
    end
    if not veafCarrierOperations.debugMarkersErasedAtEachStep[name] then
        veafCarrierOperations.debugMarkersErasedAtEachStep[name] = {}
    end
    return veafCarrierOperations.debugMarkersErasedAtEachStep[name]
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Carrier operations commands
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Start carrier operations ; changes the radio menu item to END and make the carrier move
function veafCarrierOperations.startCarrierOperations(parameters)
-- LOGGING DISABLED WHEN COMPILING("startCarrierOperations()")
-- LOGGING DISABLED WHEN COMPILING(string.format("Parameters for this command are : %s",veaf.p(parameters)))
    local carrierInfo, userUnitName = veaf.safeUnpack(parameters)
    local groupName, duration = veaf.safeUnpack(carrierInfo)
-- LOGGING DISABLED WHEN COMPILING(string.format("Carrier groupName : %s",veaf.p(groupName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("duration : %s",veaf.p(duration)))
-- LOGGING DISABLED WHEN COMPILING(string.format("userUnitName : %s",veaf.p(userUnitName)))

    local carrier = veafCarrierOperations.carriers[groupName]

    if not(carrier) then
        local text = "Cannot find the carrier group "..groupName
        veaf.loggers.get(veafCarrierOperations.Id):error(text)
        veaf.outTextForGroup(userUnitName, text, 5)
        return
    end

    -- find the actual carrier unit
    local group = Group.getByName(groupName)
    for _, unit in pairs(group:getUnits()) do
        local unitType = unit:getDesc()["typeName"]
        for knownCarrierType, data in pairs(veafCarrierOperations.AllCarriers) do
            if unitType == knownCarrierType then
                carrier.carrierUnitName = unit:getName()
                carrier.pedroUnitName = carrier.carrierUnitName .. " Pedro" -- rescue helo unit name
                carrier.tankerUnitName = carrier.carrierUnitName .. " S3B-Tanker" -- emergency tanker unit name
                carrier.tankerRouteSet = 0
                carrier.runwayAngleWithBRC = data.runwayAngleWithBRC
                carrier.desiredWindSpeedOnDeck = data.desiredWindSpeedOnDeck
                carrier.initialPosition = unit:getPosition().p
-- LOGGING DISABLED WHEN COMPILING("initialPosition="..veaf.vecToString(carrier.initialPosition))
                break
            end
        end
    end
    
    carrier.conductingAirOperations = true
    carrier.airOperationsStartedAt = timer.getTime()
    carrier.airOperationsEndAt = carrier.airOperationsStartedAt + duration * 60

    veafCarrierOperations.continueCarrierOperations(groupName) -- will update the *carrier* structure

    
    local text = 
        veafCarrierOperations.getAtcForCarrierOperations(groupName) ..
        "\n\nGetting a good alignment may require up to 5 minutes"

    veaf.loggers.get(veafCarrierOperations.Id):info(text)    
    veaf.outTextForGroup(userUnitName, text, 25)
    
    -- change the menu
-- LOGGING DISABLED WHEN COMPILING("change the menu")
    veafCarrierOperations.rebuildRadioMenu()

end

--- Continue carrier operations ; make the carrier move according to the wind. Called by startCarrierOperations and by the scheduler.
function veafCarrierOperations.continueCarrierOperations(groupName, userUnitName)
-- LOGGING DISABLED WHEN COMPILING("continueCarrierOperations(".. groupName .. ")")

    local carrier = veafCarrierOperations.carriers[groupName]

    if not(carrier) then
        local text = "Cannot find the carrier group "..groupName
        veaf.loggers.get(veafCarrierOperations.Id):error(text)
        veaf.outTextForGroup(userUnitName, text, 5)
        return
    end

    -- find the actual carrier unit
    local carrierUnit = Unit.getByName(carrier.carrierUnitName)
    
    -- take note of the starting position
    local startPosition = veaf.getAvgGroupPos(groupName)
    local currentHeading = 0
    if carrierUnit then 
        startPosition = carrierUnit:getPosition().p
-- LOGGING DISABLED WHEN COMPILING("startPosition (raw) ="..veaf.vecToString(startPosition))
        currentHeading = mist.utils.round(mist.utils.toDegree(mist.getHeading(carrierUnit, true)), 0)
    end    
-- LOGGING DISABLED WHEN COMPILING(string.format("currentHeading=%s", veaf.p(currentHeading)))
    startPosition = { x=startPosition.x, z=startPosition.z, y=startPosition.y + veafCarrierOperations.ALT_FOR_MEASURING_WIND} -- on deck, 50 meters above the water
-- LOGGING DISABLED WHEN COMPILING("startPosition="..veaf.vecToString(startPosition))
-- LOGGING DISABLED WHEN COMPILING(veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName))
-- LOGGING DISABLED WHEN COMPILING(veafCarrierOperations.traceMarkerId, "CARRIER", "startPosition", startPosition, veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName))
    local carrierDistanceFromInitialPosition = ((startPosition.x - carrier.initialPosition.x)^2 + (startPosition.z - carrier.initialPosition.z)^2)^0.5
-- LOGGING DISABLED WHEN COMPILING("carrierDistanceFromInitialPosition="..carrierDistanceFromInitialPosition)

    -- compute magnetic deviation at carrier position
    -- let's not use mist.getNorthCorrection, it's not computing magnetic deviation...
    -- TODO find how to actually compute it
    --[[
    local magdev = veaf.round(mist.getNorthCorrection(startPosition) * 180 / math.pi,1)
-- LOGGING DISABLED WHEN COMPILING("magdev = " .. magdev)
    ]]
    
    -- make the carrier move
    if startPosition ~= nil then
	
        local dir = currentHeading -- start with current heading

        --get wind info
        local wind = atmosphere.getWind(startPosition)
-- LOGGING DISABLED WHEN COMPILING("wind=%s", veaf.p(wind))
        local windspeed = mist.vec.mag(wind)
-- LOGGING DISABLED WHEN COMPILING(string.format("windspeed=%s", veaf.p(windspeed)))

        if windspeed >= veafCarrierOperations.MIN_WINDSPEED_FOR_CHANGING_HEADING then
            --get wind direction sorted
            if wind.x ~= 0 then
                dir = veaf.round(math.atan2(wind.z, wind.x) * 180 / math.pi,0)
            elseif wind.z < 0 then
                dir = 270
            elseif wind.z > 0 then
                dir = 90
            elseif wind.z == 0 then
                dir = carrier.heading
            end

            if dir < 0 then
                dir = dir + 360 --converts to positive numbers			
            end

            if dir <= 180 then
                dir = dir + 180
            else
                dir = dir - 180
            end
-- LOGGING DISABLED WHEN COMPILING(string.format("wind direction=%s", veaf.p(dir)))
            dir = veaf.round(dir + carrier.runwayAngleWithBRC) --to account for angle of landing deck and movement of the ship          
        end

        if dir > 360 then
            dir = dir - 360
        end

-- LOGGING DISABLED WHEN COMPILING(string.format("dir=%s", veaf.p(dir)))

        local speed = 1
        local desiredWindSpeedOnDeck = carrier.desiredWindSpeedOnDeck * 0.51445
        if desiredWindSpeedOnDeck < 1 then desiredWindSpeedOnDeck = 1 end -- minimum 1 m/s 
        if windspeed < desiredWindSpeedOnDeck then
            speed = desiredWindSpeedOnDeck - windspeed 
        end
        if speed < veafCarrierOperations.MIN_CARRIER_SPEED then 
            speed = veafCarrierOperations.MIN_CARRIER_SPEED
        end
-- LOGGING DISABLED WHEN COMPILING("BRC speed="..speed.." m/s")

        -- compute a new waypoint
        local headingRad = mist.utils.toRadian(dir)
        local length = 4000
        local newWaypoint = {
            x = startPosition.x + length * math.cos(headingRad),
            z = startPosition.z + length * math.sin(headingRad),
            y = startPosition.y
        }

        -- check for obstructions
        local carrierGroup = Group.getByName(groupName)
        local unitsToCheck = {}
        if carrierGroup then
            for _, unitToCheck in pairs(carrierGroup:getUnits()) do
-- LOGGING DISABLED WHEN COMPILING("checking %s %s", veaf.p(unitToCheck:getTypeName()), veaf.p(unitToCheck:getName()))
                if not carrierUnit or unitToCheck:getID() ~= carrierUnit:getID() then
                    table.insert(unitsToCheck, unitToCheck)
                end
            end
        end
-- LOGGING DISABLED WHEN COMPILING("unitsToCheck=%s", veaf.p(unitsToCheck))
        local pointA = veaf.computeCoordinatesOffsetFromRoute(startPosition, newWaypoint, 500, 500)
        local pointB = veaf.computeCoordinatesOffsetFromRoute(startPosition, newWaypoint, 500, -500)
        local pointC = veaf.computeCoordinatesOffsetFromRoute(startPosition, newWaypoint, 2000, -500)
        local pointD = veaf.computeCoordinatesOffsetFromRoute(startPosition, newWaypoint, 2000, 500)
        local polygon = {pointA, pointB, pointC, pointD}
-- LOGGING DISABLED WHEN COMPILING("polygon=%s", veaf.p(polygon))
-- LOGGING DISABLED WHEN COMPILINGQuad(veafCarrierOperations.traceMarkerId, "CARRIER", "obstructionsCheck", {pointA, pointB, pointC, pointD}, veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName), VeafDrawingOnMap.LINE_TYPE["dashed"], {1, 0, 0, 0.5})

        local obstructions = {}
        for i =1, #unitsToCheck do
            local lUnit = unitsToCheck[i]
-- LOGGING DISABLED WHEN COMPILING("lUnit:getName()=%s", veaf.p(lUnit:getName()))
            if mist.pointInPolygon(lUnit:getPosition().p, polygon) then
                obstructions[#obstructions + 1] = lUnit
            end
        end

-- LOGGING DISABLED WHEN COMPILING("obstructions=%s", veaf.p(obstructions))
        if #obstructions > 0 then
            -- obstructions found, derouting
            local newDir = dir + 90
            if newDir > 360 then
                newDir = newDir - 360
            end
    
            local msg = string.format("Obstruction found at heading %s, derouting %s to heading %s", veaf.p(#obstructions), veaf.p(dir), veaf.p(groupName), veaf.p(newDir))
-- LOGGING DISABLED WHEN COMPILING(msg)
            veaf.outTextForGroup(userUnitName, msg, 5)
            headingRad = mist.utils.toRadian(newDir)
            length = 4000
            newWaypoint = {
                x = startPosition.x + length * math.cos(headingRad),
                z = startPosition.z + length * math.sin(headingRad),
                y = startPosition.y
            }
        end

-- LOGGING DISABLED WHEN COMPILING("headingRad="..headingRad)
-- LOGGING DISABLED WHEN COMPILING("length="..length)
-- LOGGING DISABLED WHEN COMPILING("newWaypoint="..veaf.vecToString(newWaypoint))
-- LOGGING DISABLED WHEN COMPILINGArrow(veafCarrierOperations.traceMarkerId, "CARRIER", "route", startPosition, newWaypoint, veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName), VeafDrawingOnMap.LINE_TYPE["dashed"], {0, 0, 1, 0.3})
        
        local actualSpeed = speed
        if math.abs(dir - currentHeading) > 15 then -- still aligning
            actualSpeed = veafCarrierOperations.ALIGNMENT_MANOEUVER_SPEED
        end
        veaf.moveGroupTo(groupName, newWaypoint, actualSpeed, 0)
        carrier.heading = dir
-- LOGGING DISABLED WHEN COMPILING("carrier.heading = " .. carrier.heading .. " (true)")
        --carrier.heading_mag = dir + magdev
-- LOGGING DISABLED WHEN COMPILING("carrier.heading = " .. carrier.heading_mag .. " (mag)")
        carrier.speed = veaf.round(speed * 1.94384, 0)
-- LOGGING DISABLED WHEN COMPILING("carrier.speed = " .. carrier.speed .. " kn")

        -- check if a Pedro group exists for this carrier
        if not(mist.DBs.groupsByName[carrier.pedroUnitName]) then
            veaf.loggers.get(veafCarrierOperations.Id):warn("No Pedro group named " .. carrier.pedroUnitName)
        else
        -- prepare or correct the Pedro route (SH-60B, 250ft high, 1nm to the starboard side of the carrier, riding along at the same speed and heading)
            local pedroUnit = Unit.getByName(carrier.pedroUnitName)
            if (pedroUnit) then
-- LOGGING DISABLED WHEN COMPILING("found Pedro unit")
                -- check if unit is still alive
                if pedroUnit:getLife() < 1 then
                    pedroUnit = nil -- respawn when damaged
                end
            end
            
            -- spawn if needed
            if not(pedroUnit and carrier.pedroIsSpawned) then
-- LOGGING DISABLED WHEN COMPILING("respawning Pedro unit")
                local vars = {}
                vars.gpName = carrier.pedroUnitName
                vars.action = 'respawn'
                vars.point = startPosition
                vars.point.y = 100
                vars.radius = 500
                mist.teleportToPoint(vars)
                carrier.pedroIsSpawned = true
            end

            local pedroGroup = Group.getByName(carrier.pedroUnitName) -- group has the same name as the unit
            if (pedroGroup) then
-- LOGGING DISABLED WHEN COMPILING("found Pedro group")
                
                pedroUnit = Unit.getByName(carrier.pedroUnitName)
                if not pedroUnit then 
                    pedroUnit = pedroGroup:getUnits(1)
                end
-- LOGGING DISABLED WHEN COMPILING(string.format("pedroUnit=%s",veaf.p(pedroUnit)))

                -- waypoint #1 is 500m to port
                local offsetPointOnLand, offsetPoint = veaf.computeCoordinatesOffsetFromRoute(startPosition, newWaypoint, 0, 500)
                local pedroWaypoint1 = offsetPoint
                local distanceFromWP1 = ((pedroUnit:getPosition().p.x - pedroWaypoint1.x)^2 + (pedroUnit:getPosition().p.z - pedroWaypoint1.z)^2)^0.5
                if distanceFromWP1 > 500 then
-- LOGGING DISABLED WHEN COMPILING("Pedro WP1 = " .. veaf.vecToString(pedroWaypoint1))
-- LOGGING DISABLED WHEN COMPILING(veafCarrierOperations.traceMarkerId, "CARRIER", "pedroWaypoint1", pedroWaypoint1, veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName))
                else
                    pedroWaypoint1 = nil
                end

                -- waypoint #2 is 500m to port, near the end of the carrier route
                local offsetPointOnLand, offsetPoint = veaf.computeCoordinatesOffsetFromRoute(startPosition, newWaypoint, length - 250, 500)
                local pedroWaypoint2 = offsetPoint
-- LOGGING DISABLED WHEN COMPILING("Pedro WP2 = " .. veaf.vecToString(pedroWaypoint2))
-- LOGGING DISABLED WHEN COMPILING(veafCarrierOperations.traceMarkerId, "CARRIER", "pedroWaypoint2", pedroWaypoint2, veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName))

                local mission = { 
                    id = 'Mission', 
                    params = { 
                        ["communication"] = false,
                        ["start_time"] = 0,
                        ["task"] = "Transport",
                        route = { 
                            points = { }
                        } 
                    } 
                }

                if pedroWaypoint1 then 
                    mission.params.route.points = {
                        [1] = 
                        {
                            ["alt"] = 35,
                            ["action"] = "Turning Point",
                            ["alt_type"] = "BARO",
                            ["speed"] = 50,
                            ["type"] = "Turning Point",
                            ["x"] = pedroUnit:getPosition().p.x,
                            ["y"] = pedroUnit:getPosition().p.z,
                            ["speed_locked"] = true,
                        },
                        [2] = { 
                            ["type"] = "Turning Point",
                            ["action"] = "Turning Point",
                            ["x"] = pedroWaypoint1.x,
                            ["y"] = pedroWaypoint1.z,
                            ["alt"] = 35, -- in meters
                            ["alt_type"] = "BARO", 
                            ["speed"] = 50,
                            ["speed_locked"] = true, 
                        },
                        [3] = { 
                            ["type"] = "Turning Point",
                            ["action"] = "Turning Point",
                            ["x"] = pedroWaypoint2.x,
                            ["y"] = pedroWaypoint2.z,
                            ["alt"] = 35, -- in meters
                            ["alt_type"] = "BARO", 
                            ["speed"] = speed,  -- speed in m/s
                            ["speed_locked"] = true, 
                        },
                    } 
                else
                    mission.params.route.points = {
                        [1] = 
                        {
                            ["alt"] = 35,
                            ["action"] = "Turning Point",
                            ["alt_type"] = "BARO",
                            ["speed"] = 50,
                            ["type"] = "Turning Point",
                            ["x"] = pedroUnit:getPosition().p.x,
                            ["y"] = pedroUnit:getPosition().p.z,
                            ["speed_locked"] = true,
                        },
                        [2] = { 
                            ["type"] = "Turning Point",
                            ["action"] = "Turning Point",
                            ["x"] = pedroWaypoint2.x,
                            ["y"] = pedroWaypoint2.z,
                            ["alt"] = 35, -- in meters
                            ["alt_type"] = "BARO", 
                            ["speed"] = speed,  -- speed in m/s
                            ["speed_locked"] = true, 
                        },
                    } 
                end

                -- replace whole mission
-- LOGGING DISABLED WHEN COMPILING("Setting Pedro mission")
                local controller = pedroGroup:getController()
                controller:setTask(mission)

            end
        end


        -- check if a S3B-Tanker group exists for this carrier
        if not(mist.DBs.groupsByName[carrier.tankerUnitName]) then
            veaf.loggers.get(veafCarrierOperations.Id):warn("No Tanker group named " .. carrier.tankerUnitName)
        else

            local routeTanker = (carrierDistanceFromInitialPosition > 18520)
            carrier.tankerRouteSet = carrier.tankerRouteSet + 1
            if carrier.tankerRouteSet <= 2 then
                -- prepare or correct the Tanker route (8000ft high, 10nm aft and 4nm to the starboard side of the carrier, refueling on BRC)
                local tankerUnit = Unit.getByName(carrier.tankerUnitName)
                if (tankerUnit) then
-- LOGGING DISABLED WHEN COMPILING("found Tanker unit")
                    -- check if unit is still alive
                    if tankerUnit:getLife() < 1 then
                        tankerUnit = nil -- respawn when damaged
                    end
                end
                
                -- spawn if needed
                if not(tankerUnit and carrier.tankerIsSpawned) then
-- LOGGING DISABLED WHEN COMPILING("respawning Tanker unit")
                    local vars = {}
                    vars.gpName = carrier.tankerUnitName
                    vars.action = 'respawn'
                    vars.point = {}
                    vars.point.x = startPosition.x
                    vars.point.z = startPosition.z
                    vars.point.y = 2500
                    vars.radius = 500
                    mist.teleportToPoint(vars)
                    carrier.tankerIsSpawned = true
                end

                tankerUnit = Unit.getByName(carrier.tankerUnitName)
                local tankerGroup = Group.getByName(carrier.tankerUnitName) -- group has the same name as the unit
                if (tankerGroup) then
-- LOGGING DISABLED WHEN COMPILING("found Tanker group")
-- LOGGING DISABLED WHEN COMPILING("groupName="..tankerGroup:getName())
                    
                    -- waypoint #1 is 5nm to port, 5nm to the front
                    local offsetPointOnLand, offsetPoint = veaf.computeCoordinatesOffsetFromRoute(startPosition, newWaypoint, 9000, 9000)
                    local tankerWaypoint1 = offsetPoint
-- LOGGING DISABLED WHEN COMPILING("Tanker WP1 = " .. veaf.vecToString(tankerWaypoint1))
-- LOGGING DISABLED WHEN COMPILING(veafCarrierOperations.traceMarkerId, "CARRIER", "tankerWaypoint1", tankerWaypoint1, veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName))

                    -- waypoint #2 is 20nm ahead of waypoint #2, on BRC
                    local offsetPointOnLand, offsetPoint = veaf.computeCoordinatesOffsetFromRoute(startPosition, newWaypoint, 37000 + 9000, 9000)
                    local tankerWaypoint2 = offsetPoint
-- LOGGING DISABLED WHEN COMPILING("Tanker WP2 = " .. veaf.vecToString(tankerWaypoint2))
-- LOGGING DISABLED WHEN COMPILING(veafCarrierOperations.traceMarkerId, "CARRIER", "tankerWaypoint2", tankerWaypoint2, veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName))

                    local mission = { 
                        id = 'Mission', 
                        params = { 
                            ["communication"] = true,
                            ["start_time"] = 0,
                            ["task"] = "Refueling",
                            ["taskSelected"] = true,
                            ["route"] = 
                            {
                                ["points"] = 
                                {
                                    [1] = 
                                    {
                                        ["alt"] = 2500,
                                        ["action"] = "Turning Point",
                                        ["alt_type"] = "BARO",
                                        ["speed"] = 165,
                                        ["type"] = "Turning Point",
                                        ["x"] = startPosition.x,
                                        ["y"] = startPosition.z,
                                        ["speed_locked"] = true,
                                    },
                                    [2] = 
                                    {
                                        ["alt"] = 2500,
                                        ["action"] = "Turning Point",
                                        ["alt_type"] = "BARO",
                                        ["speed"] = 165,
                                        ["task"] = 
                                        {
                                            ["id"] = "ComboTask",
                                            ["params"] = 
                                            {
                                                ["tasks"] = 
                                                {
                                                    [1] = 
                                                    {
                                                        ["enabled"] = true,
                                                        ["auto"] = true,
                                                        ["id"] = "Tanker",
                                                        ["number"] = 1,
                                                    }, -- end of [1]
                                                    [2] = carrier.tankerData.tankerTacanTask
                                                }, -- end of ["tasks"]
                                            }, -- end of ["params"]
                                        }, -- end of ["task"]
                                        ["type"] = "Turning Point",
                                        ["ETA"] = 0,
                                        ["ETA_locked"] = false,
                                        ["x"] = startPosition.x,
                                        ["y"] = startPosition.z,
                                        ["speed_locked"] = true,
                                    },
                                    [3] = 
                                    {
                                        ["alt"] = 2500,
                                        ["action"] = "Turning Point",
                                        ["alt_type"] = "BARO",
                                        ["speed"] = 165,
                                        ["task"] = 
                                        {
                                            ["id"] = "ComboTask",
                                            ["params"] = 
                                            {
                                                ["tasks"] = 
                                                {
                                                    [1] = 
                                                    {
                                                        ["enabled"] = true,
                                                        ["auto"] = false,
                                                        ["id"] = "Orbit",
                                                        ["number"] = 1,
                                                        ["params"] = 
                                                        {
                                                            ["altitude"] = 2500,
                                                            ["pattern"] = "Race-Track",
                                                            ["speed"] = 165,
                                                        }, -- end of ["params"]
                                                    }, -- end of [1]
                                                }, -- end of ["tasks"]
                                            }, -- end of ["params"]
                                        }, -- end of ["task"]
                                        ["type"] = "Turning Point",
                                        ["x"] = tankerWaypoint1.x,
                                        ["y"] = tankerWaypoint1.z,
                                        ["speed_locked"] = true,
                                    },
                                    [4] = 
                                    {
                                        ["alt"] = 2500,
                                        ["action"] = "Turning Point",
                                        ["alt_type"] = "BARO",
                                        ["speed"] = 165,
                                        ["type"] = "Turning Point",
                                        ["x"] = tankerWaypoint2.x,
                                        ["y"] = tankerWaypoint2.z,
                                        ["speed_locked"] = true,
                                    }, -- end of [3]
                                }, -- end of ["points"]
                            }, -- end of ["route"]
                        }
                    }                

                    -- replace whole mission
-- LOGGING DISABLED WHEN COMPILING("Setting Tanker mission")
                    local controller = tankerGroup:getController()
                    controller:setTask(mission)
                    carrier.tankerRouteIsSet = true

                    local _setFrequency = {
                        id = 'SetFrequency',
                        params = {
                            frequency = carrier.tankerData.tankerFrequency * 1000000, --Hz
                            modulation = 0 --AM
                        }
                    }
                    Controller.setCommand(controller, _setFrequency)

                end
            end
        end
    end   
end

--- Gets informations about current carrier operations
function veafCarrierOperations.getAtcForCarrierOperations(groupName, skipNavigationData)
-- LOGGING DISABLED WHEN COMPILING("getAtcForCarrierOperations(".. groupName .. ")")

    local carrier = veafCarrierOperations.carriers[groupName]
    local carrierUnit = Unit.getByName(carrier.carrierUnitName)
    local currentHeading = -1
    local currentSpeed = -1
    local startPosition = nil
    if carrierUnit then 
        currentHeading = mist.utils.round(mist.utils.toDegree(mist.getHeading(carrierUnit, true)), 0)
        currentSpeed = mist.utils.round(mist.utils.mpsToKnots(mist.vec.mag(carrierUnit:getVelocity())),0)
        startPosition = { x=carrierUnit:getPosition().p.x, z=carrierUnit:getPosition().p.z, y=veafCarrierOperations.ALT_FOR_MEASURING_WIND} -- on deck, 50 meters above the water
    end

    if not(carrier) then
        local text = "Cannot find the carrier group "..groupName
        veaf.loggers.get(veafCarrierOperations.Id):error(text)
        trigger.action.outText(text, 5)
        return
    end

    local result = ""
    local groupPosition = veaf.getAvgGroupPos(groupName)
    
    if carrier.conductingAirOperations then
        local remainingTime = veaf.round((carrier.airOperationsEndAt - timer.getTime()) /60, 1)
        result = "The carrier group "..groupName.." is conducting air operations :\n"
        if carrier.ATC.tower then result = result .. "  - ATC : " .. carrier.ATC.tower .. "\n" end
        if carrier.ATC.tacan then result = result .. "  - TACAN : " .. carrier.ATC.tacan .. "\n" end
        if carrier.ATC.icls then result = result .. "  - ICLS : " .. carrier.ATC.icls .. "\n" end
        if carrier.ATC.link4 then 
            result = result .. "  - LINK 4 : " .. carrier.ATC.link4 .. ", " 
            if carrier.ATC.acls then
                result = result .. "ACLS is available"
            end
            result = result .. "\n"
        end
        --"  - BRC : " .. carrier.heading_mag .. " (".. carrier.heading .. " true) at " .. carrier.speed .. " kn\n" ..
        result = result .. "\n  - BRC : " .. carrier.heading .. " (true) at " .. carrier.speed .. " kn\n" ..
        "  - Remaining time : " .. remainingTime .. " minutes\n"
        if carrier.tankerData then
            result = result ..
            "\n  - Tanker " .. carrier.tankerData.tankerCallsign .. " : TACAN " ..carrier.tankerData.tankerTacanChannel.. carrier.tankerData.tankerTacanMode ..", COMM " .. carrier.tankerData.tankerFrequency .. "\n"
        end
    else
        result = "The carrier group "..groupName.." is not conducting carrier air operations\n"
    end

    if not(skipNavigationData) then
        -- add current navigation data

        if currentHeading > -1 and currentSpeed > -1 then
            -- compute magnetic deviation at carrier position
            -- let's not use mist.getNorthCorrection, it's not computing magnetic deviation...
            -- TODO find how to actually compute it
            --[[
            local magdev = veaf.round(mist.getNorthCorrection(startPosition) * 180 / math.pi,1)
-- LOGGING DISABLED WHEN COMPILING("magdev = " .. magdev)
            ]]
            result = result ..
            "\n"..
            "Current navigation parameters :\n" ..
            "  - Current heading (true) " .. veaf.round(currentHeading, 0) .. "\n" ..
            --"  - Current heading (mag)  " .. veaf.round(currentHeading, 0) .. "\n" ..
            "  - Current speed " .. currentSpeed .. " kn\n"
        end
    end

    result = result .. "\n"..veaf.weatherReport(startPosition)

    return result
end

--- Gets informations about current carrier operations
function veafCarrierOperations.atcForCarrierOperations(parameters)
    local groupName, unitName = veaf.safeUnpack(parameters)
-- LOGGING DISABLED WHEN COMPILING("atcForCarrierOperations(".. groupName .. ")")
    local text = veafCarrierOperations.getAtcForCarrierOperations(groupName)
    veaf.outTextForGroup(unitName, text, 15)
end

--- Ends carrier operations ; changes the radio menu item to START and send the carrier back to its starting point
function veafCarrierOperations.stopCarrierOperations(parameters)
    local groupName, userUnitName = veaf.safeUnpack(parameters)
-- LOGGING DISABLED WHEN COMPILING("stopCarrierOperations(".. groupName .. ")")

    local carrier = veafCarrierOperations.carriers[groupName]

    if not(carrier) then
        local text = "Cannot find the carrier group "..groupName
        veaf.loggers.get(veafCarrierOperations.Id):error(text)
        trigger.action.outText(text, 5)
        return
    end

    local carrierUnit = Unit.getByName(carrier.carrierUnitName)
    local carrierPosition = carrierUnit:getPosition().p
    
    local text = "The carrier group "..groupName.." has stopped air operations ; it's moving back to its initial position"
    veaf.loggers.get(veafCarrierOperations.Id):info(text)
    veaf.outTextForGroup(userUnitName, text, 5)
    carrier.conductingAirOperations = false
    carrier.stoppedAirOperations = true

    -- change the menu
-- LOGGING DISABLED WHEN COMPILING("change the menu")
    veafCarrierOperations.rebuildRadioMenu()

    -- make the Pedro land
    if (carrier.pedroIsSpawned) then
        carrier.pedroIsSpawned = false
        local pedroUnit = Unit.getByName(carrier.pedroUnitName)
        if (pedroUnit) then
-- LOGGING DISABLED WHEN COMPILING("found Pedro unit ; destroying it")
            pedroUnit:destroy()
        end
    end    

    -- make the tanker land
    if (carrier.tankerIsSpawned) then
        carrier.tankerIsSpawned = false
        local tankerUnit = Unit.getByName(carrier.tankerUnitName)
        if (tankerUnit) then
-- LOGGING DISABLED WHEN COMPILING("found tanker unit ; destroying it")
            tankerUnit:destroy()
        end
    end    

    veafCarrierOperations.doOperations()

end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Rebuild the radio menu
function veafCarrierOperations.rebuildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("veafCarrierOperations.rebuildRadioMenu()")

    -- find the carriers in the veafCarrierOperations.carriers table and prepare their menus
    for name, carrier in pairs(veafCarrierOperations.carriers) do
-- LOGGING DISABLED WHEN COMPILING("rebuildRadioMenu processing "..name)
        
        local menuRoot = veafCarrierOperations.rootPathRed
        if carrier.side == coalition.side.BLUE then
            menuRoot = veafCarrierOperations.rootPathBlue
        end

        -- remove the submenu if it exists
        if carrier.menuPath then
            veafRadio.delSubmenu(carrier.menuPath, menuRoot)
-- LOGGING DISABLED WHEN COMPILING("remove the submenu")
        end

        -- create the submenu
-- LOGGING DISABLED WHEN COMPILING("create the submenu")

        carrier.menuPath = veafRadio.addSubMenu(name, menuRoot)

        if carrier.conductingAirOperations then
            -- add the stop menu
            if veafCarrierOperations.DisableSecurity then
                veafRadio.addCommandToSubmenu("End air operations", carrier.menuPath, veafCarrierOperations.stopCarrierOperations, name, veafRadio.USAGE_ForGroup)
            else
                veafRadio.addSecuredCommandToSubmenu("End air operations", carrier.menuPath, veafCarrierOperations.stopCarrierOperations, name, veafRadio.USAGE_ForGroup)
            end
        else
            -- add the "start for veafCarrierOperations.MAX_OPERATIONS_DURATION" menu
            local startMenuName1 = "Start carrier air operations for " .. veafCarrierOperations.MAX_OPERATIONS_DURATION .. " minutes"
            if veafCarrierOperations.DisableSecurity then
                veafRadio.addCommandToSubmenu(startMenuName1, carrier.menuPath, veafCarrierOperations.startCarrierOperations, { name, veafCarrierOperations.MAX_OPERATIONS_DURATION }, veafRadio.USAGE_ForGroup)
            else
                veafRadio.addSecuredCommandToSubmenu(startMenuName1, carrier.menuPath, veafCarrierOperations.startCarrierOperations, { name, veafCarrierOperations.MAX_OPERATIONS_DURATION }, veafRadio.USAGE_ForGroup)
            end

            -- add the "start for veafCarrierOperations.MAX_OPERATIONS_DURATION * 2" menu
            local startMenuName2 = "Start carrier air operations for " .. veafCarrierOperations.MAX_OPERATIONS_DURATION * 2 .. " minutes"
            if veafCarrierOperations.DisableSecurity then
                veafRadio.addCommandToSubmenu(startMenuName2, carrier.menuPath, veafCarrierOperations.startCarrierOperations, { name, veafCarrierOperations.MAX_OPERATIONS_DURATION * 2 }, veafRadio.USAGE_ForGroup)
            else
                veafRadio.addSecuredCommandToSubmenu(startMenuName2, carrier.menuPath, veafCarrierOperations.startCarrierOperations, { name, veafCarrierOperations.MAX_OPERATIONS_DURATION * 2 }, veafRadio.USAGE_ForGroup)
            end
        end

        -- add the ATC menu (by player group)
        veafRadio.addCommandToSubmenu("ATC - Request informations", carrier.menuPath, veafCarrierOperations.atcForCarrierOperations, name, veafRadio.USAGE_ForGroup)

        veafRadio.refreshRadioMenu()
    end
end

--- Build the initial radio menu
function veafCarrierOperations.buildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("veafCarrierOperations.buildRadioMenu")

    -- don't create an empty menu
    if veaf.length(veafCarrierOperations.carriers) == 0 then 
        return
    end

    veafCarrierOperations.rootPath = veafRadio.addSubMenu(veafCarrierOperations.RadioMenuName)
    veafCarrierOperations.rootPathBlue = veafRadio.addSubMenu(veafCarrierOperations.RadioMenuNameBlue, veafCarrierOperations.rootPath)
    veafCarrierOperations.rootPathRed = veafRadio.addSubMenu(veafCarrierOperations.RadioMenuNameRed, veafCarrierOperations.rootPath)

    -- build HELP menu for each group
    if not(veafRadio.skipHelpMenus) then
        veafRadio.addCommandToSubmenu("HELP", veafCarrierOperations.rootPath, veafCarrierOperations.help, nil, veafRadio.USAGE_ForGroup)
    end
    
    veafCarrierOperations.rebuildRadioMenu()
end

function veafCarrierOperations.help(unitName)
    local text =
        'Use the radio menus to start and end carrier operations\n' ..
        'START: carrier will find out the wind and set sail at optimum speed to achieve a 25kn headwind\n' ..
        '       the radio menu will show the recovery course and TACAN information\n' ..
        'END  : carrier will go back to its starting point (where it was when the START command was issued)\n' ..
        'RESET: carrier will go back to where it was when the mission started'

        veaf.outTextForGroup(unitName, text, 30)
end

function veafCarrierOperations.initializeCarrierGroups()
    -- find the carriers and add them to the veafCarrierOperations.carriers table, store its initial location and create the menus
    for name, group in pairs(mist.DBs.groupsByName) do
-- LOGGING DISABLED WHEN COMPILING("found group "..name)
        -- search groups with a carrier unit in the group
        local carrier = nil
        -- find the actual carrier unit
        local group = Group.getByName(name)
        if group then
            for _, unit in pairs(group:getUnits()) do
                local unitType = unit:getDesc()["typeName"]
                for knownCarrierType, data in pairs(veafCarrierOperations.AllCarriers) do
                    if unitType == knownCarrierType then
                        local coa = group:getCoalition()
-- LOGGING DISABLED WHEN COMPILING(string.format("coa=%s", veaf.p(coa)))
                        -- found a carrier, initialize the carrier group object if needed
                        if not carrier then 
                            veafCarrierOperations.carriers[name] = {}
                            carrier = veafCarrierOperations.carriers[name]
-- LOGGING DISABLED WHEN COMPILING("found carrier !")
                        else
                            veaf.loggers.get(veafCarrierOperations.Id):warn(string.format("more than one carrier in group %s", veaf.p(name)))
                        end
                        carrier.side = coa
                        carrier.carrierUnit = unit
                        carrier.carrierUnitName = carrier.carrierUnit:getName()
                        carrier.runwayAngleWithBRC = data.runwayAngleWithBRC
                        carrier.desiredWindSpeedOnDeck = data.desiredWindSpeedOnDeck
                        carrier.heading = mist.getHeading(unit, true)

-- LOGGING DISABLED WHEN COMPILING(string.format("Carrier Data from MIST : %s",veaf.p(veaf.getGroupData(name))))

                        carrier.ATC = {}
                        carrier.ATC = veaf.getCarrierATCdata(name, unit:getName())

                        carrier.pedroUnitName = carrier.carrierUnitName .. " Pedro" -- rescue helo unit name
                        local pedroUnit = Unit.getByName(carrier.pedroUnitName)
                        if pedroUnit then
                            pedroUnit:destroy()
                        end
                        carrier.tankerUnitName = carrier.carrierUnitName .. " S3B-Tanker" -- emergency tanker unit name
                        carrier.tankerData = veaf.getTankerData(carrier.tankerUnitName)
                        local tankerUnit = Unit.getByName(carrier.tankerUnitName)
                        if tankerUnit then
                            tankerUnit:destroy()
                        end
                        break
                    end
                end
            end

            if carrier then
            -- take note of the carrier route
            carrier.missionRoute = mist.getGroupRoute(name, 'task')
-- LOGGING DISABLED WHEN COMPILING("carrier.missionRoute=%s", veaf.p(carrier.missionRoute))
            if veafCarrierOperations.Trace then
                for num, point in pairs(carrier.missionRoute) do
-- LOGGING DISABLED WHEN COMPILING(veafCarrierOperations.traceMarkerId, "CARRIER", string.format("[%s] point %d", name, tostring(num)), point, nil)
                end
            end
        end
    end
end
end

function veafCarrierOperations.doOperations()
-- LOGGING DISABLED WHEN COMPILING("veafCarrierOperations.doOperations()")

    -- find the carriers in the veafCarrierOperations.carriers table and check if they are operating
    for name, carrier in pairs(veafCarrierOperations.carriers) do
-- LOGGING DISABLED WHEN COMPILING("checking " .. name)
        if carrier.conductingAirOperations then
-- LOGGING DISABLED WHEN COMPILING(name .. " is conducting operations ; checking course and ops duration")
            if carrier.airOperationsEndAt < timer.getTime() then
                -- time to stop operations
                veaf.loggers.get(veafCarrierOperations.Id):info(name .. " has been conducting operations long enough ; stopping ops")
                veafCarrierOperations.stopCarrierOperations(name)
            else
                local remainingTime = veaf.round((carrier.airOperationsEndAt - timer.getTime()) /60, 1)
-- LOGGING DISABLED WHEN COMPILING(name .. " will continue conducting operations for " .. remainingTime .. " more minutes")
                -- check and reset course
                veafCarrierOperations.continueCarrierOperations(name)
            end
        elseif carrier.stoppedAirOperations then
            carrier.conductingAirOperations = false
-- LOGGING DISABLED WHEN COMPILING(name .. " stopped conducting operations")
-- LOGGING DISABLED WHEN COMPILING(veafCarrierOperations.getDebugMarkersErasedAtEachStep(carrier.carrierUnitName))
            carrier.stoppedAirOperations = false
            -- reset the carrier group route to its original route (set in the mission)
            if carrier.missionRoute then
-- LOGGING DISABLED WHEN COMPILING(string.format("resetting carrier %s route", name))
-- LOGGING DISABLED WHEN COMPILING("carrier.missionRoute="..veaf.p(carrier.missionRoute))
                local result = mist.goRoute(name, carrier.missionRoute)
            end
        else
-- LOGGING DISABLED WHEN COMPILING(name .. " is not conducting operations")
        end
    end
end

--- This function is called at regular interval (see veafCarrierOperations.SCHEDULER_INTERVAL) and manages the carrier operations schedules
--- It will make any carrier group that has started carrier operations maintain a correct course for recovery, even if wind changes.
--- Also, it will stop carrier operations after a set time (see veafCarrierOperations.MAX_OPERATIONS_DURATION).
function veafCarrierOperations.operationsScheduler()
-- LOGGING DISABLED WHEN COMPILING("veafCarrierOperations.operationsScheduler()")

    veafCarrierOperations.doOperations()

-- LOGGING DISABLED WHEN COMPILING("veafCarrierOperations.operationsScheduler() - rescheduling in " .. veafCarrierOperations.SCHEDULER_INTERVAL * 60 .. " s")
    mist.scheduleFunction(veafCarrierOperations.operationsScheduler,{},timer.getTime() + veafCarrierOperations.SCHEDULER_INTERVAL * 60)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- remote interface
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafCarrierOperations.listAvailableCarriers(forGroup)
    local _message = "Available carriers :\n"
    for name, carrier in pairs(veafCarrierOperations.carriers) do
        _message = _message .. " - " .. name .. "\n"
    end
    if forGroup then
        trigger.action.outTextForGroup(forGroup, _message, 15)
    else
        trigger.action.outText(_message, 15)
    end
end

-- execute command from the remote interface
function veafCarrierOperations.executeCommandFromRemote(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCarrierOperations.executeCommandFromRemote()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("parameters= %s", veaf.p(parameters)))
    local _pilot, _pilotName, _unitName, _command = unpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilot= %s", veaf.p(_pilot)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilotName= %s", veaf.p(_pilotName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_unitName= %s", veaf.p(_unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_command= %s", veaf.p(_command)))
    if not _pilot or not _command then 
        return false
    end

    local function findCarrier(carrierName)
        local _result = nil
        local _name = carrierName:lower()
        for name, carrier in pairs(veafCarrierOperations.carriers) do
            if name:lower():find(_name) then
                _result = name
            end
        end
        return _result   
    end
    
    if _command then
        -- parse the command
        local _action, _carrierName, _parameters = _command:match(veafCarrierOperations.RemoteCommandParser)
-- LOGGING DISABLED WHEN COMPILING(string.format("_action=%s",veaf.p(_action)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_carrierName=%s",veaf.p(_carrierName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_parameters=%s",veaf.p(_parameters)))
        local _groupId = nil
        if _unitName then 
            local _unit = Unit.getByName(_unitName)
            if _unit then 
                _groupId = _unit:getGroup():getID()
            end
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("_groupId=%s",veaf.p(_groupId)))
        if _action and _action:lower() == "list" then 
            veaf.loggers.get(veafCarrierOperations.Id):info(string.format("[%s] is listing carriers)",veaf.p(_pilot.name)))
            veafCarrierOperations.listAvailableCarriers(_groupId)
            return true
        elseif _action and _action:lower() == "start" and _carrierName then 
            local _duration = 45
            if _parameters and type(_parameters) == "number" then
                _duration = tonumber(parameters)
            end
            local _carrier = findCarrier(_carrierName)
-- LOGGING DISABLED WHEN COMPILING(string.format("_duration=%s",veaf.p(_duration)))
            veaf.loggers.get(veafCarrierOperations.Id):info(string.format("[%s] is starting operations on carrier [%s] for %s)",veaf.p(_pilot.name), veaf.p(_carrier), veaf.p(_parameters)))
            veafCarrierOperations.startCarrierOperations({_carrier, _duration})
            return true
        elseif _action and _action:lower() == "stop" then 
            local _carrier = findCarrier(_carrierName)
            veaf.loggers.get(veafCarrierOperations.Id):info(string.format("[%s] is stopping operations on carrier [%s])",veaf.p(_pilot.name), veaf.p(_carrier)))
            veafCarrierOperations.stopCarrierOperations(_carrier)
            return true
        elseif _action and _action:lower() == "atc" then 
            local _carrier = findCarrier(_carrierName)
            veaf.loggers.get(veafCarrierOperations.Id):info(string.format("[%s] is requesting atc on carrier [%s])",veaf.p(_pilot.name), veaf.p(_carrier)))
            local text = veafCarrierOperations.getAtcForCarrierOperations(_carrier)
            if _groupId then
                trigger.action.outTextForGroup(_groupId, text, 15)
            else
                trigger.action.outText(text, 15)
            end
            return true
        end
    end               
    return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Carrier ATC
-------------------------------------------------------------------------------------------------------------------------------------------------------------



-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafCarrierOperations.initialize()
    veafCarrierOperations.initializeCarrierGroups()
    veafCarrierOperations.buildRadioMenu()
    veafCarrierOperations.operationsScheduler()
end

veaf.loggers.get(veafCarrierOperations.Id):info(string.format("Loading version %s", veafCarrierOperations.Version))

--- Enable/Disable error boxes displayed on screen.
env.setErrorMessageBoxEnabled(false)




------------------ END script veafCarrierOperations.lua  ------------------


------------------ START script veafCasMission.lua  ------------------

------------------------------------------------------------------
-- VEAF CAS (Close Air Support) command and functions for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Listen to marker change events and creates a CAS training mission, with optional parameters
-- * Create a CAS target group, protected by SAM, AAA and manpads, to use for CAS training
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafCasMission = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafCasMission.Id = "CASMISSION"

--- Version.
veafCasMission.Version = "1.14.1"

-- trace level, specific to this module
--veafCasMission.LogLevel = "trace"

veaf.loggers.new(veafCasMission.Id, veafCasMission.LogLevel)

--- Key phrase to look for in the mark text which triggers the command.
veafCasMission.Keyphrase = "_cas"

--- Number of seconds between each check of the CAS group watchdog function
veafCasMission.SecondsBetweenWatchdogChecks = 15

--- Number of seconds between each smoke request on the CAS targets group
veafCasMission.SecondsBetweenSmokeRequests = 180

--- Number of seconds between each flare request on the CAS targets group
veafCasMission.SecondsBetweenFlareRequests = 120

--- Name of the CAS targets vehicles group 
veafCasMission.RedCasGroupName = "Red CAS Group"
veafCasMission.BlueCasGroupName = "Blue CAS Group"
veafCasMission.casGroupName = veafCasMission.RedCasGroupName
veafCasMission.afacName = nil

veafCasMission.RadioMenuName = "CAS MISSION"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Radio menus paths
veafCasMission.targetMarkersPath = nil
veafCasMission.targetInfoPath = nil
veafCasMission.rootPath = nil

-- CAS Group watchdog function id
veafCasMission.groupAliveCheckTaskID = 'none'

-- Smoke reset function id
veafCasMission.smokeResetTaskID = 'none'

-- Flare reset function id
veafCasMission.flareResetTaskID = 'none'

veafCasMission.SIDE_RED = coalition.side.RED
veafCasMission.SIDE_BLUE = coalition.side.BLUE

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafCasMission.onEventMarkChange(eventPos, event)
-- LOGGING DISABLED WHEN COMPILING(string.format("event  = %s", veaf.p(event)))

    -- choose by default the coalition opposing the player who triggered the event
    local invertedCoalition = 1
    if event.coalition == 1 then
        invertedCoalition = 2
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("event.idx  = %s", veaf.p(event.idx)))

    if veafCasMission.executeCommand(eventPos, event.text, invertedCoalition, event.idx) then 
        
        -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
        trigger.action.removeMark(event.idx)

    end
end

function veafCasMission.executeCommand(eventPos, eventText, coalition, markId, bypassSecurity)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCasMission.executeCommand(eventText=[%s])", eventText))
-- LOGGING DISABLED WHEN COMPILING(string.format("coalition=%s", veaf.p(coalition)))
-- LOGGING DISABLED WHEN COMPILING(string.format("markId=%s", veaf.p(markId)))
-- LOGGING DISABLED WHEN COMPILING(string.format("bypassSecurity=%s", veaf.p(bypassSecurity)))


    -- Check if marker has a text and the veafCasMission.keyphrase keyphrase.
    if eventText ~= nil and eventText:lower():find(veafCasMission.Keyphrase) then

        -- Analyse the mark point text and extract the keywords.
        local options = veafCasMission.markTextAnalysis(eventText)

        if options then
            -- Check options commands
            if options.casmission then

                if not (bypassSecurity or veafSecurity.checkSecurity_L9(options.password, markId)) then return end
                
                if not options.side then
                    if options.country then
                        -- deduct the side from the country
                        options.side = veaf.getCoalitionForCountry(options.country, true)
                    else
                        options.side = coalition
                    end
                end

                if not options.country then
                    -- deduct the country from the side
                    options.country = veaf.getCountryForCoalition(options.side)    
                end

                -- create the group
                veafCasMission.generateCasMission(eventPos, options.size, options.defense, options.armor, options.spacing, options.disperseOnAttack, options.side)
                return true
            end
        end
    end
    return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the mark text and extract keywords.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Extract keywords from mark text.
function veafCasMission.markTextAnalysis(text)

    -- Option parameters extracted from the mark text.
    local switch = {}
    switch.casmission = false

    -- size ; ranges from 1 to 5, 5 being the biggest.
    switch.size = 1

    -- defenses force ; ranges from 1 to 5, 5 being the toughest.
    switch.defense = 1

    -- armor force ; ranges from 1 to 5, 5 being the strongest and most modern.
    switch.armor = 1

    -- spacing ; ranges from 1 to 5, 1 being the default and 5 being the widest spacing.
    switch.spacing = 1

    -- disperse on attack ; self explanatory, if keyword is present the option will be set to true
    switch.disperseOnAttack = false

    -- password
    switch.password = nil

    -- coalition
    switch.side = nil

    -- Check for correct keywords.
    if text:lower():find(veafCasMission.Keyphrase) then
        switch.casmission = true
    else
        return nil
    end

    -- keywords are split by ","
    local keywords = veaf.split(text, ",")

    for _, keyphrase in pairs(keywords) do
        -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
        local str = veaf.breakString(veaf.trim(keyphrase), " ")
        local key = str[1]
        local val = str[2]

        if key:lower() == "password" then
            -- Unlock the command
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword password", val))
            switch.password = val
        end

        if switch.casmission and key:lower() == "size" then
            -- Set size.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword size = %d", val))
            local nVal = tonumber(val)
            if nVal <= 5 and nVal >= 1 then
                switch.size = nVal
            end
        end

        if switch.casmission and key:lower() == "defense" then
            -- Set defense.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword defense = %d", val))
            local nVal = tonumber(val)
            if nVal <= 5 and nVal >= 0 then
                switch.defense = nVal
            end
        end

        if switch.casmission and key:lower() == "armor" then
            -- Set armor.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword armor = %d", val))
            local nVal = tonumber(val)
            if nVal <= 5 and nVal >= 0 then
                switch.armor = nVal
            end
        end

        if switch.casmission and key:lower() == "spacing" then
            -- Set spacing.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword spacing = %d", val))
            local nVal = tonumber(val)
            if nVal <= 5 and nVal >= 1 then
                switch.spacing = nVal
            end
        end

        if key:lower() == "side" then
            -- Set side
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword side = %s", val))
            if val:upper() == "BLUE" then
                switch.side = veafCasMission.SIDE_BLUE
            else
                switch.side = veafCasMission.SIDE_RED
            end
        end

        if switch.casmission and key:lower() == "disperse" then
            -- Set disperse on attack.
-- LOGGING DISABLED WHEN COMPILING("Keyword disperse = %s", val)
            
            if val ~= "" then
                local nVal = tonumber(val)
                if nVal then
                    switch.disperseOnAttack = nVal
                end
            else
                switch.disperseOnAttack = 15
            end
        end

    end

    return switch
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- CAS target group generation and management
-------------------------------------------------------------------------------------------------------------------------------------------------------------

local function _addDefenseForGroups(group, side, defense, multiple, forInfantry)
-- LOGGING DISABLED WHEN COMPILING(string.format("_addDefenseForGroups(defense=[%s], side=[%s], multiple=[%s], forInfantry=[%s])", veaf.p(defense), veaf.p(side), veaf.p(multiple), veaf.p(forInfantry)))
    local _actualDefense = defense
    if defense > 0 then
        -- roll a dice : 20% chance to get a -1 (lower) difficulty, 30% chance to get a +1 (higher) difficulty, and 50% to get what was asked for
        local _dice = math.random(100)
-- LOGGING DISABLED WHEN COMPILING("_dice = " .. _dice)
        if _dice <= 20 then
            _actualDefense = defense - 1
        elseif _dice > 80 then
            _actualDefense = defense + 1
        end
    end
    if _actualDefense > 5 then _actualDefense = 6 end
    if _actualDefense < 0 then _actualDefense = 0 end
-- LOGGING DISABLED WHEN COMPILING("_actualDefense = " .. _actualDefense)
    for _ = 1, multiple do
        if _actualDefense > 5 then
            if side == veafCasMission.SIDE_BLUE then
                if forInfantry then
                    -- only spawn manpads
                    for _ = 1, math.random(1,_actualDefense-2) do
                        table.insert(group.units, { "Stinger comm", random=true })
                        table.insert(group.units, { "Soldier stinger", random=true })
                    end
                else
                    table.insert(group.units, { "M1097 Avenger", random=true })
                    table.insert(group.units, { "Roland ADS", random=true })
                    table.insert(group.units, { "Gepard", random=true })
                end
            else
                if forInfantry then
                    -- only spawn manpads
                    for _ = 1, math.random(1,_actualDefense-2) do
                        -- for _actualDefense = 4-5, spawn a modern Igla-S team
                        table.insert(group.units, { "SA-18 Igla-S comm", random=true })
                        table.insert(group.units, { "SA-18 Igla-S manpad", random=true })
                    end
                else
                    table.insert(group.units, { veaf.randomlyChooseFrom({"2S6 Tunguska", "Tor 9A331", "Tor 9A331"}), random=true })
                    table.insert(group.units, { "Strela-10M3", random=true })
                    table.insert(group.units, { "ZSU-23-4 Shilka", random=true })
                end
            end
        elseif _actualDefense == 5 then
            if side == veafCasMission.SIDE_BLUE then
                if forInfantry then
                    -- only spawn manpads
                    for _ = 1, math.random(1,_actualDefense-2) do
                        table.insert(group.units, { "Stinger comm", random=true })
                        table.insert(group.units, { "Soldier stinger", random=true })
                    end
                else
                    table.insert(group.units, { veaf.randomlyChooseFrom({"Gepard", "M1097 Avenger", "M1097 Avenger"}), random=true })
                    table.insert(group.units, { "Roland ADS", random=true })
                end
            else
                if forInfantry then
                    -- only spawn manpads
                    for _ = 1, math.random(1,_actualDefense-2) do
                        -- for _actualDefense = 4-5, spawn a modern Igla-S team
                        table.insert(group.units, { "SA-18 Igla-S comm", random=true })
                        table.insert(group.units, { "SA-18 Igla-S manpad", random=true })
                    end
                else
                    table.insert(group.units, { veaf.randomlyChooseFrom({"Osa 9A33 ln", "2S6 Tunguska"}), random=true })
                    table.insert(group.units, { veaf.randomlyChooseFrom({"ZSU-23-4 Shilka", "Strela-10M3"}), random=true })
                end
            end
        elseif _actualDefense == 4 then
            if side == veafCasMission.SIDE_BLUE then
                if forInfantry then
                    -- only spawn manpads
                    for _ = 1, math.random(1,_actualDefense-2) do
                        table.insert(group.units, { "Stinger comm", random=true })
                        table.insert(group.units, { "Soldier stinger", random=true })
                    end
                else
                    table.insert(group.units, { "Gepard", random=true })
                    table.insert(group.units, { "Roland ADS", random=true })
                end
            else
                if forInfantry then
                    -- only spawn manpads
                    for _ = 1, math.random(1,_actualDefense-2) do
                        -- for _actualDefense = 4-5, spawn a modern Igla-S team
                        table.insert(group.units, { "SA-18 Igla-S comm", random=true })
                        table.insert(group.units, { "SA-18 Igla-S manpad", random=true })
                    end
                else
                    table.insert(group.units, { veaf.randomlyChooseFrom({"ZSU-23-4 Shilka", "ZSU-23-4 Shilka", "ZSU_57_2"}), random=true })
                    table.insert(group.units, { veaf.randomlyChooseFrom({"HQ-7_LN_EO", "HQ-7_LN_SP"}), random=true })
                end
            end
        elseif _actualDefense == 3 then
            if side == veafCasMission.SIDE_BLUE then
                if forInfantry then
                    -- only spawn manpads
                    for _ = 1, math.random(1,_actualDefense-2) do
                        table.insert(group.units, { "Stinger comm", random=true })
                        table.insert(group.units, { "Soldier stinger", random=true })
                    end
                else
                    table.insert(group.units, { veaf.randomlyChooseFrom({"M48 Chaparral", "M6 Linebacker"}), random=true })
                    table.insert(group.units, { "Gepard", random=true })
                end
            else
                if forInfantry then
                    -- only spawn manpads
                    for _ = 1, math.random(1,_actualDefense-2) do
                        -- for _actualDefense = 3, spawn an older Igla team
                        table.insert(group.units, { "SA-18 Igla comm", random=true })
                        table.insert(group.units, { "SA-18 Igla manpad", random=true })
                    end
                else
                    table.insert(group.units, { veaf.randomlyChooseFrom({"Strela-1 9P31", "Strela-10M3"}), random=true })
                    table.insert(group.units, { veaf.randomlyChooseFrom({"ZSU-23-4 Shilka", "ZSU-23-4 Shilka", "ZSU_57_2"}), random=true })
                end
            end
        elseif _actualDefense == 2 then
            if side == veafCasMission.SIDE_BLUE then
                table.insert(group.units, { "Gepard", random=true })
                table.insert(group.units, { "Vulcan", random=true })
            else
                table.insert(group.units, { veaf.randomlyChooseFrom({"ZSU-23-4 Shilka", "ZSU_57_2"}), random=true })
                table.insert(group.units, { veaf.randomlyChooseFrom({"ZSU-23-4 Shilka", "ZSU_57_2"}), random=true })
            end
        elseif _actualDefense == 1 then
            if side == veafCasMission.SIDE_BLUE then
                table.insert(group.units, { "Vulcan", random=true })
            else
                table.insert(group.units, { veaf.randomlyChooseFrom({"Ural-375 ZU-23", "ZSU_57_2"}), random=true })
            end
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("group.units=%s", veaf.p(group.units)))
end

--- Generates an air defense group
function veafCasMission.generateAirDefenseGroup(groupName, defense, side)
    side = side or veafCasMission.SIDE_RED
    
    -- generate a primary air defense platoon
    local _actualDefense = defense
    if defense > 0 then
        -- roll a dice : 20% chance to get a -1 (lower) difficulty, 30% chance to get a +1 (higher) difficulty, and 50% to get what was asked for
        local _dice = math.random(100)
-- LOGGING DISABLED WHEN COMPILING("_dice = " .. _dice)
        if _dice <= 20 then
            _actualDefense = defense - 1
        elseif _dice > 80 then
            _actualDefense = defense + 1
        end
    end
    if _actualDefense > 5 then _actualDefense = 5 end
    if _actualDefense < 0 then _actualDefense = 0 end
-- LOGGING DISABLED WHEN COMPILING("_actualDefense = " .. _actualDefense)
    local _groupDefinition = "generateAirDefenseGroup-BLUE-"
    if side == veafCasMission.SIDE_RED then
        _groupDefinition = "generateAirDefenseGroup-RED-"
    end
    _groupDefinition = _groupDefinition .. tostring(_actualDefense)
-- LOGGING DISABLED WHEN COMPILING("_groupDefinition = " .. _groupDefinition)

    local group = veafUnits.findGroup(_groupDefinition)
    if not group then
        veaf.loggers.get(veafCasMission.Id):error(string.format("veafCasMission.generateAirDefenseGroup cannot find group [%s]", _groupDefinition or ""))
    end
    group.description = groupName
    group.groupName = groupName
    
-- LOGGING DISABLED WHEN COMPILING("#group.units = " .. #group.units)
    return group
end

--- Generates a transport company and its air defenses
function veafCasMission.generateTransportCompany(groupName, defense, side, size)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCasMission.generateTransportCompany(groupName=[%s], defense=[%s], side=[%s], size=[%s])", groupName or "", defense  or "", side or "", size or ""))
    side = side or veafCasMission.SIDE_RED
    local groupCount = math.floor((size or math.random(10, 15)))
-- LOGGING DISABLED WHEN COMPILING(string.format("groupCount=%s", tostring(groupCount)))
    local group = {
            disposition = { h = groupCount, w = groupCount},
            units = {},
            description = groupName,
            groupName = groupName,
        }
    -- generate a transport company
    local transportType
  
    for _ = 1, groupCount do
        if veaf.config.ww2 then
            if side == veafCasMission.SIDE_BLUE then
                transportType = veaf.randomlyChooseFrom({"Bedford_MWD", "CCKW_353", "Willys_MB"})
            else
                transportType = veaf.randomlyChooseFrom({"Blitz_36-6700A", "Horch_901_typ_40_kfz_21", "Kubelwagen_82", "Sd_Kfz_7", "Sd_Kfz_2" })
            end
        else
            if side == veafCasMission.SIDE_BLUE then
                transportType = veaf.randomlyChooseFrom({"LUV HMMWV Jeep", "M 818", "M978 HEMTT Tanker", "Land_Rover_101_FC", "Land_Rover_109_S3"})
            else
                transportType = veaf.randomlyChooseFrom({"ATZ-60_Maz", "ZIL-135", "ATZ-5", 'Ural-4320 APA-5D', 'SKP-11', 'GAZ-66', 'KAMAZ Truck', 'Ural-375', "KrAZ6322", 'ZIL-131 KUNG', "Tigr_233036", "UAZ-469"})
            end
        end
        table.insert(group.units, { transportType, random=true})
    end

    -- add an air defense vehicle every 10 vehicles
    local nbDefense = groupCount / 10 + 1
    if nbDefense == 0 then
        nbDefense = 1
    end
-- LOGGING DISABLED WHEN COMPILING("nbDefense = " .. nbDefense)
    if not veaf.config.ww2 then
        _addDefenseForGroups(group, side, defense, nbDefense)
    else
        -- nothing, there are no mobile defense units in WW2
    end

    return group
end

--- Generates an armor platoon and its air defenses
function veafCasMission.generateArmorPlatoon(groupName, defense, armor, side, size)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCasMission.generateArmorPlatoon(groupName=[%s], defense=[%s], armor=[%s], side=[%s], size=[%s])", groupName or "", defense  or "", armor or "", side or "", size or ""))
    side = side or veafCasMission.SIDE_RED
    
    -- generate an armor platoon
    local groupCount = math.floor((size or math.random(3, 6)) * (math.random(8, 12)/10))
-- LOGGING DISABLED WHEN COMPILING(string.format("groupCount=%s", tostring(groupCount)))
    local group = {
            disposition = { h = groupCount, w = groupCount},
            units = {},
            description = groupName,
            groupName = groupName,
        }
    if group.disposition.h < 4 then 
        group.disposition.h = 4
        group.disposition.w = 4
    end
    local armorType
    local armorRand
    for _ = 1, groupCount do
        if armor <= 2 then
            if veaf.config.ww2 then
                if side == veafCasMission.SIDE_BLUE then
                    armorType = veaf.randomlyChooseFrom({"M30_CC", "M10_GMC"})
                else
                    armorType = veaf.randomlyChooseFrom({"Sd_Kfz_251", "Sd_Kfz_234_2_Puma"})
                end
            else
                if side == veafCasMission.SIDE_BLUE then
                    armorType = veaf.randomlyChooseFrom({'IFV Marder', 'MCV-80', 'IFV LAV-25', "M1134 Stryker ATGM", 'M-2 Bradley'})
                else
                    armorType = veaf.randomlyChooseFrom({"BTR-82A", 'BMP-1', 'BMP-1', "VAB_Mephisto", 'BMP-2'})
                end
            end
        elseif armor == 3 then
            if veaf.config.ww2 then
                if side == veafCasMission.SIDE_BLUE then
                    armorType = veaf.randomlyChooseFrom({"M30_CC", "M10_GMC", "Centaur_IV",})
                else
                    armorType = veaf.randomlyChooseFrom({"Sd_Kfz_251", "Sd_Kfz_234_2_Puma", "Elefant_SdKfz_184"})
                end
            else
                if side == veafCasMission.SIDE_BLUE then
                    armorType = veaf.randomlyChooseFrom({'IFV Marder', "VAB_Mephisto", "M-2 Bradley", 'MBT Leopard 1A3', "Chieftain_mk3"})
                else
                    armorType = veaf.randomlyChooseFrom({"BTR-82A", "VAB_Mephisto", 'BMP-2', 'T-55', "Chieftain_mk3"})
                end
            end
        elseif armor == 4 then
            if veaf.config.ww2 then
                if side == veafCasMission.SIDE_BLUE then
                    armorType = veaf.randomlyChooseFrom({"Centaur_IV", "Churchill_VII", "Cromwell_IV"})
                else
                    armorType = veaf.randomlyChooseFrom({"Pz_IV_H", "Tiger_I", "Tiger_II_H","Stug_III","Stug_IV"})
                end
            else
                if side == veafCasMission.SIDE_BLUE then
                    armorType = veaf.randomlyChooseFrom({'M-2 Bradley', 'MBT Leopard 1A3', "Merkava_Mk4", "M1128 Stryker MGS"})
                else
                    armorType = veaf.randomlyChooseFrom({"BTR-82A", "BMP-3", "Chieftain_mk3", 'T-72B'})
                end
            end
        elseif armor >= 5 then
            if veaf.config.ww2 then
                if side == veafCasMission.SIDE_BLUE then
                    armorType = veaf.randomlyChooseFrom({"Centaur_IV", "Churchill_VII", "Cromwell_IV", "M4_Sherman", "M4A4_Sherman_FF"}, armor-5)
                else
                    armorType = veaf.randomlyChooseFrom({"Pz_IV_H", "Tiger_I", "Tiger_II_H","Stug_III","Stug_IV", "JagdPz_IV", "Jagdpanther_G1", "Pz_V_Panther_G"}, armor-5)
                end
            else
                if side == veafCasMission.SIDE_BLUE then
                    armorType = veaf.randomlyChooseFrom({"Merkava_Mk4", "Challenger2", "Leclerc", "Leopard-2", 'M-1 Abrams'}, armor-5)
                else
                    armorType = veaf.randomlyChooseFrom({"BMP-3", "ZTZ96B", 'T-72B3', 'T-80UD', 'T-90'}, armor-5)
                end
            end
        end
        table.insert(group.units, { armorType, random=true })
    end

    -- add air defense vehicles
    if not veaf.config.ww2 then
        _addDefenseForGroups(group, side, defense, 1)
    else
        -- nothing, there are no mobile defense units in WW2
    end

    return group
end

--- Generates an infantry group along with its manpad units and tranport vehicles
function veafCasMission.generateInfantryGroup(groupName, defense, armor, side, size)
    side = side or veafCasMission.SIDE_RED
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCasMission.generateInfantryGroup(groupName=%s, defense=%d, armor=%d)",groupName, defense, armor))
    -- generate an infantry group
    local groupCount = math.floor((size or math.random(3, 6)) * (math.random(8, 12)/10))
-- LOGGING DISABLED WHEN COMPILING(string.format("groupCount=%s", tostring(groupCount)))
    local group = {
            disposition = { h = groupCount, w = groupCount},
            units = {},
            description = groupName,
            groupName = groupName,
        }
    if group.disposition.h < 4 then 
        group.disposition.h = 4
        group.disposition.w = 4
    end
    for _ = 1, groupCount do
        local rand = math.random(3)
        local unitType = nil
        if rand == 1 then
            if side == veafCasMission.SIDE_BLUE then
                unitType = 'Soldier RPG'
            else
                unitType = "Paratrooper RPG-16"
            end
        elseif rand == 2 then
            if side == veafCasMission.SIDE_BLUE then
                unitType = "Soldier M249"
            else
                unitType = "Infantry AK ver3"
            end
        else
            if side == veafCasMission.SIDE_BLUE then
                unitType = "Soldier M4 GRG"
            else
                unitType = "Infantry AK ver2"
            end
        end
        table.insert(group.units, { unitType })
    end

    -- add a transport vehicle or an APC/IFV
    if armor > 3 then
        if side == veafCasMission.SIDE_BLUE then
            table.insert(group.units, { "M-2 Bradley", cell=11, random=true })
        else
            table.insert(group.units, { "BMP-2", cell=11, random=true })
        end
    elseif armor > 0 then
        if side == veafCasMission.SIDE_BLUE then
            table.insert(group.units, { "IFV Marder", cell=11, random=true })
        else
            table.insert(group.units, { "BTR-82A", cell=11, random=true })
        end
    else
        if side == veafCasMission.SIDE_BLUE then
            table.insert(group.units, { "M 818", cell=11, random=true })
        else
            table.insert(group.units, { "KAMAZ Truck", cell=11, random=true })
        end
    end

    -- add air defense
    if not veaf.config.ww2 then
        _addDefenseForGroups(group, side, defense, 1, true)
    else
        -- nothing, there are no mobile defense units in WW2
    end

    return group
end

function veafCasMission.placeGroup(groupDefinition, spawnPosition, spacing, resultTable, hasDest)
    if spawnPosition ~= nil and groupDefinition ~= nil then
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCasMission.placeGroup(#groupDefinition.units=%d)",#groupDefinition.units))

        -- process the group 
-- LOGGING DISABLED WHEN COMPILING("process the group")
        local group = veafUnits.processGroup(groupDefinition)
        
        -- place its units
        local groupPosition = { x = spawnPosition.x, z = spawnPosition.y }
        local hdg = math.random(359)
        local group, cells = veafUnits.placeGroup(group, veaf.placePointOnLand(groupPosition), spacing+3, hdg, hasDest)
        if veaf.Trace then 
            veafUnits.traceGroup(group, cells)
        end
        
        -- add the units to the result units list
        if not resultTable then 
            resultTable = {}
        end
        for _,u in pairs(group.units) do
            table.insert(resultTable, u)
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("#resultTable=%d",#resultTable))
    return resultTable
end

--- Generates a complete CAS target group
function veafCasMission.generateCasGroup(casGroupName, spawnSpot, size, defense, armor, spacing, side)
-- LOGGING DISABLED WHEN COMPILING("side = " .. tostring(side))
    side = side or veafCasMission.SIDE_RED
    local units = {}
    local zoneRadius = (size+spacing)*350
-- LOGGING DISABLED WHEN COMPILING("zoneRadius = " .. zoneRadius)
    
    -- generate between size-2 and size+1 infantry groups
    local infantryGroupsCount = math.random(math.max(1, size-2), size + 1)
-- LOGGING DISABLED WHEN COMPILING("infantryGroupsCount = " .. infantryGroupsCount)
    for infantryGroupNumber = 1, infantryGroupsCount do
        local groupName = casGroupName .. " - Infantry Section " .. infantryGroupNumber
        local groupPosition = veaf.findPointInZone(spawnSpot, zoneRadius, false)
-- LOGGING DISABLED WHEN COMPILING(string.format("infantry group #%s position : %s", veaf.p(infantryGroupNumber), veaf.p(groupPosition)))
        local group = veafCasMission.generateInfantryGroup(groupName, defense, armor, side)
        veafCasMission.placeGroup(group, groupPosition, spacing, units)
    end

    if armor > 0 then
        -- generate between size-2 and size+1 armor platoons
        local armorPlatoonsCount = math.random(math.max(1, size-2), size + 1)
-- LOGGING DISABLED WHEN COMPILING("armorPlatoonsCount = " .. armorPlatoonsCount)
        for armorGroupNumber = 1, armorPlatoonsCount do
            local groupName = casGroupName .. " - Armor Platoon " .. armorGroupNumber
            local groupPosition = veaf.findPointInZone(spawnSpot, zoneRadius, false)
-- LOGGING DISABLED WHEN COMPILING(string.format("armor group #%s position : %s", veaf.p(armorGroupNumber), veaf.p(groupPosition)))
            local group = veafCasMission.generateArmorPlatoon(groupName, defense, armor, side)
            veafCasMission.placeGroup(group, groupPosition, spacing, units)
        end
    end

    if defense > 0 then
        -- generate between 1 and 2 air defense groups
        local airDefenseGroupsCount = 1
        if defense > 3 then
            airDefenseGroupsCount = 2
        end
-- LOGGING DISABLED WHEN COMPILING("airDefenseGroupsCount = " .. airDefenseGroupsCount)
        for airDefenseGroupNumber = 1, airDefenseGroupsCount do
            local groupName = casGroupName .. " - Air Defense Group ".. airDefenseGroupNumber
            local groupPosition = veaf.findPointInZone(spawnSpot, zoneRadius, false)
-- LOGGING DISABLED WHEN COMPILING(string.format("air defense group #%s position : %s", veaf.p(airDefenseGroupNumber), veaf.p(groupPosition)))
            local group = veafCasMission.generateAirDefenseGroup(groupName, defense, side)
            veafCasMission.placeGroup(group, groupPosition, spacing, units)
        end
    end

    -- generate between 1 and size transport companies
    local transportCompaniesCount = math.random(1, size)
-- LOGGING DISABLED WHEN COMPILING("transportCompaniesCount = " .. transportCompaniesCount)
    for transportCompanyGroupNumber = 1, transportCompaniesCount do
        local groupName = casGroupName .. " - Transport Company " .. transportCompanyGroupNumber
        local groupPosition = veaf.findPointInZone(spawnSpot, zoneRadius, false)
-- LOGGING DISABLED WHEN COMPILING(string.format("transport group #%s position : %s", veaf.p(transportCompanyGroupNumber), veaf.p(groupPosition)))
        local group = veafCasMission.generateTransportCompany(groupName, defense, side)
        veafCasMission.placeGroup(group, groupPosition, spacing, units)
    end

    return units
end

--- Generates a CAS mission
function veafCasMission.generateCasMission(spawnSpot, size, defense, armor, spacing, disperseOnAttack, side)
    if veafCasMission.groupAliveCheckTaskID ~= 'none' then
        trigger.action.outText("A CAS target group already exists !", 15)
        return
    end
    if side == veafCasMission.SIDE_BLUE then
        veafCasMission.casGroupName = veafCasMission.BlueCasGroupName
    end
    local country = veaf.getCountryForCoalition(side)
    local units = veafCasMission.generateCasGroup(veafCasMission.casGroupName, spawnSpot, size, defense, armor, spacing, side)

    -- prepare the actual DCS units
    local dcsUnits = {}
    for i=1, #units do
        local unit = units[i]
        local unitType = unit.typeName
        local unitName = veafCasMission.casGroupName .. " / " .. unit.displayName .. " #" .. i
        local unitHdg = unit.hdg
        
        local spawnPosition = unit.spawnPoint
        
        -- check if position is correct for the unit type
        if veafUnits.checkPositionForUnit(spawnPosition, unit) then
            local toInsert = {
                    ["x"] = spawnPosition.x,
                    ["y"] = spawnPosition.z,
                    ["alt"] = spawnPosition.y,
                    ["type"] = unitType,
                    ["name"] = unitName,
                    ["speed"] = 0,
                    ["skill"] = "Random",
                    ["heading"] = unitHdg,
            }
            table.insert(dcsUnits, toInsert)
        end
    end

    -- actually spawn groups
    mist.dynAdd({country = country, category = "GROUND_UNIT", name = veafCasMission.casGroupName, hidden = false, units = dcsUnits})

    -- set AI options
    local controller = Group.getByName(veafCasMission.casGroupName):getController()
    controller:setOption(9, 2) -- set alarm state to red
    controller:setOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, disperseOnAttack) -- set disperse on attack according to the option

    -- Spawn Reaper
    local opposing_side = coalition.side.BLUE
    if coalition.side.RED ~= side then
        opposing_side = coalition.side.RED
    end

    local avgPos = veaf.getAveragePosition(veafCasMission.casGroupName)
    veafCasMission.afacName = veafSpawn.spawnAFAC(avgPos, "mq9", veaf.getCountryForCoalition(opposing_side), nil, nil, nil, veafSpawn.convertLaserToFreq(1688), "FM", 1688, true, false, false)

    -- build menu for each player
    veafRadio.addCommandToSubmenu('Target information', veafCasMission.rootPath, veafCasMission.reportTargetInformation, nil, veafRadio.USAGE_ForGroup)

    -- add radio menus for commands
    veafRadio.addSecuredCommandToSubmenu('Skip current objective', veafCasMission.rootPath, veafCasMission.skipCasTarget)
    veafCasMission.targetMarkersPath = veafRadio.addSubMenu("Target markers", veafCasMission.rootPath)
    veafRadio.addCommandToSubmenu('Request smoke on target area', veafCasMission.targetMarkersPath, veafCasMission.smokeCasTargetGroup)
    veafRadio.addCommandToSubmenu('Request illumination flare over target area', veafCasMission.targetMarkersPath, veafCasMission.flareCasTargetGroup)

    local nbVehicles, nbInfantry = veafUnits.countInfantryAndVehicles(veafCasMission.casGroupName)
    local message = "TARGET: Group of " .. nbVehicles .. " vehicles and " .. nbInfantry .. " soldiers. See F10 radio menu for details\n"
    trigger.action.outText(message,5)

    veafRadio.refreshRadioMenu()

    -- start checking for targets destruction
    veafCasMission.casGroupWatchdog()
end

-- Ask a report
-- @param int groupId
function veafCasMission.reportTargetInformation(unitName)
    -- generate information dispatch
    local nbVehicles, nbInfantry = veafUnits.countInfantryAndVehicles(veafCasMission.casGroupName)

    local message = "TARGET: Group of " .. nbVehicles .. " vehicles and " .. nbInfantry .. " soldiers.\n"

    if veafCasMission.afacName then
        message = message .. "AFAC on station: " .. veafCasMission.afacName .. "\n"
    end

    message = message .. "\n"

    -- add coordinates and position from bullseye
    local averageGroupPosition = veaf.getAveragePosition(veafCasMission.casGroupName)
    local lat, lon = coord.LOtoLL(averageGroupPosition)
    local mgrsString = mist.tostringMGRS(coord.LLtoMGRS(lat, lon), 3)
    local bullseye = mist.utils.makeVec3(mist.DBs.missionData.bullseye.blue, 0)
    local vec = {x = averageGroupPosition.x - bullseye.x, y = averageGroupPosition.y - bullseye.y, z = averageGroupPosition.z - bullseye.z}
    local dir = mist.utils.round(mist.utils.toDegree(mist.utils.getDir(vec, bullseye)), 0)
    local dist = mist.utils.get2DDist(averageGroupPosition, bullseye)
    local distMetric = mist.utils.round(dist/1000, 0)
    local distImperial = mist.utils.round(mist.utils.metersToNM(dist), 0)
    local fromBullseye = string.format('%03d', dir) .. ' for ' .. distMetric .. 'km /' .. distImperial .. 'nm'

    message = message .. "LAT LON (decimal): " .. mist.tostringLL(lat, lon, 2) .. ".\n"
    message = message .. "LAT LON (DMS)    : " .. mist.tostringLL(lat, lon, 0, true) .. ".\n"
    message = message .. "MGRS/UTM         : " .. mgrsString .. ".\n"
    message = message .. "FROM BULLSEYE    : " .. fromBullseye .. ".\n"
    message = message .. "\n"

    message = message .. veaf.weatherReport(averageGroupPosition, nil, true)

    -- send message only for the unit
    veaf.outTextForGroup(unitName, message, 30)
end

--- add a smoke marker over the target area
function veafCasMission.smokeCasTargetGroup()
-- LOGGING DISABLED WHEN COMPILING("veafCasMission.smokeCasTargetGroup START")
    veafSpawn.spawnSmoke(veaf.getAveragePosition(veafCasMission.casGroupName), trigger.smokeColor.Red)
    trigger.action.outText('Copy smoke requested, RED smoke on the deck!',5)
    veafRadio.delCommand(veafCasMission.targetMarkersPath, 'Request smoke on target area')
    veafRadio.addCommandToSubmenu('Target is marked with red smoke', veafCasMission.targetMarkersPath, veaf.emptyFunction)
    veafCasMission.smokeResetTaskID = mist.scheduleFunction(veafCasMission.smokeReset,{},timer.getTime()+veafCasMission.SecondsBetweenSmokeRequests)
    veafRadio.refreshRadioMenu()
end

--- Reset the smoke request radio menu
function veafCasMission.smokeReset()
    veafRadio.delCommand(veafCasMission.targetMarkersPath, 'Target is marked with red smoke')
    veafRadio.addCommandToSubmenu('Request smoke on target area', veafCasMission.targetMarkersPath, veafCasMission.smokeCasTargetGroup)
    trigger.action.outText('Smoke marker available',5)
    veafRadio.refreshRadioMenu()
end

--- add an illumination flare over the target area
function veafCasMission.flareCasTargetGroup()
    veafSpawn.spawnIlluminationFlare(veaf.getAveragePosition(veafCasMission.casGroupName))
	trigger.action.outText('Copy illumination flare requested, illumination flare over target area!',5)
    veafRadio.delCommand(veafCasMission.targetMarkersPath, 'Request illumination flare over target area')
    veafRadio.addCommandToSubmenu('Target area is marked with illumination flare', veafCasMission.targetMarkersPath, veaf.emptyFunction)
    veafCasMission.flareResetTaskID = mist.scheduleFunction(veafCasMission.flareReset,{},timer.getTime()+veafCasMission.SecondsBetweenFlareRequests)
    veafRadio.refreshRadioMenu()
end

--- Reset the flare request radio menu
function veafCasMission.flareReset()
    veafRadio.delCommand(veafCasMission.targetMarkersPath, 'Target area is marked with illumination flare')
    veafRadio.addCommandToSubmenu('Request illumination flare over target area', veafCasMission.targetMarkersPath, veafCasMission.flareCasTargetGroup)
    trigger.action.outText('Target illumination available',5)
    veafRadio.refreshRadioMenu()
end

--- Checks if the vehicles group is still alive, and if not announces the end of the CAS mission
function veafCasMission.casGroupWatchdog() 
    local nbVehicles, nbInfantry = veafUnits.countInfantryAndVehicles(veafCasMission.casGroupName)
    if nbVehicles > 0 then
-- LOGGING DISABLED WHEN COMPILING("Group is still alive with "..nbVehicles.." vehicles and "..nbInfantry.." soldiers")
        veafCasMission.groupAliveCheckTaskID = mist.scheduleFunction(veafCasMission.casGroupWatchdog,{},timer.getTime()+veafCasMission.SecondsBetweenWatchdogChecks)
    else
        trigger.action.outText("CAS objective group destroyed!", 5)
        veafCasMission.cleanupAfterMission()
    end
end

--- Called from the "Skip target" radio menu : remove the current CAS target group
function veafCasMission.skipCasTarget()
    veafCasMission.cleanupAfterMission()
    trigger.action.outText("CAS objective group cleaned up.", 5)
end

--- Cleanup after either mission is ended or aborted
function veafCasMission.cleanupAfterMission()
-- LOGGING DISABLED WHEN COMPILING("skipCasTarget START")

    -- destroy vehicles and infantry groups
-- LOGGING DISABLED WHEN COMPILING("destroy CAS group")
    local group = Group.getByName(veafCasMission.casGroupName)
    if group and group:isExist() == true then
        group:destroy()
    end
-- LOGGING DISABLED WHEN COMPILING("destroy AFAC group")
    group = Group.getByName(veafCasMission.afacName)
    if group and group:isExist() == true then
        group:destroy()
    end
    veafCasMission.afacName = nil

    -- remove the watchdog function
-- LOGGING DISABLED WHEN COMPILING("remove the watchdog function")
    if veafCasMission.groupAliveCheckTaskID ~= 'none' then
        mist.removeFunction(veafCasMission.groupAliveCheckTaskID)
    end
    veafCasMission.groupAliveCheckTaskID = 'none'

    
-- LOGGING DISABLED WHEN COMPILING("update the radio menu 1")
    veafRadio.delCommand(veafCasMission.rootPath, 'Target information')

-- LOGGING DISABLED WHEN COMPILING("update the radio menu 2")
    veafRadio.delCommand(veafCasMission.rootPath, 'Skip current objective')
-- LOGGING DISABLED WHEN COMPILING("update the radio menu 3")
    veafRadio.delCommand(veafCasMission.rootPath, 'Get current objective situation')
-- LOGGING DISABLED WHEN COMPILING("update the radio menu 4")
    veafRadio.delSubmenu(veafCasMission.targetMarkersPath, veafCasMission.rootPath)

    veafRadio.refreshRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("skipCasTarget DONE")

end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Build the initial radio menu
function veafCasMission.buildRadioMenu()
    veafCasMission.rootPath = veafRadio.addSubMenu(veafCasMission.RadioMenuName)
    if not(veafRadio.skipHelpMenus) then
        veafRadio.addCommandToSubmenu("HELP", veafCasMission.rootPath, veafCasMission.help, nil, veafRadio.USAGE_ForGroup)
    end
end

function veafCasMission.help(unitName)
    local text =
        'Create a marker and type "_cas" in the text\n' ..
        'This will create a default CAS target group\n' ..
        'You can add options (comma separated) :\n' ..
        '   "defense 0" completely disables air defenses\n' ..
        '   "defense [1-5]" specifies air defense cover (1 = light, 5 = heavy)\n' ..
        '   "size [1-5]" changes the group size (1 = small, 5 = huge)\n' ..
        '   "armor [1-5]" specifies armor presence (1 = light, 5 = heavy)\n' ..
        '   "spacing [1-5]" changes the groups spacing (1 = dense, 3 = default, 5 = sparse)'

        veaf.outTextForGroup(unitName, text, 30)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafCasMission.initialize()
    veafCasMission.buildRadioMenu()
    veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafCasMission.onEventMarkChange)
end

veaf.loggers.get(veafCasMission.Id):info(string.format("Loading version %s", veafCasMission.Version))

--- Enable/Disable error boxes displayed on screen.
env.setErrorMessageBoxEnabled(false)




------------------ END script veafCasMission.lua  ------------------


------------------ START script veafCombatMission.lua  ------------------

------------------------------------------------------------------
-- VEAF combat mission functions for DCS World
-- By zip (2020)
--
-- Features:
-- ---------
-- * A combat mission consists in spawning enemy aircrafts
-- * It also contains a mass briefing, optional objectives (timed, number of kills, ...) and can trigger the activation of one or more combat zones
-- * For each mission, a specific radio sub-menu is created, allowing common actions (get mission status, weather, briefing, start and stop the mission, etc.)
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafCombatMission = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafCombatMission.Id = "COMBATMISSION"

--- Version.
veafCombatMission.Version = "2.2.0"

-- trace level, specific to this module
--veafCombatMission.LogLevel = "trace"

veaf.loggers.new(veafCombatMission.Id, veafCombatMission.LogLevel)

--- Number of seconds between each check of the watchdog function
veafCombatMission.SecondsBetweenWatchdogChecks = 30

veafCombatMission.RadioMenuName = "MISSIONS"

veafCombatMission.MinimumSpacingBetweenClones = 300 -- minimum spawn distance between clones of a group

veafCombatMission.RemoteCommandParser = "([[a-zA-Z0-9]+)%s?([^%s]*)%s?(.*)"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Radio menus paths
veafCombatMission.rootPath = nil

-- Missions list (table of VeafCombatMission objects)
veafCombatMission.missionsList = {}

-- Missions dictionary (map of VeafCombatMission objects by mission name)
veafCombatMission.missionsDict = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafCombatMissionObjective object
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafCombatMissionObjective = {}

VeafCombatMissionObjective.FAILED = -1
VeafCombatMissionObjective.SUCCESS = 1
VeafCombatMissionObjective.NOTHING = 0

function VeafCombatMissionObjective:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object

    -- technical name
    objectToCreate.name = nil
    -- description for the briefing
    objectToCreate.description = nil
    -- message when the objective is completed
    objectToCreate.message = nil
    -- parameters
    objectToCreate.parameters = {}
    -- function that is call when the mission starts
    objectToCreate.onStartupFunction = nil
    -- function that is called when the completion check watchdog runs (should check for objective completion and return one of the FAILED, SUCCESS or NOTHING constants)
    objectToCreate.onCheckFunction = nil

    return objectToCreate
end

function VeafCombatMissionObjective:copy()
    local copy = VeafCombatMissionObjective:new()

    -- copy the attributes
    copy.name = self.name
    copy.description = self.description
    copy.onStartupFunction = self.onStartupFunction
    copy.onCheckFunction = self.onCheckFunction

    -- deep copy the collections
    copy.parameters = {}
    for name, value in pairs(self.parameters) do
-- LOGGING DISABLED WHEN COMPILING(string.format("copying parameter %s : ",tostring(name)))
        copy.parameters[name]=value
    end

    return copy
end

---
--- setters and getters
---

function VeafCombatMissionObjective:setName(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective.setName([%s])",value or ""))
    self.name = value
    return self
end

function VeafCombatMissionObjective:getName()
    return self.name
end

function VeafCombatMissionObjective:setDescription(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].setDescription([%s])", self:getName() or "", value or ""))
    self.description = value
    return self
end

function VeafCombatMissionObjective:getDescription()
    return self.description
end

function VeafCombatMissionObjective:setMessage(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].setMessage([%s])", self:getName() or "", value or ""))
    self.message = value
    return self
end

function VeafCombatMissionObjective:getMessage()
    return self.message
end

function VeafCombatMissionObjective:setParameters(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].setParameters([%s])", self:getName() or "", veaf.p(value or "")))
    self.parameters = value
    return self
end

function VeafCombatMissionObjective:getParameters()
    return self.parameters
end

function VeafCombatMissionObjective:setOnCheck(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].setOnCheck(some function)",self:getName()))
    self.onCheckFunction = value
    return self
end

function VeafCombatMissionObjective:getOnCheck()
    return self.onCheckFunction
end

function VeafCombatMissionObjective:setOnStartup(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].setOnStartup(some function)", self:getName()))
    self.onStartupFunction = value
    return self
end

function VeafCombatMissionObjective:getOnStartup()
    return self.onStartupFunction
end

---
--- other methods
---

function VeafCombatMissionObjective:onCheck(mission)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].onCheck([%s])", self:getName() or "", mission:getName() or ""))
    if self.onCheckFunction then
        return self.onCheckFunction(mission, self.parameters)
    else
        return VeafCombatMissionObjective.NOTHING
    end
end

function VeafCombatMissionObjective:onStartup(mission)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].onStartup([%s])", self:getName() or "", mission:getName() or ""))
    if self.onStartupFunction then
        return self.onStartupFunction(self.parameters)
    end
end

function VeafCombatMissionObjective:configureAsTimedObjective(timeInSeconds)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].configureAsTimedObjective()",self:getName()))

    local function onCheck(mission, parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective.NewTimedObjective.onCheck()"))
        local timeout = parameters.timeout
        local startTime = parameters.startTime
        if timer.getTime() > startTime + timeout then
            return VeafCombatMissionObjective.FAILED
        else
            return VeafCombatMissionObjective.NOTHING
        end
    end

    return self
        :setParameters({timeout=timeInSeconds})
        :setOnStartup(
            function(parameters)
                parameters["startTime"] = timer.getTime()
            end
        )
        :setOnCheck(onCheck)
end

function VeafCombatMissionObjective:configureAsKillEnemiesObjective(nbKillsToWin, whatsInAKill)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].configureAsKillEnemiesObjective()",self:getName()))

    local function onCheck(mission, parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective.configureAsKillEnemiesObjective.onCheck()"))
        if mission:isActive() then
            local nbKillsToWin = parameters.nbKillsToWin
            local whatsInAKill = parameters.whatsInAKill
-- LOGGING DISABLED WHEN COMPILING(string.format("nbKillsToWin = %d",nbKillsToWin))
-- LOGGING DISABLED WHEN COMPILING(string.format("whatsInAKill = %d",whatsInAKill))

            local nbLiveUnits, nbDamagedUnits, nbDeadUnits = mission:getRemainingEnemies(whatsInAKill)

-- LOGGING DISABLED WHEN COMPILING(string.format("nbLiveUnits = %d",nbLiveUnits))
-- LOGGING DISABLED WHEN COMPILING(string.format("nbDamagedUnits = %d",nbDamagedUnits))
-- LOGGING DISABLED WHEN COMPILING(string.format("nbDeadUnits = %d",nbDeadUnits))

            if (nbKillsToWin == -1 and nbLiveUnits == 0) or (nbKillsToWin >= 0 and nbDeadUnits >= nbKillsToWin) then
                -- objective is achieved
-- LOGGING DISABLED WHEN COMPILING(string.format("objective is achieved"))
                local msg = string.format(self:getMessage(), nbDeadUnits)
                if not mission:isSilent() then
                    trigger.action.outText(msg, 15)
                end
                return VeafCombatMissionObjective.SUCCESS
            else
-- LOGGING DISABLED WHEN COMPILING(string.format("objective is NOT achieved"))
                return VeafCombatMissionObjective.NOTHING
            end
        end
        return VeafCombatMissionObjective.NOTHING
    end

    return self
            :setParameters({nbKillsToWin=nbKillsToWin or -1, whatsInAKill=whatsInAKill or 0})
            :setOnCheck(onCheck)
end

function VeafCombatMissionObjective:configureAsPreventDestructionOfSceneryObjectsInZone(zones, objects)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective[%s].configureAsPreventDestructionOfSceneryObjectsInZone()",self:getName()))

    local function onCheck(mission, parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionObjective.configureAsPreventDestructionOfSceneryObjectsInZone.onCheck()"))
        if mission:isActive() then

            local zones = parameters.zones
            local objects = parameters.objects
            local failed = false
            local killedObjectsNames = nil

            local killedObjects = mist.getDeadMapObjsInZones(zones)
-- LOGGING DISABLED WHEN COMPILING(veaf.serialize("killedObjects", killedObjects))

            for _, object in pairs(killedObjects) do
-- LOGGING DISABLED WHEN COMPILING(string.format("checking id_ = [%s]", object.object.id_))
                if objects[object.object.id_] then
-- LOGGING DISABLED WHEN COMPILING(string.format("found [%s]", objects[object.object.id_]))
                    if killedObjectsNames then
                        killedObjectsNames = killedObjectsNames .. ", " .. objects[object.object.id_]
                    else
                        killedObjectsNames = objects[object.object.id_]
                    end
                    failed = true
                end
            end

            if failed then
                -- objective is failed
-- LOGGING DISABLED WHEN COMPILING(string.format("objective is failed"))
                local msg = string.format(self:getMessage(), killedObjectsNames)
                if not mission:isSilent() then
                    trigger.action.outText(msg, 15)
                end
                return VeafCombatMissionObjective.FAILED
            else
-- LOGGING DISABLED WHEN COMPILING(string.format("objective is NOT failed"))
                return VeafCombatMissionObjective.NOTHING
            end
        end
        return VeafCombatMissionObjective.NOTHING
    end

    return self
            :setParameters({zones=zones, objects=objects})
            :setOnCheck(onCheck)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafCombatMissionElement object
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafCombatMissionElement = {}

function VeafCombatMissionElement:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object

    -- name
    objectToCreate.name = nil
    -- groups : a list of group names that compose this element
    objectToCreate.groups = {}
    -- skill ("Average", "Good", "High", "Excellent" or "Random"), defaults to "Random"
    objectToCreate.skill = "Random" -- SPAWN:InitSkill(Skill)
    -- spawn radius in meters (randomness introduced in the respawn mechanism)
    objectToCreate.spawnRadius = 0 -- SPAWN:InitRandomizePosition
    -- spawn chance in percent (xx chances in 100 that the unit is spawned - or the command run)
    objectToCreate.spawnChance = 100
    -- the element can be multiplied to scale the mission
    objectToCreate.scale = 1
    -- if true, the element is scalable
    objectToCreate.scalable = true
    -- init tables (no need to init simple values)

    return objectToCreate
end

function VeafCombatMissionElement:copy()
    local copy = VeafCombatMissionElement:new()

    -- copy the attributes
    copy.name = self.name
    copy.skill = self.skill
    copy.spawnRadius = self.spawnRadius
    copy.spawnChance = self.spawnChance
    copy.scale = self.scale
    copy.scalable = self.scalable

    -- deep copy the collections
    copy.groups = {}
    for _, group in pairs(self.groups) do
        table.insert(copy.groups, group)
    end
    copy.spawnPoints = {}
    for groupName, spawnPoint in pairs(self.spawnPoints) do
        copy.spawnPoints[groupName] = spawnPoint
    end

    return copy
end

---
--- setters and getters
---

function VeafCombatMissionElement:setName(value)
    self.name = value
    return self
end

function VeafCombatMissionElement:getName()
    return self.name
end

function VeafCombatMissionElement:setGroups(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMissionElement[%s]:setGroups(%s)",veaf.p(self.name), veaf.p(value)))
    self.groups = value
    self.spawnPoints = {}
    for _, groupName in pairs(self.groups) do
-- LOGGING DISABLED WHEN COMPILING(string.format("processing groupName=%s",veaf.p(groupName)))
        local _group = Group.getByName(groupName)
-- LOGGING DISABLED WHEN COMPILING(string.format("_group=%s",veaf.p(_group)))
        if _group then
            local _unit1 = _group:getUnit(1)
-- LOGGING DISABLED WHEN COMPILING(string.format("_unit1=%s",veaf.p(_unit1)))
            if _unit1 then
-- LOGGING DISABLED WHEN COMPILING(string.format("_unit1:getPoint()=%s",veaf.p(_unit1:getPoint())))
                self.spawnPoints[groupName] = _unit1:getPoint()
            end
        end
    end
    return self
end

function VeafCombatMissionElement:getGroups()
    return self.groups
end

function VeafCombatMissionElement:setSkill(value)
    self.skill = value
    return self
end

function VeafCombatMissionElement:getSkill()
    return self.skill
end

function VeafCombatMissionElement:setSpawnRadius(value)
    self.spawnRadius = tonumber(value)
    return self
end

function VeafCombatMissionElement:getSpawnRadius()
    return self.spawnRadius
end

function VeafCombatMissionElement:setSpawnChance(value)
    self.spawnChance = tonumber(value)
    return self
end

function VeafCombatMissionElement:getSpawnChance()
    return self.spawnChance
end

function VeafCombatMissionElement:setScale(value)
    self.scale = tonumber(value)
    return self
end

function VeafCombatMissionElement:getScale()
    return self.scale
end

function VeafCombatMissionElement:setScalable(value)
    self.scalable = value
    return self
end

function VeafCombatMissionElement:isScalable()
    return self.scalable
end

---
--- other methods
---

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafCombatMission object
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafCombatMission = {
    -- mission name (technical)
    name = nil,
    -- mission name (human-friendly)
    friendlyName = nil,
    -- mission briefing
    briefing = nil,
    -- secured :  if true, the radio menu will be secured
    secured = false,
    -- list of objectives
    objectives = {},
    -- list of the elements defined in the mission
    elements = {},
    -- mission is active
    active = false,
    -- mission is a training mission
    training = false,
    -- DCS groups that have been spawned (for cleaning up later)
    spawnedGroups = {},
    --- Radio menus paths
    radioMarkersPath = nil,
    radioTargetInfoPath = nil,
    radioRootPath = nil,
    -- the watchdog function checks for mission objectives completion
    watchdogFunctionId = nil,
    -- if false, the mission will not appear in the radio menu
    radioMenuEnabled = false,
    -- if true, no message will be displayed when activating/deactivating the mission
    hidden = false,
    -- same as hidden but only valid for one activation of the mission (will be reset to *hidden* at next start)
    silent = false,
    spawnedUnitsCountByGroup = {},
    spawnedNamesIndex = {}
}

function VeafCombatMission:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self
    
    -- init the new object
    
    -- mission name (technical)
    objectToCreate.name = nil
    -- mission name (human-friendly)
    objectToCreate.friendlyName = nil
    -- mission briefing
    objectToCreate.briefing = nil
    -- secured :  if true, the radio menu will be secured
    objectToCreate.secured = false
    -- list of objectives
    objectToCreate.objectives = {}
    -- list of the elements defined in the mission
    objectToCreate.elements = {}
    -- mission is active
    objectToCreate.active = false
    -- mission is a training mission
    objectToCreate.training = false
    -- DCS groups that have been spawned (for cleaning up later)
    objectToCreate.spawnedGroups = {}
    --- Radio menus paths
    objectToCreate.radioMarkersPath = nil
    objectToCreate.radioTargetInfoPath = nil
    objectToCreate.radioRootPath = nil
    -- the watchdog function checks for mission objectives completion
    objectToCreate.watchdogFunctionId = nil
    -- if false, the mission will not appear in the radio menu
    objectToCreate.radioMenuEnabled = false
    -- if true, no message will be displayed when activating/deactivating the mission
    objectToCreate.hidden = false
    -- same as hidden but only valid for one activation of the mission (will be reset to *hidden* at next start)
    objectToCreate.silent = false
    objectToCreate.spawnedUnitsCountByGroup = {}
    objectToCreate.spawnedNamesIndex = {}

    return objectToCreate
end

function VeafCombatMission:copy(newSkill, newScale)
    local copy = VeafCombatMission:new()

    -- copy the attributes
    copy.name = self.name
    copy.friendlyName = self.friendlyName
    copy.secured = self.secured
    copy.briefing = self.briefing
    copy.active = self.active
    copy.training = self.training
    copy.hidden = self.hidden
    copy.radioMenuEnabled = self.radioMenuEnabled
    copy.silent = self.silent

    -- deep copy the collections
    for _, objective in pairs(self.objectives) do
        copy:addObjective(objective:copy())
    end
    for _, element in pairs(self.elements) do
        local elementCopy = element:copy()
        if element:isScalable() and newScale then
            elementCopy:setScale(newScale)
        end
        if newSkill then
            elementCopy:setSkill(newSkill)
        end
        copy:addElement(elementCopy)
    end

    return copy
end

---
--- setters and getters
---

function VeafCombatMission:setName(value)
    self.name = value
    return self
end

function VeafCombatMission:getName()
    return self.name
end

function VeafCombatMission:setSecured(value)
    self.secured = value
    return self
end

function VeafCombatMission:isSecured()
    return self.secured
end

function VeafCombatMission:getRadioMenuName()
    return self:getFriendlyName()
end

function VeafCombatMission:setFriendlyName(value)
    self.friendlyName = value
    return self
end

function VeafCombatMission:getFriendlyName()
    return self.friendlyName
end

function VeafCombatMission:setBriefing(value)
    self.briefing = value
    return self
end

function VeafCombatMission:getBriefing()
    return self.briefing
end

function VeafCombatMission:isActive()
    return self.active
end

function VeafCombatMission:setActive(value)
    self.active = value
    return self
end

function VeafCombatMission:isTraining()
    return self.training
end

function VeafCombatMission:setTraining(value)
    self.training = value
    return self
end

function VeafCombatMission:addElement(value)
    table.insert(self.elements, value)
    return self
end

function VeafCombatMission:addSpawnedGroup(group)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:addSpawnedGroup(%s)",self.name or "", group:getName() or ""))
    if not self.spawnedGroups then
        self.spawnedGroups = {}
    end
    table.insert(self.spawnedGroups, group)

    -- count units in group
    self.spawnedUnitsCountByGroup[group:getName()] = #group:getUnits()
-- LOGGING DISABLED WHEN COMPILING(string.format("%s units in group [%s]",tostring(self.spawnedUnitsCountByGroup[group:getName()]), tostring(group:getName())))

    return self
end

function VeafCombatMission:getSpawnedGroups()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:getSpawnedGroups()",self.name or ""))
    for _, group in pairs(self.spawnedGroups) do
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnedGroups[%s]",group:getName()))
    end
    return self.spawnedGroups
end

function VeafCombatMission:clearSpawnedGroups()
    self.spawnedGroups = {}
    return self
end

function VeafCombatMission:addObjective(objective)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:addObjective(%s)",self.name or "", objective:getName() or ""))
    if not self.objectives then
        self.objectives = {}
    end
    table.insert(self.objectives, objective)
    return self
end

function VeafCombatMission:isHidden()
    return self.hidden
end

function VeafCombatMission:setHidden(value)
    self.hidden = value
    return self
end

function VeafCombatMission:isSilent()
    return self.silent
end

function VeafCombatMission:setSilent(value)
    self.silent = value
    return self
end

function VeafCombatMission:isRadioMenuEnabled()
    return self.radioMenuEnabled
end

function VeafCombatMission:setRadioMenuEnabled(value)
    self.radioMenuEnabled = value
    return self
end

function VeafCombatMission:setAllElementsSkill(skill)
    for _, element in self.elements do
        element:setSkill(skill)
    end
    return self
end
---
--- other methods
---

function VeafCombatMission:scheduleWatchdogFunction()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:scheduleWatchdogFunction()",self.name or ""))
    self.watchdogFunctionId = mist.scheduleFunction(veafCombatMission.CompletionCheck,{self.name},timer.getTime()+veafCombatMission.SecondsBetweenWatchdogChecks)
    return self
end

function VeafCombatMission:unscheduleWatchdogFunction()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:unscheduleWatchdogFunction()",self.name or ""))
    if self.watchdogFunctionId then
-- LOGGING DISABLED WHEN COMPILING(string.format("mist.removeFunction()"))
        mist.removeFunction(self.watchdogFunctionId)
        self.watchdogFunctionId = nil
    end
    return self
end

function VeafCombatMission:getObjectives()
    return self.objectives
end

function VeafCombatMission:addDefaultObjectives()
    -- TODO
    return self
end

function VeafCombatMission:initialize()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:initialize()",self.name or ""))

    -- check parameters
    if not self.name then
        return self
    end
    if not self.friendlyName then
        self:setFriendlyName(self.name)
    end
    if #self.objectives == 0 then
        self:addDefaultObjectives()
    end

    -- refresh the radio menu
    self:updateRadioMenu()

    return self
end

function VeafCombatMission:getRemainingEnemiesString()
    local nbLiveUnits, nbDamagedUnits, nbDeadUnits = self:getRemainingEnemies()
    return string.format("%d alive (%d damaged), %d dead", nbLiveUnits, nbDamagedUnits, nbDeadUnits)
end

function VeafCombatMission:getRemainingEnemies(whatsInAKill)
    local whatsInAKill = whatsInAKill or 0.01
    local nbLiveUnits = 0
    local nbDamagedUnits = 0
    local nbDeadUnits = 0
    for _, group in pairs(self:getSpawnedGroups()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("processing group [%s]",group:getName()))
        local groupLiveUnits = 0
        local groupDamagedUnits = 0
        if group and group:getUnits() then
            for _, unit in pairs(group:getUnits()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("processing unit [%s]",unit:getName()))
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.getUnitLifeRelative(unit) = %f",veaf.getUnitLifeRelative(unit)))
                if veaf.getUnitLifeRelative(unit) == 1.0 then
-- LOGGING DISABLED WHEN COMPILING(string.format("unit[%s] is alive",unit:getName()))
                    groupLiveUnits = groupLiveUnits + 1
                elseif veaf.getUnitLifeRelative(unit) > whatsInAKill then
-- LOGGING DISABLED WHEN COMPILING(string.format("unit[%s] is damaged (%d %%)",unit:getName(), veaf.getUnitLifeRelative(unit)*100 ))
                    groupDamagedUnits = groupDamagedUnits + 1
                    groupLiveUnits = groupLiveUnits + 1
                else
-- LOGGING DISABLED WHEN COMPILING(string.format("unit[%s] is dead",unit:getName()))
                    -- should never come to that, Moose do not return dead units in getUnits()
                end
            end
        else
            groupLiveUnits = 0
        end
        local groupDeadUnits = (self.spawnedUnitsCountByGroup[group:getName()] or 0) - groupLiveUnits
        if groupDeadUnits < 0 then -- should never happen but who knows ? This is DCS !
            groupDeadUnits = 0
        end

-- LOGGING DISABLED WHEN COMPILING(string.format("groupLiveUnits = %d",groupLiveUnits))
-- LOGGING DISABLED WHEN COMPILING(string.format("groupDamagedUnits = %d",groupDamagedUnits))
-- LOGGING DISABLED WHEN COMPILING(string.format("groupDeadUnits = %d",groupDeadUnits))

        nbLiveUnits = nbLiveUnits + groupLiveUnits
        nbDamagedUnits = nbDamagedUnits + groupDamagedUnits
        nbDeadUnits = nbDeadUnits + groupDeadUnits
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("nbLiveUnits = %d",nbLiveUnits))
-- LOGGING DISABLED WHEN COMPILING(string.format("nbDamagedUnits = %d",nbDamagedUnits))
-- LOGGING DISABLED WHEN COMPILING(string.format("nbDeadUnits = %d",nbDeadUnits))

    return nbLiveUnits, nbDamagedUnits, nbDeadUnits
end

function VeafCombatMission:getInformation()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:getInformation()",self.name or ""))
    local message =      "COMBAT MISSION "..self:getFriendlyName().." \n\n"
    if (self:getBriefing()) then
        message = message .. "BRIEFING: \n"
        message = message .. self:getBriefing()
        message = message .. "\n\n"
    end
    if (self:getObjectives() and #self:getObjectives() > 0) then
        message = message .. "OBJECTIVES: \n"
        for _, objective in pairs(self:getObjectives()) do
            message = message .. " - " .. objective:getDescription() .. "\n"
        end
        message = message .. "\n\n"
    end
    if self:isActive() then

        -- generate information dispatch
        message = message .. "ENEMIES : " ..self:getRemainingEnemiesString() .."\n"

        if self:isTraining() then
            -- TODO find the position of the enemies
        end

    else
        message = message .. "mission is not yet active."
    end

    return message
end

-- activate the mission
function VeafCombatMission:activate(silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:activate(%s)",self:getName(), tostring(silent)))

    -- don't start twice
    if self:isActive() then
        return nil
    end

    self:setActive(true)
    self:setSilent(self:isHidden() or silent)

    for _, missionElement in pairs(self.elements) do
-- LOGGING DISABLED WHEN COMPILING(string.format("processing element [%s]",missionElement:getName()))
        local chance = math.random(0, 100)
        if chance <= missionElement:getSpawnChance() then
            -- spawn the element
-- LOGGING DISABLED WHEN COMPILING(string.format("chance hit (%d <= %d)",chance, missionElement:getSpawnChance()))
            for _, groupName in pairs(missionElement:getGroups()) do
                local _spawnPoint = missionElement.spawnPoints[groupName]
-- LOGGING DISABLED WHEN COMPILING(string.format("_spawnPoint=%s",veaf.p(_spawnPoint)))
                local _spawnRadius = missionElement:getSpawnRadius()
                if (missionElement:getScale() > 1 and _spawnRadius < veafCombatMission.MinimumSpacingBetweenClones) then
                    _spawnRadius = veafCombatMission.MinimumSpacingBetweenClones
                end
-- LOGGING DISABLED WHEN COMPILING(string.format("_spawnRadius=%s",veaf.p(_spawnRadius)))

                local vars = {}
                vars.gpName = groupName
                vars.action = 'clone'
                vars.point = _spawnPoint
                vars.radius = _spawnRadius
                vars.disperse = false
                vars.route = mist.getGroupRoute(groupName, 'task')
-- LOGGING DISABLED WHEN COMPILING(string.format("vars=%s",veaf.p(vars)))

                for i=1,missionElement:getScale() do
                    if not self.spawnedNamesIndex[groupName] then
                        self.spawnedNamesIndex[groupName] = 1
                    else
                        self.spawnedNamesIndex[groupName] = self.spawnedNamesIndex[groupName] + 1
                    end
                    local spawnedGroupName = string.format("%s #%04d", groupName, self.spawnedNamesIndex[groupName])
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnedGroupName=%s",veaf.p(spawnedGroupName)))
                    local _group = mist.teleportToPoint(vars, true)
                    if _group then
                        for _, unit in pairs(_group.units) do
                            unit.skill = missionElement:getSkill()
                        end
                    end
                    _group.groupName = spawnedGroupName
                    if _group then
                        for _, unit in pairs(_group.units) do
                            local unitName = unit.unitName
-- LOGGING DISABLED WHEN COMPILING(string.format("unitName=%s",veaf.p(unitName)))
                            if not self.spawnedNamesIndex[unitName] then
                                self.spawnedNamesIndex[unitName] = 1
                            else
                                self.spawnedNamesIndex[unitName] = self.spawnedNamesIndex[unitName] + 1
                            end
                            local spawnedUnitName = string.format("%s #%04d", unitName, self.spawnedNamesIndex[unitName])
                            unit.groupName = spawnedUnitName
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnedUnitName=%s",veaf.p(spawnedUnitName)))
                        end
                    end
-- LOGGING DISABLED WHEN COMPILING(string.format("_group=%s",veaf.p(_group)))
                    local _spawnedGroup = mist.dynAdd(_group)
                    if _spawnedGroup then
-- LOGGING DISABLED WHEN COMPILING(string.format("_spawnedGroup.name=%s",veaf.p(_spawnedGroup.name)))
                        local _dcsSpawnedGroup = Group.getByName(_spawnedGroup.name)
-- LOGGING DISABLED WHEN COMPILING(string.format("_spawnedGroup.name=%s",veaf.p(_dcsSpawnedGroup:getName())))
                        for _, unit in pairs(_dcsSpawnedGroup:getUnits()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("_spawnedGroup.unit.name=%s",veaf.p(unit:getName())))
                        end

                        self:addSpawnedGroup(_dcsSpawnedGroup)
                        -- add the group to the Hound Elint, if there is one
                        if veafHoundElint then
-- LOGGING DISABLED WHEN COMPILING(string.format("veafHoundElint.addPlatformToSystem(%s)",veaf.p(_dcsSpawnedGroup:getName())))
                            veafHoundElint.addPlatformToSystem(_dcsSpawnedGroup)
                        end
                    end
                end
            end
        else
-- LOGGING DISABLED WHEN COMPILING(string.format("chance missed (%d > %d)",chance, missionElement:getSpawnChance()))
        end
    end

    -- start all the objectives
    for _, objective in pairs(self.objectives) do
        objective:onStartup(self)
    end

    -- start the completion watchdog
    self:scheduleWatchdogFunction()

    -- refresh the radio menu
    self:updateRadioMenu()

    return self
end

-- desactivate the mission
function VeafCombatMission:desactivate()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:desactivate()",self.name or ""))
    self:setActive(false)
    self:unscheduleWatchdogFunction()

    for _, group in pairs(self:getSpawnedGroups()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("trying to destroy group [%s]",group:getName()))
        group:destroy()
    end
    self:clearSpawnedGroups()

    -- refresh the radio menu
    self:updateRadioMenu()

    return self
end

-- check if there are still units in mission
function VeafCombatMission:completionCheck()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:completionCheck()",self.name or ""))

    VeafCombatMissionObjective.FAILED = -1
    VeafCombatMissionObjective.SUCCESS = 1
    VeafCombatMissionObjective.NOTHING = 0

    local reschedule = true

    -- check all the objectives
    for _, objective in pairs(self.objectives) do
        local result = objective:onCheck(self)
        if result == VeafCombatMissionObjective.FAILED then
            -- mission is failed
            local message = string.format([[
Objective not met : %s
The mission %s will now end.
You can replay by starting it again, in the radio menu.]], objective:getDescription(), self:getFriendlyName())
            if not self:isSilent() then
                trigger.action.outText(message, 15)
            end
            self:desactivate()
            reschedule = false
        elseif result == VeafCombatMissionObjective.SUCCESS then
            -- mission is won
            local message = string.format([[
All objectives were met !
The mission %s is a success ! It will now end.
You can replay by starting it again, in the radio menu.]], self:getFriendlyName())
            if not self:isSilent() then
                trigger.action.outText(message, 15)
            end
            self:desactivate()
            reschedule = false
        end
    end

    if reschedule then
        -- reschedule
        self:scheduleWatchdogFunction()
    end
end

-- updates the radio menu according to the mission state
function VeafCombatMission:updateRadioMenu(inBatch)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatMission[%s]:updateRadioMenu(%s)",self.name or "", tostring(inBatch)))

    -- do not update the radio menu for a mission that has no menu
    if not self:isRadioMenuEnabled() then
        return self
    end

    -- do not update the radio menu if not yet initialized
    if not veafCombatMission.rootPath then
        return self
    end

    -- reset the radio menu
    if self.radioRootPath then
-- LOGGING DISABLED WHEN COMPILING("reset the radio submenu")
        veafRadio.clearSubmenu(self.radioRootPath)
    end

    -- populate the radio menu
-- LOGGING DISABLED WHEN COMPILING("populate the radio menu")
    -- global commands
    veafRadio.addCommandToSubmenu("Get info", self.radioRootPath, veafCombatMission.GetInformationOnMission, self.name, veafRadio.USAGE_ForAll)
    if self:isActive() then
        -- mission is active, set up accordingly (desactivate mission, get information, pop smoke, etc.)
-- LOGGING DISABLED WHEN COMPILING("mission is active")
        if self:isSecured() then
            veafRadio.addSecuredCommandToSubmenu('Desactivate mission', self.radioRootPath, veafCombatMission.DesactivateMission, self.name, veafRadio.USAGE_ForAll)
        else
            veafRadio.addCommandToSubmenu('Desactivate mission', self.radioRootPath, veafCombatMission.DesactivateMission, self.name, veafRadio.USAGE_ForAll)
        end
    else
        -- mission is not active, set up accordingly (activate mission)
-- LOGGING DISABLED WHEN COMPILING("mission is not active")
        if self:isSecured() then
            veafRadio.addSecuredCommandToSubmenu('Activate mission', self.radioRootPath, veafCombatMission.ActivateMission, self.name, veafRadio.USAGE_ForAll)
        else
            veafRadio.addCommandToSubmenu('Activate mission', self.radioRootPath, veafCombatMission.ActivateMission, self.name, veafRadio.USAGE_ForAll)
        end
    end

    if not inBatch then veafRadio.refreshRadioMenu() end
    return self
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- global functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafCombatMission.GetMissionNumber(number)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.GetMissionNumber([%s])",tostring(number)))
    local mission = veafCombatMission.missionsList[number]
    return mission
end

function veafCombatMission.GetMission(name)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.GetMission([%s])",name or ""))
-- LOGGING DISABLED WHEN COMPILING(string.format("Searching for mission with name [%s]", name))
    local mission = veafCombatMission.missionsDict[name:lower()]
    if not mission then
        local message = string.format("VeafCombatMission [%s] was not found !",name)
        veaf.loggers.get(veafCombatMission.Id):error(message)
        trigger.action.outText(message,5)
    end
    return mission
end

-- add a mission
function veafCombatMission.AddMission(mission)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.AddMission([%s])",mission:getName() or ""))
-- LOGGING DISABLED WHEN COMPILING(string.format("Adding mission [%s]", mission:getName()))
    mission:initialize()
    table.insert(veafCombatMission.missionsList, mission)
    veafCombatMission.missionsDict[mission:getName():lower()] = mission
    return mission
end

-- add a mission and create copies with different skills
function veafCombatMission.AddMissionsWithSkillAndScale(mission, includeOriginal, skills, scales)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.AddMissionsWithSkill([%s])",mission:getName() or ""))
-- LOGGING DISABLED WHEN COMPILING(string.format("skills=%s",veaf.p(skills)))
-- LOGGING DISABLED WHEN COMPILING(string.format("scales=%s",veaf.p(scales)))

    if (mission:isRadioMenuEnabled() and includeOriginal) then
        veafCombatMission.AddMission(mission)
    end

    local skills = skills or  {"Average", "Good", "High", "Excellent", "Random"}
    local scales = scales or {1, 2, 3, 4}

    for _, scale in pairs(scales) do
        for _, skill in pairs(skills) do
            local copy = mission:copy(skill, scale)--:setRadioMenuEnabled(false)
            copy:setName(mission:getName().."/"..skill.."/"..scale)
            copy:setFriendlyName(mission:getFriendlyName())
            veafCombatMission.AddMission(copy)
        end
    end

    return mission
end

-- activate a mission by number
function veafCombatMission.ActivateMissionNumber(number, silent)
    local mission = veafCombatMission.GetMissionNumber(number)
    if mission then
        veafCombatMission.ActivateMission(mission:getName(), silent)
    end
end

-- activate a mission
function veafCombatMission.ActivateMission(name, silent, unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.ActivateMission([%s])",name or ""))
    local mission = veafCombatMission.GetMission(name)
    local result = mission:activate(silent)
    if not silent and not mission:isSilent() then
        if result then
            veaf.outTextForUnit(unitName, "VeafCombatMission "..mission:getFriendlyName().." has been activated.", 10)
            mist.scheduleFunction(veafCombatMission.GetInformationOnMission,{{name}},timer.getTime()+1)
        else
            veaf.outTextForUnit(unitName, "VeafCombatMission "..mission:getFriendlyName().." was already active.", 10)
        end
    end
    veafCombatMission.buildRadioMenu()
end

-- desactivate a mission by number
function veafCombatMission.DesactivateMissionNumber(number, silent)
    local mission = veafCombatMission.GetMission(number)
    if mission then
        veafCombatMission.DesactivateMission(mission:getName(), silent)
    end
end

-- desactivate a mission
function veafCombatMission.DesactivateMission(name, silent, unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.DesactivateMission([%s])",name or ""))
    local mission = veafCombatMission.GetMission(name)
    mission:desactivate()
    if not silent and not mission:isSilent() then
        veaf.outTextForUnit(unitName, "VeafCombatMission "..mission:getFriendlyName().." has been desactivated.", 10)
    end
    veafCombatMission.buildRadioMenu()
end

-- print information about a mission
function veafCombatMission.GetInformationOnMission(parameters)
    local name, unitName = veaf.safeUnpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.GetInformationOnMission([%s])",name or ""))
    local mission = veafCombatMission.GetMission(name)
    local text = mission:getInformation()
    if unitName then
        veaf.outTextForGroup(unitName, text, 30)
    else
        trigger.action.outText(text, 30)
    end
end

-- call the completion watchdog methods
function veafCombatMission.CompletionCheck(name)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.CompletionCheck([%s])",name or ""))
    local mission = veafCombatMission.GetMission(name)
    mission:completionCheck()
end


-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------
local function _groupMissions()
    local missionGroups = {}
    local activeGroups = {}

    for _, mission in pairs(veafCombatMission.missionsDict) do
-- LOGGING DISABLED WHEN COMPILING(string.format("grouping missionName=%s", mission:getName()))
        if mission:isRadioMenuEnabled() then
            local regex = ("^([^/]+)/([^/]+)/(.+)$")
            local name, skill, scale = mission:getName():match(regex)
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s, skill=%s, scale=%s", tostring(name), tostring(skill), tostring(scale)))
            local groupName = name
            if not groupName then groupName = mission:getName() end
            if not(missionGroups[groupName]) then
                missionGroups[groupName] = {}
-- LOGGING DISABLED WHEN COMPILING(string.format("creating group %s", groupName))
            end
            table.insert(missionGroups[groupName], mission)
            if mission:isActive() then
-- LOGGING DISABLED WHEN COMPILING(string.format("mission %s is active", mission:getName()))
-- LOGGING DISABLED WHEN COMPILING(string.format("activating group %s", groupName))
                if not(activeGroups[groupName]) then
                    activeGroups[groupName] = {}
                end
                if skill then
-- LOGGING DISABLED WHEN COMPILING(string.format("activating skill %s", skill))
                    if not(activeGroups[groupName][skill]) then
                        activeGroups[groupName][skill] = {}
                    end
                    if scale then
-- LOGGING DISABLED WHEN COMPILING(string.format("activating scale %s", scale))
                        activeGroups[groupName][skill][scale] = true
                    end
                end
            end
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("missionGroups=%s",veaf.p(missionGroups)))
-- LOGGING DISABLED WHEN COMPILING(string.format("activeGroups=%s",veaf.p(activeGroups)))
    return missionGroups, activeGroups
end

function veafCombatMission._buildMissionRadioMenu(menu, title, element)
    local missions = element.missions
    if #missions == 1 then
        -- one simple mission
        local mission = missions[1]
        if mission:isActive() then title = "* "..title end
        mission.radioRootPath = veafRadio.addSubMenu(title, menu)
        mission:updateRadioMenu(true)
    else
        -- group by skill and scale
-- LOGGING DISABLED WHEN COMPILING("group by skill and scale")
        local skills = {}
        for _, mission in pairs(missions) do
            local regex = ("^([^/]+)/([^/]+)/(%d+)$")
            local name, skill, scale = mission:getName():match(regex)
-- LOGGING DISABLED WHEN COMPILING(string.format("missionName=[%s], name=%s, skill=%s, scale=%s", tostring(mission:getName()), tostring(name), tostring(skill), tostring(scale)))
            if not skills[skill] then
                skills[skill] = {}
            end
            skills[skill][scale] = mission
        end

-- LOGGING DISABLED WHEN COMPILING(string.format("skills=%s", veaf.p(skills)))

        -- create the radio menus
        local title = title
        if element.activeGroups then title = "* "..title end
        local missionPath = veafRadio.addSubMenu(title, menu)
-- LOGGING DISABLED WHEN COMPILING(string.format("  %s", title))
        local skillsNames = {}
        for skill, _ in pairs(skills) do
            table.insert(skillsNames, skill)
        end
        table.sort(skillsNames)
        for _, skill in pairs(skillsNames) do
            local scales = skills[skill]
            local skillTitle = skill
            if element.activeGroups and element.activeGroups[skill] then skillTitle = "* "..skillTitle end
            local skillPath = veafRadio.addSubMenu(skillTitle, missionPath)
-- LOGGING DISABLED WHEN COMPILING(string.format("    %s", skill))
            local scalesNames = {}
            for scale, _ in pairs(scales) do
                table.insert(scalesNames, scale)
            end
            table.sort(scalesNames)
            for _, scale in pairs(scalesNames) do
                local mission = scales[scale]
                local scaleTitle = "scale "..scale
                if element.activeGroups and element.activeGroups[skill] and element.activeGroups[skill][scale] then scaleTitle = "* "..scaleTitle end
                local scalePath = veafRadio.addSubMenu(scaleTitle, skillPath)
-- LOGGING DISABLED WHEN COMPILING(string.format("      %s", scale))
                mission.radioRootPath = scalePath
                mission:updateRadioMenu(true)
            end
        end
    end
end

--- Build the initial radio menu
function veafCombatMission.buildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("buildRadioMenu()")

    -- don't create an empty menu
    if veaf.length(veafCombatMission.missionsDict) == 0 then
        return
    end

    if veafCombatMission.rootPath then
        veafRadio.clearSubmenu(veafCombatMission.rootPath)
    else
        veafCombatMission.rootPath = veafRadio.addMenu(veafCombatMission.RadioMenuName)
    end
    if not(veafRadio.skipHelpMenus) then
        veafRadio.addCommandToSubmenu("HELP", veafCombatMission.rootPath, veafCombatMission.help, nil, veafRadio.USAGE_ForGroup)
    end
    veafRadio.addCommandToSubmenu("List available", veafCombatMission.rootPath, veafCombatMission.listAvailableMissions, nil, veafRadio.USAGE_ForAll)
    veafRadio.addCommandToSubmenu("List active", veafCombatMission.rootPath, veafCombatMission.listActiveMissions, nil, veafRadio.USAGE_ForAll)

    local missions = {}
    local missionGroups, activeGroups = _groupMissions()
    for groupName, missionsInGroup in pairs(missionGroups) do
-- LOGGING DISABLED WHEN COMPILING(string.format("processing groupName=%s",groupName))
        missions[groupName] = {title=missionsInGroup[1]:getRadioMenuName(), sort=missionsInGroup[1]:getFriendlyName(), missions=missionsInGroup, activeGroups=activeGroups[groupName]}
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("missions=%s",veaf.p(missions)))
-- LOGGING DISABLED WHEN COMPILING(string.format("#missions=%d",#missions))
    veafRadio.addPaginatedRadioElements(veafCombatMission.rootPath, veafCombatMission._buildMissionRadioMenu, missions)
    veafRadio.refreshRadioMenu()
end

function veafCombatMission.help(unitName)
    local text =
        'Combat missions are defined by the mission maker, and listed here\n' ..
        'You can start and stop them at will,\n' ..
        'as well as ask for information about their status.'

    veaf.outTextForGroup(unitName, text, 30)
end

function veafCombatMission.listAvailableMissions(unitName)
    -- sort the missions alphabetically
    local sortedMissions = {}
    local groupedMissions = _groupMissions()
    for groupName, missionsInGroup in pairs(groupedMissions) do
        table.insert(sortedMissions, groupName)
    end
    table.sort(sortedMissions)

    local text =
    'List of all available combat missions:\n'

    for _, missionName in pairs(sortedMissions) do
        text = text .. " - " .. missionName .. "\n"
    end
    veaf.outTextForUnit(unitName, text, 20)
end

function veafCombatMission.listActiveMissions()
    -- sort the missions alphabetically
    local sortedMissions = {}
    for _, mission in pairs(veafCombatMission.missionsDict) do
        if mission:isActive() then
            table.insert(sortedMissions, mission:getName() .. ' : ' .. mission:getRemainingEnemiesString())
        end
    end
    table.sort(sortedMissions)

    local text =
    'No active combat mission !'

    if #sortedMissions > 0 then
        text =
        'List of active combat missions:\n'

        for _, missionName in pairs(sortedMissions) do
            text = text .. " - " .. missionName .. "\n"
        end
    end

    trigger.action.outText(text, 20)
end

-- add a standard CAP mission with a single group
function veafCombatMission.addCapMission(missionName, missionDescription, missionBriefing, secured, radioMenuEnabled, skills, scales, spawnRadius)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.addCapMission(%s)",tostring(missionName)))

    local groupName = "OnDemand-"..missionName
    local spawnRadius = spawnRadius
    if spawnRadius == nil then spawnRadius = 20000 end
    local secured = secured
    if secured == nil then secured = true end
    local radioMenuEnabled = radioMenuEnabled
    if radioMenuEnabled == nil then radioMenuEnabled = false end
    local skills = skills
-- LOGGING DISABLED WHEN COMPILING(string.format("checking skills"))
    if not skills then
-- LOGGING DISABLED WHEN COMPILING(string.format("skills is nil"))
        if radioMenuEnabled then
            skills = {"Good", "Excellent"}
        else
            skills = nil
        end
    end
    local scales = scales
-- LOGGING DISABLED WHEN COMPILING(string.format("checking scales"))
    if not scales then
-- LOGGING DISABLED WHEN COMPILING(string.format("scales is nil"))
        if radioMenuEnabled then
            scales = {1, 2}
        else
            scales = nil
        end
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("skills=(%s)", veaf.p(skills)))
-- LOGGING DISABLED WHEN COMPILING(string.format("scales=(%s)", veaf.p(scales)))

    veafCombatMission.AddMissionsWithSkillAndScale(
		VeafCombatMission:new()
		:setSecured(secured)
		:setRadioMenuEnabled(radioMenuEnabled)
		:setName(missionName)
		:setFriendlyName(missionDescription)
		:setBriefing(missionBriefing)
		:addElement(
			VeafCombatMissionElement:new()
			:setName(groupName)
            :setGroups({groupName})
            :setSkill("Random")
            :setScalable(true)
            :setSpawnRadius(spawnRadius)
		)
		:addObjective(
			VeafCombatMissionObjective:new()
			:setName("Kill all the ennemies")
			:setDescription("you must kill all of the ennemies")
			:setMessage("%d ennemies destroyed !")
			:configureAsKillEnemiesObjective()
		)
		:initialize()
    ,false, skills, scales)
end

function veafCombatMission.dumpMissionsList(export_path)

    local jsonify = function(key, value)
-- LOGGING DISABLED WHEN COMPILING("jsonify()")
-- LOGGING DISABLED WHEN COMPILING("key=%s", veaf.p(key))
-- LOGGING DISABLED WHEN COMPILING("value=%s", veaf.p(value))
        if veaf.json then
            return veaf.json.stringify(veafCombatMission.missionsDict[value])
        else
            return ""
        end
    end

    -- sort the missions alphabetically
    local sortedMissions = {}
    for _, mission in pairs(veafCombatMission.missionsDict) do
-- LOGGING DISABLED WHEN COMPILING("mission=%s", veaf.p(mission))
        table.insert(sortedMissions, mission:getName():lower())
    end
    table.sort(sortedMissions)

    local _filename = "CombatMissionsList.json"
    if veaf.config.MISSION_NAME then
        _filename = "CombatMissionsList_" .. veaf.config.MISSION_NAME .. ".json"
    end
    if not(veaf.DO_NOT_EXPORT_JSON_FILES) then
        veaf.exportAsJson(sortedMissions, "combatMissions", jsonify, _filename, export_path)
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- remote interface
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- execute command from the remote interface
function veafCombatMission.executeCommandFromRemote(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatMission.executeCommandFromRemote()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("parameters= %s", veaf.p(parameters)))
    local _pilot, _pilotName, _unitName, _command = unpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilot= %s", veaf.p(_pilot)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilotName= %s", veaf.p(_pilotName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_unitName= %s", veaf.p(_unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_command= %s", veaf.p(_command)))
    if not _pilot or not _command then
        return false
    end

    if _command then
        -- parse the command
        local _action, _missionName, _parameters = _command:match(veafCombatMission.RemoteCommandParser)
-- LOGGING DISABLED WHEN COMPILING(string.format("_action=%s",veaf.p(_action)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_missionName=%s",veaf.p(_missionName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_parameters=%s",veaf.p(_parameters)))
        if _action and _action:lower() == "list" then
            veaf.loggers.get(veafCombatMission.Id):info(string.format("[%s] is listing air missions)",veaf.p(_pilot.name)))
            veafCombatMission.listAvailableMissions(_unitName)
            return true
        elseif _action and _action:lower() == "start" and _missionName then
            local _silent = _parameters and _parameters:lower() == "silent"
            veaf.loggers.get(veafCombatMission.Id):info(string.format("[%s] is starting air mission [%s] %s)",veaf.p(_pilot.name), veaf.p(_missionName), veaf.p(_parameters)))
            veafCombatMission.ActivateMission(_missionName, _silent, _unitName)
            return true
        elseif _action and _action:lower() == "stop" then
            local _silent = _parameters and _parameters:lower() == "silent"
            veaf.loggers.get(veafCombatMission.Id):info(string.format("[%s] is stopping air mission [%s] %s)",veaf.p(_pilot.name), veaf.p(_missionName), veaf.p(_parameters)))
            veafCombatMission.DesactivateMission(_missionName, _silent, _unitName)
            return true
        end
    end
    return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafCombatMission.initialize()
    veaf.loggers.get(veafCombatMission.Id):info("Initializing module")
    veafCombatMission.buildRadioMenu()
    veafCombatMission.dumpMissionsList(veaf.config.MISSION_EXPORT_PATH)
end

veaf.loggers.get(veafCombatMission.Id):info(string.format("Loading version %s", veafCombatMission.Version))


------------------ END script veafCombatMission.lua  ------------------


------------------ START script veafCombatZone.lua  ------------------

------------------------------------------------------------------
-- VEAF combat zone functions for DCS World
-- By zip (2019-20)
--
-- Features:
-- ---------
-- * Zones can be defined in the mission editor that are then managed by this script.
-- * For each zone, a specific radio sub-menu is created, allowing common actions on all specific zone (get coordinates, enemy presence, weather, pop smoke and flares, read a briefing, stop and start dynamic activity on the zone, etc.)
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafCombatZone = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafCombatZone.Id = "COMBATZONE"

--- Version.
veafCombatZone.Version = "1.16.2"

-- trace level, specific to this module
--veafCombatZone.LogLevel = "trace"

veaf.loggers.new(veafCombatZone.Id, veafCombatZone.LogLevel)

--- Number of seconds between each check of the zone watchdog function
veafCombatZone.SecondsBetweenWatchdogChecks = 60

--- Number of seconds between each smoke request on the zones
veafCombatZone.SecondsBetweenSmokeRequests = 180

--- Number of seconds between each flare request on the zones
veafCombatZone.SecondsBetweenFlareRequests = 120

veafCombatZone.DefaultSpawnRadiusForUnits = 50

veafCombatZone.DefaultSpawnRadiusForStatics = 0

veafCombatZone.RadioMenuName = "COMBAT ZONES"

-- Combat zones specific radio menu name
veafCombatZone.CombatZoneRadioMenuName = nil

-- Combat operations specific radio menu name
veafCombatZone.OperationRadioMenuName = nil

veafCombatZone.EventMessages = {
    CombatZoneComplete = [[
    Well done ! All enemies in zone %s have been destroyed or routed.
    The zone will now be desactivated.
    You can replay by activating it again, in the radio menu.]],
    PopSmokeRequest = "Copy RED smoke requested on %s !",
    UseFlareRequest = "Copy illumination flare requested on %s !",
    CombatOperationComplete = "Operation %s is over. Congratulations !"
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Radio menus paths
veafCombatZone.rootPath = nil

--- Combat Zones radio menus paths
veafCombatZone.combatZoneRootPath = nil
--- Operation radio menus paths
veafCombatZone.operationRootPath = nil

-- Zones list (table of VeafCombatZone objects)
veafCombatZone.zonesList = {}

-- Zones dictionary (map of VeafCombatZone objects by zone name)
veafCombatZone.zonesDict = {}

-- Radio groups dictionary (map of radio menu paths by radio group name)
veafCombatZone.radioGroupsDict = {}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utils
-------------------------------------------------------------------------------------------------------------------------------------------------------------

local messageSeparator = "\n=====================================================\n"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafCombatZoneElement object
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafCombatZoneElement = {}

function VeafCombatZoneElement:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object

    -- name
    objectToCreate.name = nil
    -- position on the map
    objectToCreate.position = nil
    -- if true, this is a simple dcs static
    objectToCreate.dcsStatic = false
    -- if true, this is a simple dcs group
    objectToCreate.dcsGroup = false
    -- if true, this is a VEAF command
    objectToCreate.veafCommand = nil
    --  coalition (0 = neutral, 1 = red, 2 = blue)
    objectToCreate.coalition = nil
    -- route, only for veaf commands (groups already have theirs)
    objectToCreate.route = nil
    -- spawn radius in meters (randomness introduced in the respawn mechanism)
    objectToCreate.spawnRadius = 0
    -- spawn chance in percent (xx chances in 100 that the unit is spawned - or the command run)
    objectToCreate.spawnChance = 100
    -- grouping elements (spawnGroup) so that a certain number (spawnCount) is guaranteed to spawn, by running the spawn random chance computation as often as necessary
    objectToCreate.spawnGroup = nil
    -- grouping elements (spawnGroup) so that a certain number (spawnCount) is guaranteed to spawn, by running the spawn random chance computation as often as necessary
    objectToCreate.spawnCount = 1

    return objectToCreate
end

---
--- setters and getters
---

function VeafCombatZoneElement:setName(value)
    self.name = value
    return self
end

function VeafCombatZoneElement:getName()
    return self.name
end

function VeafCombatZoneElement:setPosition(value)
    self.position = value
    return self
end

function VeafCombatZoneElement:getPosition()
    return self.position
end

function VeafCombatZoneElement:setDcsStatic(value)
    self.dcsStatic = value
    return self
end

function VeafCombatZoneElement:isDcsStatic()
    return self.dcsStatic
end

function VeafCombatZoneElement:setDcsGroup(value)
    self.dcsGroup = value
    return self
end

function VeafCombatZoneElement:isDcsGroup()
    return self.dcsGroup
end

function VeafCombatZoneElement:setVeafCommand(value)
    self.veafCommand = value
    return self
end

function VeafCombatZoneElement:getVeafCommand()
    return self.veafCommand
end

function VeafCombatZoneElement:setRoute(value)
    self.route = value
    return self
end

function VeafCombatZoneElement:getRoute()
    return self.route
end

function VeafCombatZoneElement:setCoalition(value)
    self.coalition = value
    return self
end

function VeafCombatZoneElement:getCoalition()
    return self.coalition
end

function VeafCombatZoneElement:setSpawnRadius(value)
    self.spawnRadius = tonumber(value)
    return self
end

function VeafCombatZoneElement:getSpawnRadius()
    return self.spawnRadius
end

function VeafCombatZoneElement:setSpawnChance(value)
    self.spawnChance = tonumber(value)
    return self
end

function VeafCombatZoneElement:getSpawnChance()
    return self.spawnChance
end

function VeafCombatZoneElement:setSpawnGroup(value)
    self.spawnGroup = value
    return self
end

function VeafCombatZoneElement:getSpawnGroup()
    return self.spawnGroup
end

function VeafCombatZoneElement:setSpawnDelay(value)
    if type(value) ~= "number" then
        value = tonumber(value)
    end
    self.spawnDelay = value
    return self
end

function VeafCombatZoneElement:getSpawnDelay()
    return self.spawnDelay
end

function VeafCombatZoneElement:setSpawnCount(value)
    self.spawnCount = tonumber(value)
    return self
end

function VeafCombatZoneElement:getSpawnCount()
    return self.spawnCount
end

---
--- other methods
---

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafCombatZone object
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafCombatZone = {}

function VeafCombatZone:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object

    -- zone name (human-friendly)
    objectToCreate.friendlyName = nil
    -- technical zone name (in the mission editor)
    objectToCreate.missionEditorZoneName = nil
    -- mission briefing
    objectToCreate.briefing = nil
    -- list of defined objectives
    objectToCreate.objectives = {}
    -- list of the elements defined in the zone
    objectToCreate.elements = {}
    objectToCreate.elementGroups = {}
    -- the trigger zone object
    objectToCreate.triggerZone = nil
    -- the zone center
    objectToCreate.zoneCenter = nil
    -- zone is active
    objectToCreate.active = false
    -- zone is a training zone
    objectToCreate.training = false
    -- display the remaining units
    objectToCreate.showUnitsList = true
    -- display the zone coordinates and weather
    objectToCreate.showZonePositionInfo = true
    -- zone is completable (i.e. disable it when all ennemies are dead)
    objectToCreate.completable = true
    -- DCS groups that have been spawned (for cleaning up later)
    objectToCreate.spawnedGroups = {}
    objectToCreate.delayedSpawners = {}
    -- Whether we want the combat zone to be added to populate the radio menu
    objectToCreate.enableRadioMenu = true
    -- whether the zone can be activated/deactivated by user via radio menu. If false, the zone won't be added to radio menu until activated
    objectToCreate.enableUserActivation = true
    -- whether we want to allow ground marking of the zone
    objectToCreate.enableSmokeAndFlare = true
    -- list of chained combat zones; this is are list of combat zones, that are activated randomly
    objectToCreate.chainedCombatZones = nil
    -- delay (in seconds) between the end of this combat zone and the start of the other; can be a randomizable numeric (e.g. "[1-5]")
    objectToCreate.chainedCombatZonesDelay = nil
    --- Radio menus
    objectToCreate.radioGroupName = nil
    objectToCreate.radioParentPath = nil
    objectToCreate.radioMarkersPath = nil
    objectToCreate.radioTargetInfoPath = nil
    objectToCreate.radioRootPath = nil
    -- the watchdog function checks for zone objectives completion
    objectToCreate.watchdogFunctionId = nil
    -- "pop smoke" command reset function id
    objectToCreate.smokeResetFunctionId = nil
    -- "pop flare" command reset function id
    objectToCreate.flareResetFunctionId = nil
    -- function to call when combat zone is over. The function is passed self combat zone
    objectToCreate.onCompletedHook = nil

    return objectToCreate
  end

---
--- setters and getters
---
function VeafCombatZone:setOnCompletedHook(onCompletedFunction)
    self.onCompletedHook = onCompletedFunction
    return self
end

function VeafCombatZone:disableRadioMenu()
    self.enableRadioMenu = false
    return self
end

-- make sure users cannot activate the zone (it won't be in the radio menu unless it's already active)
function VeafCombatZone:disableUserActivation()
    self.enableUserActivation = false
    return self
end

-- make sure users can activate the zone (it will be in the radio menu even if inactive - that's the default)
function VeafCombatZone:enableUserActivation()
    self.enableUserActivation = true
    return self
end

function VeafCombatZone:setEnableUserActivation(value)
    self.enableUserActivation = value
    return self
end

function VeafCombatZone:setEnableSmokeAndFlare(value)
    self.enableSmokeAndFlare = value
    return self
end

function VeafCombatZone:getRadioMenuName(asActive)
    local active = ""
    if asActive then
        active = "* "
    end
    return active .. self:getFriendlyName()
end

function VeafCombatZone:setFriendlyName(value)
    self.friendlyName = value
    return self
end

function VeafCombatZone:getFriendlyName()
    return self.friendlyName
end

function VeafCombatZone:setBriefing(value)
    self.briefing = value
    return self
end

function VeafCombatZone:getBriefing()
    return self.briefing
end

function VeafCombatZone:setMissionEditorZoneName(value)
    self.missionEditorZoneName = value
    return self
end

function VeafCombatZone:getMissionEditorZoneName()
    return self.missionEditorZoneName
end

function VeafCombatZone:isActive()
    return self.active
end

function VeafCombatZone:setActive(value)
    self.active = value
    return self
end

function VeafCombatZone:isTraining()
    return self.training
end

function VeafCombatZone:setTraining(value)
    self.training = value
    if value then 
        self.showUnitsList = true
        self.showZonePositionInfo = true
    end
    return self
end

function VeafCombatZone:isShowUnitsList()
    return self.showUnitsList
end

function VeafCombatZone:setShowUnitsList(value)
    self.showUnitsList = value
    return self
end

function VeafCombatZone:isShowZonePositionInfo()
    return self.showZonePositionInfo
end

function VeafCombatZone:setShowZonePositionInfo(value)
    self.showZonePositionInfo = value
    return self
end

function VeafCombatZone:isCompletable()
    return self.completable
end

function VeafCombatZone:setCompletable(value)
    self.completable = value
    return self
end

function VeafCombatZone:getTriggerZone()
    return self.triggerZone
end

function VeafCombatZone:getCenter()
    return self.zoneCenter
end

function VeafCombatZone:setRadioParentPath(value)
    self.radioParentPath = value
    return self
end

function VeafCombatZone:getRadioParentPath()
    return self.radioParentPath
end

function VeafCombatZone:setRadioGroupName(value)
    self.radioGroupName = value
    return self
end

function VeafCombatZone:getRadioGroupName()
    return self.radioGroupName
end

function VeafCombatZone:addSpawnedGroup(groupOrName)
    local groupName = groupOrName
    if type(groupName) ~= "string" then
        groupName = tostring(groupName)
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:addSpawnedGroup(%s)",veaf.p(self.missionEditorZoneName), veaf.p(groupName)))
    if not self.spawnedGroups then
        self.spawnedGroups = {}
    end
    table.insert(self.spawnedGroups, groupName)
    return self
end

function VeafCombatZone:getSpawnedGroups()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:getSpawnedGroups()",veaf.p(self.missionEditorZoneName)))
-- LOGGING DISABLED WHEN COMPILING(veaf.serialize("self.spawnedGroups", self.spawnedGroups))
    return self.spawnedGroups
end

function VeafCombatZone:clearSpawnedGroups()
    self.spawnedGroups = {}
    return self
end

function VeafCombatZone:addDelayedSpawner(id)
-- LOGGING DISABLED WHEN COMPILING("VeafCombatZone[%s]:addDelayedSpawner(%s)", veaf.p(self.missionEditorZoneName), veaf.p(id))
    if not self.delayedSpawners then
        self.delayedSpawners = {}
    end
    table.insert(self.delayedSpawners, id)
    return self
end

function VeafCombatZone:getDelayedSpawners()
-- LOGGING DISABLED WHEN COMPILING("VeafCombatZone[%s]:getDelayedSpawners()", veaf.p(self.missionEditorZoneName))
-- LOGGING DISABLED WHEN COMPILING("self.delayedSpawners=%s", self.delayedSpawners)
    return self.delayedSpawners
end

function VeafCombatZone:clearDelayedSpawners()
    self.delayedSpawners = {}
    return self
end

function VeafCombatZone:addZoneElement(element)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:addZoneElement(%s)",veaf.p(self.missionEditorZoneName), veaf.p(element:getName())))
    if not self.elements then
        self.elements = {}
    end
    if not self.elementGroups then
        self.elementGroups = {}
    end
    table.insert(self.elements, element)
    if not self.elementGroups[element:getSpawnGroup()] then
        local elementGroup = {}
        elementGroup.spawnGroup = element:getSpawnGroup()
        elementGroup.spawnCount = element:getSpawnCount()
        elementGroup.elements = {}
        self.elementGroups[element:getSpawnGroup()] = elementGroup
    end
    local elementGroup = self.elementGroups[element:getSpawnGroup()]
    table.insert(elementGroup.elements, element)
    return self
end

function VeafCombatZone:getZoneElements()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:getZoneElement()",veaf.p(self.missionEditorZoneName)))
-- LOGGING DISABLED WHEN COMPILING(veaf.serialize("self.elements", self.elements))
    return self.elements
end

function VeafCombatZone:getZoneElementsGroups()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:getZoneElementsGroups()",veaf.p(self.missionEditorZoneName)))
    return self.elementGroups
end

-- get the list of chained combat zones; this is are list of combat zones, that are activated randomly
function VeafCombatZone:getChainedCombatZones()
    if not self.chainedCombatZones then
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:getChainedCombatZones() - Initializing",veaf.p(self.missionEditorZoneName)))
        self.chainedCombatZones = {}
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:getChainedCombatZones() = %s",veaf.p(self.missionEditorZoneName), veaf.p(self.chainedCombatZones)))
    return self.chainedCombatZones
end

-- add a chained combat zone (by name); the zone does not have to exist at the time the function is called
function VeafCombatZone:addChainedCombatZone(combatZoneName)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:addChainedCombatZone([%s])",veaf.p(self.missionEditorZoneName), veaf.p(combatZoneName)))
    table.insert(self:getChainedCombatZones(), combatZoneName)
    return self
end

-- get the next chained combat zone; if the list is more than 1 zone long, get one at random
function VeafCombatZone:getNextChainedCombatZone()
    local nextZoneName = veaf.randomlyChooseFrom(self:getChainedCombatZones())
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:getNextChainedCombatZone() = [%s]",veaf.p(self.missionEditorZoneName), veaf.p(nextZoneName)))
    return nextZoneName
end

-- get the delay (in seconds) between the end of this combat zone and the start of the other; if the set value was a randomizable numeric, randomize it
function VeafCombatZone:getChainedCombatZonesDelay()
    return veaf.getRandomizableNumeric(self.chainedCombatZonesDelay or 0)
end

-- set the delay (in seconds) between the end of this combat zone and the start of the other; can be a randomizable numeric (e.g. "1-5")
function VeafCombatZone:setChainedCombatZonesDelay(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:setChainedCombatZonesDelay([%s])",veaf.p(self.missionEditorZoneName), veaf.p(value)))
    if not value then value = 0 end
    self.chainedCombatZonesDelay = value
    return self
end

---
--- other methods
---
function VeafCombatZone:scheduleWatchdogFunction()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:scheduleWatchdogFunction()",veaf.p(self.missionEditorZoneName)))
    if self:isCompletable() then
        self.watchdogFunctionId = mist.scheduleFunction(veafCombatZone.CompletionCheck,{self.missionEditorZoneName},timer.getTime()+veafCombatZone.SecondsBetweenWatchdogChecks)
    end
    return self
end

function VeafCombatZone:unscheduleWatchdogFunction()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:unscheduleWatchdogFunction()",veaf.p(self.missionEditorZoneName)))
    if self.watchdogFunctionId then
        mist.removeFunction(self.watchdogFunctionId)
    end
    self.watchdogFunctionId = nil
    return self
end

function VeafCombatZone:addObjective(value)
    table.insert(self.objectives, value)
    return self
end

function VeafCombatZone:addDefaultObjectives()
    -- TODO
    return self
end

function VeafCombatZone:initialize()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:initialize()",veaf.p(self.missionEditorZoneName)))

    -- check parameters
    if not self.missionEditorZoneName then
        return self
    else
        self.triggerZone = veaf.getTriggerZone(self.missionEditorZoneName)
        if not self.triggerZone then
            local message = string.format("Trigger zone [%s] does not exist in the mission !",veaf.p(self.missionEditorZoneName))
            veaf.loggers.get(veafCombatZone.Id):error(message)
            trigger.action.outText(message,5)
        return self
        end
    end
    if not self.friendlyName then
        self:setFriendlyName(self.missionEditorZoneName)
    end
    if #self.objectives == 0 then
        self:addDefaultObjectives()
    end

    -- find the trigger zone center
    self.zoneCenter = mist.utils.zoneToVec3(self.missionEditorZoneName)
    if not self.zoneCenter then
        local message = string.format("Trigger zone [%s] does not exist in the mission !",veaf.p(self.missionEditorZoneName))
        veaf.loggers.get(veafCombatZone.Id):error(message)
        trigger.action.outText(message,5)
        return self
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("zone center = [%s]",veaf.vecToString(self.zoneCenter)))

    -- find units in the trigger zone
    local units
    units, _ = veaf.safeUnpack(self:findUnitsInCombatZone())

    -- process special commands in the units 
    local alreadyAddedGroups = {}
    for _,unit in pairs(units) do
        local zoneElement = VeafCombatZoneElement:new()
        zoneElement:setCoalition(unit:getCoalition())
        local unitName = unit:getName()
-- LOGGING DISABLED WHEN COMPILING(string.format("processing unit [%s] of coalition [%d]", unitName, unit:getCoalition()))
        zoneElement:setPosition(unit:getPosition().p)
        local spawnRadius, command, spawnChance, spawnGroup, spawnCount, spawnDelay
        _, _, spawnRadius = unitName:lower():find("#spawnradius%s*=%s*(%d+)")
        _, _, command = unitName:lower():find("#command%s*=%s*\"([^\"]+)\"")
        _, _, spawnChance = unitName:lower():find("#spawnchance%s*=%s*(%d+)")
        _, _, spawnGroup = unitName:lower():find("#spawngroup%s*=%s*\"([^\"]+)\"")
        _, _, spawnCount = unitName:lower():find("#spawncount%s*=%s*(%d+)")
        _, _, spawnDelay = unitName:lower():find("#spawndelay%s*=%s*(%d+)")
        if spawnRadius then
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnRadius = [%d]", spawnRadius))
            zoneElement:setSpawnRadius(spawnRadius)
        end
        if spawnChance then
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnChance = [%d]", spawnChance))
            zoneElement:setSpawnChance(spawnChance)
        end
        if spawnCount then
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnCount = [%d]", spawnCount))
            zoneElement:setSpawnCount(spawnCount)
        end
        if spawnGroup then
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnGroup = [%s]", spawnGroup))
            zoneElement:setSpawnGroup(spawnGroup)
        end
        if spawnDelay then
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnDelay = [%s]", spawnDelay))
            zoneElement:setSpawnDelay(spawnDelay)
        end
        if command then
            -- it's a fake unit transporting a VEAF command
-- LOGGING DISABLED WHEN COMPILING(string.format("command = [%s]", command))
            zoneElement:setVeafCommand(command)
            local groupName = unit:getGroup():getName()
            zoneElement:setName(groupName)
-- LOGGING DISABLED WHEN COMPILING(string.format("groupName = [%s]", groupName))
            local route = mist.getGroupRoute(groupName, 'task')
            zoneElement:setRoute(route)
            if not zoneElement:getSpawnGroup() then zoneElement:setSpawnGroup(groupName) end -- default the spawn group to the group name in case there is no spawn group  defined
        else
            -- it's a group or a static unit
            local groupName = nil
            local objectCategory = Object.getCategory(unit)
-- LOGGING DISABLED WHEN COMPILING("objectCategory=%s", veaf.p(objectCategory))
            if objectCategory == 1 then
                local unitCategory = Unit.getCategory(unit)
-- LOGGING DISABLED WHEN COMPILING("unitCategory=%s", veaf.p(unitCategory))
            end
            if objectCategory == 3 or objectCategory == 6 then -- 3 is static objects, 6 is cargo (a kind of static object)
                groupName = unitName -- default for static objects = groups themselves
                zoneElement:setDcsStatic(true)
                if not zoneElement:getSpawnRadius() then
                    zoneElement:setSpawnRadius(veafCombatZone.DefaultSpawnRadiusForStatics)
                end
            else
                groupName = unit:getGroup():getName()
                zoneElement:setDcsGroup(true)
                if not zoneElement:getSpawnRadius() then
                    zoneElement:setSpawnRadius(veafCombatZone.DefaultSpawnRadiusForUnits)
                end
            end
            if not zoneElement:getSpawnGroup() then zoneElement:setSpawnGroup(groupName) end -- default the spawn group to the group name in case there is no spawn group  defined
            if not alreadyAddedGroups[groupName] then
                -- add a group element
-- LOGGING DISABLED WHEN COMPILING(string.format("adding group [%s]", groupName))
                alreadyAddedGroups[groupName] = groupName
                zoneElement:setName(groupName)
            else
-- LOGGING DISABLED WHEN COMPILING(string.format("skipping group [%s]", groupName))
                zoneElement = nil -- don't add this element, it's a group that has already been added
            end
        end

        if zoneElement then self:addZoneElement(zoneElement) end
    end

    -- deactivate the zone
-- LOGGING DISABLED WHEN COMPILING("desactivate the zone")
    self:desactivate()

    -- remove all units in the trigger zone (we want it CLEAN !)
    local _, groupNames = veaf.safeUnpack(self:findUnitsInCombatZone())
    if (groupNames) then
        for _, groupName in pairs(groupNames) do

-- LOGGING DISABLED WHEN COMPILING(string.format("destroying group [%s]",groupName))
            local group = Group.getByName(groupName)
            if not group then
                group = StaticObject.getByName(groupName)
            end
            if group then
                group:destroy()
            end
        end
    end
    return self
end

function VeafCombatZone:getInformation()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:getInformation()",veaf.p(self.missionEditorZoneName)))
    local message =      "COMBAT ZONE "..self:getFriendlyName().." \n\n"
    if (self:getBriefing()) then
        message = message .. "BRIEFING: \n"
        message = message .. self:getBriefing()
        message = message .. "\n\n"
    end
    if self:isActive() then

        -- generate information dispatch
        local nbShipsR = 0
        local nbVehiclesR = 0
        local nbInfantryR = 0
        local nbStaticsR = 0
        local nbShipsB = 0
        local nbVehiclesB = 0
        local nbInfantryB = 0
        local nbStaticsB = 0
        local unitsByTypeR = {}
        local unitsByTypeB = {}

        for _, groupName in pairs(self:getSpawnedGroups()) do
            local group = Group.getByName(groupName)
            if group then
                for _, u in pairs(group:getUnits()) do
                    local coa = u:getCoalition()
                    if Object.getCategory(u) == 3 then
                        if coa == 1 then
                            nbStaticsR = nbStaticsR + 1
                        elseif coa == 2 then
                            nbStaticsB = nbStaticsB + 1
                        end
                    else
                        local typeName = u:getTypeName()
                        if typeName then
                            local unit = veafUnits.findUnit(typeName)
                            if unit then
                                if coa == 1 then
                                    if not(unitsByTypeR[typeName]) then
                                        unitsByTypeR[typeName] = 0
                                    end
                                    unitsByTypeR[typeName] = unitsByTypeR[typeName] + 1
                                    if unit.vehicle then
                                        nbVehiclesR = nbVehiclesR + 1
                                    elseif unit.naval then
                                        nbShipsR = nbShipsR + 1
                                    else
                                        nbInfantryR = nbInfantryR + 1
                                    end
                                elseif coa == 2 then
                                    if not(unitsByTypeB[typeName]) then
                                        unitsByTypeB[typeName] = 0
                                    end
                                    unitsByTypeB[typeName] = unitsByTypeB[typeName] + 1
                                    if unit.vehicle then
                                        nbVehiclesB = nbVehiclesB + 1
                                    elseif unit.naval then
                                        nbShipsB = nbShipsB + 1
                                    else
                                        nbInfantryB = nbInfantryB + 1
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end

        if nbShipsB+nbStaticsB+nbVehiclesB+nbInfantryB > 0 and self:isShowUnitsList() then
            local msgs = {}
            if nbShipsB > 0 then
                table.insert(msgs, nbShipsB .. " ship(s)")
            end
            if nbStaticsB > 0 then
                table.insert(msgs, nbStaticsB .. " structure(s)")
            end
            if nbVehiclesB > 0 then
                table.insert(msgs, nbVehiclesB .. " vehicle(s)")
            end
            if nbInfantryB > 0 then
                table.insert(msgs, nbInfantryB .. " soldier(s)")
            end
            message = message .. "FRIENDS: ".. table.concat(msgs, ",") .." remaining.\n"
            if self:isTraining() then
                local firstUnit = true
                for name, count in pairs(unitsByTypeB) do
                    local separator = ", "
                    if firstUnit then
                        separator = ""
                        firstUnit = false
                    end
                    message = message .. string.format("%s%d %s",separator, count, name)
                end
                message = message .. "\n"
            end
        end
        if nbShipsR+nbStaticsR+nbVehiclesR+nbInfantryR > 0 and self:isShowUnitsList() then
            local msgs = {}
            if nbShipsR > 0 then
                table.insert(msgs, nbShipsR .. " ship(s)")
            end
            if nbStaticsR > 0 then
                table.insert(msgs, nbStaticsR .. " structure(s)")
            end
            if nbVehiclesR > 0 then
                table.insert(msgs, nbVehiclesR .. " vehicle(s)")
            end
            if nbInfantryR > 0 then
                table.insert(msgs, nbInfantryR .. " soldier(s)")
            end
            message = message .. "ENEMIES: ".. table.concat(msgs, ",") .." remaining.\n"
            if self:isTraining() then
                local firstUnit = true
                for name, count in pairs(unitsByTypeR) do
                    local separator = ", "
                    if firstUnit then
                        separator = ""
                        firstUnit = false
                    end
                    message = message .. string.format("%s%d %s",separator, count, name)
                end
                message = message .. "\n"
            end
        end
        message = message .. "\n"

        if self:isShowZonePositionInfo() then
            -- add coordinates and position from bullseye
            local zoneCenter = self:getCenter()
            local lat, lon = coord.LOtoLL(zoneCenter)
            local mgrsString = mist.tostringMGRS(coord.LLtoMGRS(lat, lon), 3)
            local bullseye = mist.utils.makeVec3(mist.DBs.missionData.bullseye.blue, 0)
            local vec = {x = zoneCenter.x - bullseye.x, y = zoneCenter.y - bullseye.y, z = zoneCenter.z - bullseye.z}
            local dir = mist.utils.round(mist.utils.toDegree(mist.utils.getDir(vec, bullseye)), 0)
            local dist = mist.utils.get2DDist(zoneCenter, bullseye)
            local distMetric = mist.utils.round(dist/1000, 0)
            local distImperial = mist.utils.round(mist.utils.metersToNM(dist), 0)
            local fromBullseye = string.format('%03d', dir) .. ' for ' .. distMetric .. 'km /' .. distImperial .. 'nm'

            message = message .. "LAT LON (decimal): " .. mist.tostringLL(lat, lon, 2) .. ".\n"
            message = message .. "LAT LON (DMS)    : " .. mist.tostringLL(lat, lon, 0, true) .. ".\n"
            message = message .. "MGRS/UTM         : " .. mgrsString .. ".\n"
            message = message .. "FROM BULLSEYE    : " .. fromBullseye .. ".\n"
            message = message .. "\n"

            -- get altitude, qfe and wind information
            message = message .. veaf.weatherReport(zoneCenter, nil, true)
        end
    else
        message = message .. "zone is not yet active."
    end

    return message
end

function VeafCombatZone:spawnElement(zoneElement, now)
-- LOGGING DISABLED WHEN COMPILING("zoneElement=%s", zoneElement)
    if not now and zoneElement:getSpawnDelay() and type(zoneElement:getSpawnDelay()) == "number" then
        -- self-schedule
-- LOGGING DISABLED WHEN COMPILING("scheduling spawn of zoneElement=%s in %s seconds", zoneElement:getName(), zoneElement:getSpawnDelay())
        local id = mist.scheduleFunction(VeafCombatZone.spawnElement,{self, zoneElement, true}, timer.getTime()+zoneElement:getSpawnDelay())
        self:addDelayedSpawner(id)
    else
        -- spawn now
-- LOGGING DISABLED WHEN COMPILING("spawning zoneElement=%s now", zoneElement:getName())
        local position = zoneElement:getPosition()
        if zoneElement:getSpawnRadius() > 0 then
-- LOGGING DISABLED WHEN COMPILING(string.format("position=[%s]",veaf.vecToString(position)))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnRadius=[%s]",zoneElement:getSpawnRadius()))
            local mistP = mist.getRandPointInCircle(position, zoneElement:getSpawnRadius())
-- LOGGING DISABLED WHEN COMPILING(string.format("mistP=[%s]",veaf.vecToString(mistP)))
            position = {x = mistP.x, y = position.y, z = mistP.y}
        end
        if zoneElement:isDcsStatic() or zoneElement:isDcsGroup() then
-- LOGGING DISABLED WHEN COMPILING(string.format("respawning group [%s] at position [%s]",zoneElement:getName(), veaf.vecToString(position)))
            local vars = {}
            vars.gpName = zoneElement:getName()
            vars.name = zoneElement:getName()
            vars.route = mist.getGroupRoute(vars.gpName, 'task')
            vars.action = 'respawn'
            vars.point = position
            local newGroup = mist.teleportToPoint(vars)
            if type(newGroup) == 'table' then
-- LOGGING DISABLED WHEN COMPILING(string.format("[%s]:activate() - mist.teleportToPoint([%s])", self:getMissionEditorZoneName(), zoneElement:getName()))
                self:addSpawnedGroup(newGroup.name)
                veaf.readyForCombat(newGroup.name)
            else
-- LOGGING DISABLED WHEN COMPILING(string.format("[%s]:activate() - mist.teleportToPoint([%s]) failed", self:getMissionEditorZoneName(), zoneElement:getName()))
            end
        elseif zoneElement:getVeafCommand() then
-- LOGGING DISABLED WHEN COMPILING(string.format("executing command [%s] at position [%s]",zoneElement:getName(), veaf.vecToString(position)))
            local spawnedGroups = {}
            veafInterpreter.execute(zoneElement:getVeafCommand(), position, zoneElement:getCoalition(), nil, spawnedGroups)
            for _, newGroup in pairs(spawnedGroups) do
-- LOGGING DISABLED WHEN COMPILING(string.format("[%s].addSpawnedGroup", zoneElement:getName()))
                self:addSpawnedGroup(newGroup)
-- LOGGING DISABLED WHEN COMPILING(string.format("newGroup = [%s]", newGroup))
                local route = zoneElement:getRoute()
-- LOGGING DISABLED WHEN COMPILING(string.format("got route"))
                local result = mist.goRoute(newGroup, route)
-- LOGGING DISABLED WHEN COMPILING(string.format("sent group on its way"))
            end
        end
    end
end

-- activate the zone
function VeafCombatZone:activate()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:activate()",self:getMissionEditorZoneName()))
    self:setActive(true)

    for _, zoneElementGroup in pairs(self:getZoneElementsGroups()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("processing spawnGroup [%s]",zoneElementGroup.spawnGroup))
        local spawnCount = zoneElementGroup.spawnCount
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnCount = [%d]",spawnCount))
        local tries = 10
        local alreadySpawnedElements = {}
        local shuffledIndexes = {}
        for i=1,#zoneElementGroup.elements do
            local zoneElement = zoneElementGroup.elements[i]
            alreadySpawnedElements[zoneElement:getName()]=false
            table.insert(shuffledIndexes, i)
        end
        veaf.shuffle(shuffledIndexes)
        while spawnCount > 0 and tries > 0 do
-- LOGGING DISABLED WHEN COMPILING(string.format("tries = [%d]",tries))
            tries = tries - 1

            for i=1,#shuffledIndexes do
                local zoneElement = zoneElementGroup.elements[shuffledIndexes[i]]
                if spawnCount > 0 then
                    if not alreadySpawnedElements[zoneElement:getName()] then
-- LOGGING DISABLED WHEN COMPILING(string.format("processing element [%s]",zoneElement:getName()))
                        local chance = math.random(0, 100)
                        if tries == 1 then chance = 0 end -- force chance if in the last try
-- LOGGING DISABLED WHEN COMPILING(string.format("chance = [%d]",chance))
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnChance = [%d]",zoneElement:getSpawnChance()))
                        if chance <= zoneElement:getSpawnChance() then
-- LOGGING DISABLED WHEN COMPILING(string.format("chance hit (%d <= %d)",chance, zoneElement:getSpawnChance()))
                            spawnCount = spawnCount - 1
                            alreadySpawnedElements[zoneElement:getName()]=true
                            self:spawnElement(zoneElement)
                        else
-- LOGGING DISABLED WHEN COMPILING(string.format("chance missed (%d > %d)",chance, zoneElement:getSpawnChance()))
                        end
                    else
-- LOGGING DISABLED WHEN COMPILING(string.format("already spawned [%s]",zoneElement:getName()))
                    end
                end
            end
        end
    end

    -- start the completion watchdog
    self:scheduleWatchdogFunction()

    -- refresh the radio menu
    self:updateRadioMenu()

    return self
end

-- activate the next chained zone (if any)
function VeafCombatZone:activateNextChainedZone()
    local nextZoneName = self:getNextChainedCombatZone()
    local nextZone = veafCombatZone.GetZone(nextZoneName)
    if not nextZone then return self end
    local delay = self:getChainedCombatZonesDelay()
-- LOGGING DISABLED WHEN COMPILING(string.format("activating the next chained zone ([%s]) in %s seconds)",veaf.p(nextZoneName), veaf.p(delay)))
    mist.scheduleFunction(VeafCombatZone.activate,{nextZone},timer.getTime()+delay)
    return self
end

-- desactivate the zone
function VeafCombatZone:desactivate()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:desactivate()",veaf.p(self.missionEditorZoneName)))
    self:setActive(false)
    self:unscheduleWatchdogFunction()

    for _, delayedSpawner in pairs(self:getDelayedSpawners()) do
-- LOGGING DISABLED WHEN COMPILING("unscheduling delayed spawner %s", delayedSpawner)
        mist.removeFunction(delayedSpawner)
    end
    self:clearDelayedSpawners()

    for _, groupName in pairs(self:getSpawnedGroups()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("trying to destroy group [%s]",groupName))
        local group = Group.getByName(groupName)
        if not group then
            group = StaticObject.getByName(groupName)
            if group then
-- LOGGING DISABLED WHEN COMPILING(string.format("found static [%s]",group:getName()))
            else
                veaf.loggers.get(veafCombatZone.Id):info(string.format("cannot find static [%s]",groupName))
            end
        end
        if group then
-- LOGGING DISABLED WHEN COMPILING(string.format("destroying group [%s]",group:getName()))
            group:destroy()
        end
    end
    self:clearSpawnedGroups()

    -- refresh the radio menu
    self:updateRadioMenu()

    return self
end

-- check if there are still units in zone
function VeafCombatZone:completionCheck()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:completionCheck()",veaf.p(self.missionEditorZoneName)))
    if not self:isCompletable() then
        return
    end
    local nbUnitsR = 0
    local nbUnitsB = 0

    for _, groupName in pairs(self:getSpawnedGroups()) do
        local group = Group.getByName(groupName)
        if group then
            for _, unit in pairs(group:getUnits()) do
                local coa = unit:getCoalition()
                if coa == 1 then
                    nbUnitsR = nbUnitsR + 1
                elseif coa == 2 then
                    nbUnitsB = nbUnitsB + 1
                end
            end
        else
            local static = StaticObject.getByName(groupName)
            if static then
                local coa = static:getCoalition()
                if coa == 1 then
                    nbUnitsR = nbUnitsR + 1
                elseif coa == 2 then
                    nbUnitsB = nbUnitsB + 1
                end
            end
        end
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("nbUnitsB=%d",nbUnitsB))
-- LOGGING DISABLED WHEN COMPILING(string.format("nbUnitsR=%d",nbUnitsR))

    if nbUnitsR == 0 then
        -- everyone is dead, let's end this mess
        if veafCombatZone.EventMessages.CombatZoneComplete then
            local message = string.format(veafCombatZone.EventMessages.CombatZoneComplete, self:getFriendlyName())
            trigger.action.outText(message, 15)
        end
        -- call the onCompleted hook
        if self.onCompletedHook then self.onCompletedHook(self) end
        -- desactivate the zone
        self:desactivate()
        -- activate the next chained zone if needed
        self:activateNextChainedZone()
    else
        -- reschedule
        self:scheduleWatchdogFunction()
    end
end


-- pop a smoke marker over the zone
function VeafCombatZone:popSmoke()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:popSmoke()",veaf.p(self.missionEditorZoneName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("self:getCenter()=%s",veaf.vecToString(self:getCenter())))
    local smokePoint = self:getCenter()
    if self:isTraining() then
        -- compute the barycenter of all remaining units
        local totalPosition = {x = 0,y = 0,z = 0}
        local units, _ = veaf.safeUnpack(self:findUnitsInCombatZone())
        for count = 1,#units do
            if units[count] then
                totalPosition = mist.vec.add(totalPosition,Unit.getPosition(units[count]).p)
            end
        end
        if #units > 0 then
            smokePoint = mist.vec.scalar_mult(totalPosition,1/#units)
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("smokePoint=%s",veaf.vecToString(smokePoint)))
    veafSpawn.spawnSmoke(smokePoint, trigger.smokeColor.Red)
    self.smokeResetFunctionId = mist.scheduleFunction(veafCombatZone.SmokeReset,{self.missionEditorZoneName},timer.getTime()+veafCombatZone.SecondsBetweenSmokeRequests)
    trigger.action.outText(string.format(veafCombatZone.EventMessages.PopSmokeRequest, self:getFriendlyName()),5)
    self:updateRadioMenu()

    return self
end

-- pop an illumination  flare over a zone
function VeafCombatZone:popFlare()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:popFlare()",veaf.p(self.missionEditorZoneName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("self:getCenter()=%s",veaf.vecToString(self:getCenter())))

    veafSpawn.spawnIlluminationFlare(self:getCenter())
    self.flareResetFunctionId = mist.scheduleFunction(veafCombatZone.FlareReset,{self.missionEditorZoneName},timer.getTime()+veafCombatZone.SecondsBetweenFlareRequests)
    trigger.action.outText(string.format(veafCombatZone.EventMessages.UseFlareRequest, self:getFriendlyName()),5)
    self:updateRadioMenu()

    return self
end

-- updates the radio menu according to the zone state
function VeafCombatZone:updateRadioMenu(inBatch)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatZone[%s]:updateRadioMenu(%s)",veaf.p(self.missionEditorZoneName), tostring(inBatch)))
-- LOGGING DISABLED WHEN COMPILING("radioGroupName=%s", self.radioGroupName)

    -- do not update the radio menu if not yet initialized or if we don't want to
    if not self.radioParentPath or not self.enableRadioMenu then
        return self
    end

    local shouldAddSubMenu = self.enableUserActivation or self.active
-- LOGGING DISABLED WHEN COMPILING("User activation enabled : %s, Zone active: %s, shouldAddSubMenu: %s", veaf.p(self.enableUserActivation), veaf.p(self.active), veaf.p(shouldAddSubMenu))

    -- reset the radio menu
    if self.radioRootPath then
-- LOGGING DISABLED WHEN COMPILING("Remove the radio submenu %s", veaf.p(self:getRadioMenuName()))
        veafRadio.delSubmenu(self:getRadioMenuName(), self.radioParentPath)
        veafRadio.delSubmenu(self:getRadioMenuName(true), self.radioParentPath)
        self.radioRootPath = nil
    end
    if shouldAddSubMenu then
-- LOGGING DISABLED WHEN COMPILING("add the radio submenu")
        self.radioRootPath = veafRadio.addSubMenu(self:getRadioMenuName(self:isActive()), self.radioParentPath)
    end

    if shouldAddSubMenu then
        -- populate the radio menu
-- LOGGING DISABLED WHEN COMPILING("populate the radio menu")
        -- global commands
        veafRadio.addCommandToSubmenu("Get info", self.radioRootPath, veafCombatZone.GetInformationOnZone, self.missionEditorZoneName, veafRadio.USAGE_ForGroup)
        if self:isActive() then
            -- zone is active, set up accordingly (desactivate zone, get information, pop smoke, etc.)
-- LOGGING DISABLED WHEN COMPILING("zone is active")
            if(self.enableUserActivation) then
                if self:isTraining() then
                    veafRadio.addCommandToSubmenu('Desactivate zone', self.radioRootPath, veafCombatZone.DesactivateZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
                else
                    veafRadio.addSecuredCommandToSubmenu('Desactivate zone', self.radioRootPath, veafCombatZone.DesactivateZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
                end
            end
            if self.enableSmokeAndFlare then
                if self.smokeResetFunctionId then
                    veafRadio.addCommandToSubmenu('Smoke not available', self.radioRootPath, veaf.emptyFunction, nil, veafRadio.USAGE_ForAll)
                else
                    veafRadio.addCommandToSubmenu('Request RED smoke on target', self.radioRootPath, veafCombatZone.SmokeZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
                end
                if self.flareResetFunctionId then
                    veafRadio.addCommandToSubmenu('Flare not available', self.radioRootPath, veaf.emptyFunction, nil, veafRadio.USAGE_ForAll)
                else
                    veafRadio.addCommandToSubmenu('Request illumination flare on target', self.radioRootPath, veafCombatZone.LightUpZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
                end
            end
        else
            -- zone is not active, set up accordingly (activate zone)
-- LOGGING DISABLED WHEN COMPILING("zone is not active")
            if self.enableUserActivation then
                if self:isTraining() then
                    veafRadio.addCommandToSubmenu('Activate zone', self.radioRootPath, veafCombatZone.ActivateZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
                else
                    veafRadio.addSecuredCommandToSubmenu('Activate zone', self.radioRootPath, veafCombatZone.ActivateZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
                end
            end
        end
    end

    if not inBatch then veafRadio.refreshRadioMenu() end
    return self
end

---
--- lists all units and statics (and their groups names) in a combat zone that also match the combat zone name
---
function VeafCombatZone:findUnitsInCombatZone()
    local unitsNames = veaf.getUnitsNamesOfCoalition(true, nil) -- include statics, all coalitions
    local units = {}
    local resultUnits = {}
    local groupNames = {}
    local alreadyAddedGroups = {}
    local triggerZone = self:getTriggerZone()
    local upperTriggerzoneName = self:getMissionEditorZoneName():upper()

    if not triggerZone then
        return self
    end

-- LOGGING DISABLED WHEN COMPILING("#unitsNames=%s", veaf.p(#unitsNames))

-- LOGGING DISABLED WHEN COMPILING("triggerZone.type=%s", veaf.p(triggerZone.type))
    if triggerZone.type == 0 then -- circular
        units = mist.getUnitsInZones(unitsNames, {self:getMissionEditorZoneName()})
    elseif triggerZone.type == 2 then -- quad point
        units = mist.getUnitsInPolygon(unitsNames, triggerZone.verticies)
    end

-- LOGGING DISABLED WHEN COMPILING("#units=%s", veaf.p(#units))

    for _, unit in pairs(units) do
        local unitName = unit:getName()
        local objectCategory = Object.getCategory(unit)
        local groupName = nil
-- LOGGING DISABLED WHEN COMPILING(string.format("processing unit [%s]", unitName))
-- LOGGING DISABLED WHEN COMPILING(string.format("objectCategory = [%d]", objectCategory))
        if objectCategory == 3 or objectCategory == 6 then -- 3 is static objects, 6 is cargo (a kind of static object)
            groupName = unitName -- default for static objects = groups themselves
        else
            groupName = unit:getGroup():getName()
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("groupName = %s", groupName))
        if string.sub(groupName:upper(),1,string.len(upperTriggerzoneName))==upperTriggerzoneName then
            resultUnits[#resultUnits + 1] = unit
            if not alreadyAddedGroups[groupName] then
                alreadyAddedGroups[groupName] = groupName
                groupNames[#groupNames + 1] = groupName
            end
        end
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("found %d units (%d groups) in zone", #resultUnits, #groupNames))
    return {resultUnits, groupNames}
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafCombatOperationTaskingOrder object
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafCombatOperationTaskingOrder = {
    -- combat zone of the tasking order
    zone = nil,
    -- what tasking orders needs to be completed before starting this one
    requiredCompleteNames = {}
}
VeafCombatOperationTaskingOrder.__index = VeafCombatOperationTaskingOrder

function VeafCombatOperationTaskingOrder:new(zone)
    local self = setmetatable({}, VeafCombatOperationTaskingOrder)
    self.zone = zone
    self.requiredCompleteNames = {}

    return self
end

function VeafCombatOperationTaskingOrder:setRequiredComplete(requiredCompleteNames)
    self.requiredCompleteNames = requiredCompleteNames
    return self
end

function VeafCombatOperationTaskingOrder:getZone()
    return self.zone
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafCombatOperation object
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafCombatOperation = VeafCombatZone:new()

function VeafCombatOperation:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object

    -- operation name (human-friendly)
    objectToCreate.friendlyName = nil
    -- technical operation name (named missionEditorZoneName not to break all zone stuffs)
    objectToCreate.missionEditorZoneName = nil
    -- mission briefing
    objectToCreate.briefing = nil
    -- operation is active
    objectToCreate.active = false
    -- list of zones used as tasking order
    objectToCreate.taskingOrderList = {}
    -- dictionnary of zones used as tasking order
    objectToCreate.taskingOrderDict = {}
    -- combat zone that we want to be completed before continuing operation
    objectToCreate.primaryTaskingOrders = {}
    -- the watchdog function checks for zone objectives completion
    objectToCreate.watchdogFunctionId = nil
    -- function to call when combat zone is over. The function is passed self combat zone
    objectToCreate.onCompletedHook = nil
    -- how many tasks were complete so far
    objectToCreate.currentCompletedTaskingOrderCount = 0

    return objectToCreate
end

---
--- setters and getters
---
function VeafCombatOperation:setOnCompletedHook(onCompletedFunction)
    self.onCompletedHook = onCompletedFunction
    return self
end

function VeafCombatOperation:getRadioMenuName()
    return self:getFriendlyName()
end

function VeafCombatOperation:getInformation()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:getInformation()",veaf.p(self.missionEditorZoneName) ))
    local message = "OPERATION "..self:getFriendlyName().." \n\n"
    if (self:getBriefing()) then
        message = message .. messageSeparator
        message = message .. self:getBriefing()
        message = message .. "\n\n"
    end

    if self:isActive() then
        message = message .. messageSeparator .. "Air Tasking Orders: \n"
        for _, primaryTaskingOrder in pairs(self.primaryTaskingOrders) do
            if primaryTaskingOrder.zone:isActive() then
                message = message .. primaryTaskingOrder:getZone():getFriendlyName() .. "\n"
            end
        end
    else
        message = message .. string.format(veafCombatZone.EventMessages.CombatOperationComplete, self:getFriendlyName())
    end

    return message
end

function VeafCombatOperation:addTaskingOrder(zone, requiredComplete)
    -- add requiredComplete in log
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:addTaskingOrder(%s)",veaf.p(self.missionEditorZoneName), veaf.p(zone.missionEditorZoneName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Adding combat zone %s to operation %s", zone.missionEditorZoneName, veaf.p(self.missionEditorZoneName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Tasks required before activation: %s", veaf.p(requiredComplete)))

    for _, mandatoryZoneName in pairs(requiredComplete or {}) do
        if(not self.taskingOrderDict[mandatoryZoneName]) then
            veaf.loggers.get(veafCombatZone.Id):error(string.format("Cannot add mandatory zone %s as it is not in known zones", veaf.p(mandatoryZoneName)))
            return self
        end
    end

-- LOGGING DISABLED WHEN COMPILING("remove task order from combat zone radio menu")
    zone:disableRadioMenu()

    -- adds tasking order to the zone lists to make it accessible
    veafCombatZone.AddZone(zone)

    local newTaskingOrder = VeafCombatOperationTaskingOrder:new(zone)
        :setRequiredComplete(requiredComplete or {})

    table.insert(self.taskingOrderList, newTaskingOrder)
    self.taskingOrderDict[zone.missionEditorZoneName] = newTaskingOrder

    return self
end

-------------------
--- Other methods
-------------------
function VeafCombatOperation:scheduleWatchdogFunction()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:scheduleWatchdogFunction()", veaf.p(self.missionEditorZoneName)))
    self.watchdogFunctionId = mist.scheduleFunction(veafCombatZone.CompletionCheck,{self.missionEditorZoneName},timer.getTime()+veafCombatZone.SecondsBetweenWatchdogChecks)
    return self
end

function VeafCombatOperation:unscheduleWatchdogFunction()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:unscheduleWatchdogFunction()",veaf.p(self.missionEditorZoneName)))
    if self.watchdogFunctionId then
        mist.removeFunction(self.watchdogFunctionId)
    end
    self.watchdogFunctionId = nil
    return self
end

function VeafCombatOperation:updatePrimaryTasks()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:updatePrimaryTasks()",veaf.p(self.missionEditorZoneName)))

-- LOGGING DISABLED WHEN COMPILING("Clear primary tasks")
    self.primaryTaskingOrders = {}

-- LOGGING DISABLED WHEN COMPILING("Look for next tasks")
    local newPrimaryTasks = {}
    for _, candidateTaskingOrder in pairs(self.taskingOrderDict) do
        -- filter tasks that are not completed yet
        if candidateTaskingOrder:getZone():isActive() then
            local requirementFulfilled = true
            for _, requiredCombatZoneName in pairs(candidateTaskingOrder.requiredCompleteNames) do
                local requiredCombatZone = veafCombatZone.GetZone(requiredCombatZoneName)

                -- if any of required tasking order is active, then tasking order is not eligible
                if requiredCombatZone:isActive() then
                    requirementFulfilled = false
                    break
                end
            end

            if requirementFulfilled then
                table.insert(newPrimaryTasks, candidateTaskingOrder)
            end
        end
    end

    -- No task left, operation complete !
    if veaf.length(newPrimaryTasks) == 0 then
-- LOGGING DISABLED WHEN COMPILING("No tasks left")
        self:desactivate()


        if veafCombatZone.EventMessages.CombatOperationComplete then
            trigger.action.outText(string.format(veafCombatZone.EventMessages.CombatOperationComplete, self.friendlyName), 10)
        end
        return self
    end


-- LOGGING DISABLED WHEN COMPILING("Setting new primary tasks")
    self.primaryTaskingOrders = newPrimaryTasks
end

-- checks if primary tasks are completed to unlock next
function VeafCombatOperation:completionCheck()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:completionCheck()",veaf.p(self.missionEditorZoneName)))

    local completedTaskingOrderCount = 0
    -- if any of primary tasks is still active, then check is done
    for _, primaryTask in pairs(self.primaryTaskingOrders) do
        if not primaryTask:getZone():isActive() then
-- LOGGING DISABLED WHEN COMPILING(string.format("Primary task %s is completed",primaryTask:getZone():getFriendlyName()))
            completedTaskingOrderCount = completedTaskingOrderCount + 1
        end
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("%s completed out of %s, previous was %s",completedTaskingOrderCount, #self.primaryTaskingOrders, self.currentCompletedTaskingOrderCount))
    if completedTaskingOrderCount == #self.primaryTaskingOrders then
-- LOGGING DISABLED WHEN COMPILING("Primary tasks complete")
        self:updatePrimaryTasks()
        self:updateRadioMenu()
        completedTaskingOrderCount = 0
        if not self:isActive() then
            return self
        end
    end

-- LOGGING DISABLED WHEN COMPILING("Still got work to do.")

    if completedTaskingOrderCount ~= self.currentCompletedTaskingOrderCount then
-- LOGGING DISABLED WHEN COMPILING("New tasking order completed. Update radio.")
        self:updatePrimaryTasks()
        self:updateRadioMenu()
    end
    self.currentCompletedTaskingOrderCount = completedTaskingOrderCount

    -- reschedule
    self:scheduleWatchdogFunction()


    return self
end

function VeafCombatOperation:initialize()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:initialize()",veaf.p(self.missionEditorZoneName)))

    -- check parameters
    if not self.missionEditorZoneName then
        return self
    end
    if not self.friendlyName then
        self:setFriendlyName(self.missionEditorZoneName)
    end

    -- initializes  member combat zones and sets starting primary tasks
    for _, taskingOrder in pairs(self.taskingOrderDict) do
        taskingOrder:getZone():initialize()
    end


    -- deactivate the zone
-- LOGGING DISABLED WHEN COMPILING("desactivate the operation")
    self:desactivate()

    return self
end

-- activate the operation
function VeafCombatOperation:activate()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:activate()", veaf.p(self.missionEditorZoneName)))
    self:setActive(true)

    local primaryTasks = {}
    -- activates member combat zones and sets starting primary tasks
-- LOGGING DISABLED WHEN COMPILING("activate the operation's zones")
    for _, taskingOrder in pairs(self.taskingOrderDict) do
        taskingOrder:getZone():activate()

        -- selects combat zones with no requiredComplete combat zones
        if veaf.length(taskingOrder.requiredCompleteNames) == 0 then table.insert(primaryTasks, taskingOrder) end
    end

-- LOGGING DISABLED WHEN COMPILING("set primary task")
    self.primaryTaskingOrders = primaryTasks

    -- schedule the watchdog function
    self:scheduleWatchdogFunction()

    -- refresh the radio menu
    self:updateRadioMenu()

    return self
end

-- desactivate the operation
function VeafCombatOperation:desactivate()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:desactivate()",veaf.p(self.missionEditorZoneName)))
    self:setActive(false)

    -- unscheduel watchdog function
    self:unscheduleWatchdogFunction()

    -- refresh the radio menu
    self:updateRadioMenu()

    return self
end

-- updates the radio menu according to the zone state
function VeafCombatOperation:updateRadioMenu(inBatch)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafCombatOperation[%s]:updateRadioMenu(%s)",veaf.p(self.missionEditorZoneName), veaf.p(inBatch)))

    -- do not update the radio menu if not yet initialized
    if not veafCombatZone.rootPath then
        return self
    end

    local menuToFill = veafCombatZone.rootPath
    if(veafCombatZone.operationRootPath) then
        menuToFill = veafCombatZone.operationRootPath
    end

    -- reset the radio menu
    if self.radioRootPath then
-- LOGGING DISABLED WHEN COMPILING("reset the radio submenu")
        veafRadio.clearSubmenu(self.radioRootPath)
    else
-- LOGGING DISABLED WHEN COMPILING("add the radio submenu")
        self.radioRootPath = veafRadio.addSubMenu(self:getRadioMenuName(), menuToFill)
    end

    -- populate the radio menu
-- LOGGING DISABLED WHEN COMPILING("populate the radio menu")
    -- global commands
    veafRadio.addCommandToSubmenu("Get info", self.radioRootPath, veafCombatZone.GetInformationOnZone, self.missionEditorZoneName, veafRadio.USAGE_ForGroup)
    for _, taskingOrder in pairs(self.primaryTaskingOrders) do
        if taskingOrder.zone:isActive() then
-- LOGGING DISABLED WHEN COMPILING(string.format("Add briefing for %s, %s", taskingOrder.zone:getFriendlyName(), taskingOrder.zone:getMissionEditorZoneName()))
            veafRadio.addCommandToSubmenu("Briefing " .. taskingOrder.zone:getFriendlyName(), self.radioRootPath, veafCombatZone.GetInformationOnZone, taskingOrder.zone:getMissionEditorZoneName(), veafRadio.USAGE_ForGroup)
        else
-- LOGGING DISABLED WHEN COMPILING(string.format("Skip briefing for %s, %s as it is not active", taskingOrder.zone:getFriendlyName(), taskingOrder.zone:getMissionEditorZoneName()))
        end

    end

    if self:isActive() then
        -- zone is active, set up accordingly (desactivate zone, get information, pop smoke, etc.)
-- LOGGING DISABLED WHEN COMPILING("zone is active")

        -- veafRadio.addSecuredCommandToSubmenu('Desactivate zone', self.radioRootPath, veafCombatZone.DesactivateZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)

        -- if self.smokeResetFunctionId then 
        --     veafRadio.addCommandToSubmenu('Smoke not available', self.radioRootPath, veaf.emptyFunction, nil, veafRadio.USAGE_ForAll)
        -- else
        --     veafRadio.addCommandToSubmenu('Request RED smoke on target', self.radioRootPath, veafCombatZone.SmokeZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
        -- end
        -- if self.flareResetFunctionId then 
        --     veafRadio.addCommandToSubmenu('Flare not available', self.radioRootPath, veaf.emptyFunction, nil, veafRadio.USAGE_ForAll)
        -- else
        --     veafRadio.addCommandToSubmenu('Request illumination flare on target', self.radioRootPath, veafCombatZone.LightUpZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
        -- end
    else
        -- zone is not active, set up accordingly (activate zone)
-- LOGGING DISABLED WHEN COMPILING("zone is not active")

        -- veafRadio.addSecuredCommandToSubmenu('Activate zone', self.radioRootPath, veafCombatZone.ActivateZone, self.missionEditorZoneName, veafRadio.USAGE_ForAll)
    end

    if not inBatch then veafRadio.refreshRadioMenu() end
    return self
end


-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- global functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------
--- GLOBAL INTERFACE, working for both zones and operations
--------------------------------------------------------------------------------------------------------------

function veafCombatZone.GetZone(zoneName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.GetZone([%s])", veaf.p(zoneName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Searching for zone with name [%s]", veaf.p(zoneName)))
    if zoneName then
        local zone = veafCombatZone.zonesDict[zoneName:lower()]
        if not zone then
            local message = string.format("VeafCombatZone [%s] was not found !",zoneName)
            veaf.loggers.get(veafCombatZone.Id):error(message)
            trigger.action.outText(message,5)
        end
        return zone
    else
        return nil
    end
end

-- add a zone
function veafCombatZone.AddZone(zone)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.AddZone([%s])",veaf.p(veaf.ifnns(zone, "missionEditorZoneName"))))
    if zone then
        zone:initialize()
        table.insert(veafCombatZone.zonesList, zone)
        veafCombatZone.zonesDict[zone.missionEditorZoneName:lower()] = zone
        return zone
    else
        return nil
    end
end

-- activate a zone by number
function veafCombatZone.ActivateZoneNumber(number, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.ActivateZoneNumber([%s])",veaf.p(number)))
    local zone = veafCombatZone.zonesList[number]
    if zone then
        veafCombatZone.ActivateZone(zone:getMissionEditorZoneName(), silent)
    end
end

-- activate a zone
function veafCombatZone.ActivateZone(zoneName, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.ActivateZone([%s])", veaf.p(zoneName)))
    local zone = veafCombatZone.GetZone(zoneName)
    if zone then
        if zone:isActive() then
            if not silent then
                trigger.action.outText("VeafCombatZone "..zone:getFriendlyName().." is already active.", 10)
            end
            return
        end
        mist.scheduleFunction(zone.activate,{zone},timer.getTime()+1)
        if not silent then
            trigger.action.outText("VeafCombatZone "..zone:getFriendlyName().." has been activated.", 10)
            mist.scheduleFunction(veafCombatZone.GetInformationOnZone,{{zoneName}},timer.getTime()+2)
        end
        return zone
    else
        return nil
    end
end

-- desactivate a zone by number
function veafCombatZone.DesactivateZoneNumber(number, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.DesactivateZoneNumber([%s])",veaf.p(number)))
    local zone = veafCombatZone.zonesList[number]
    if zone then
        veafCombatZone.DesactivateZone(zone:getMissionEditorZoneName(), silent)
    end
end

-- desactivate a zone by name
function veafCombatZone.DesactivateZone(zoneName, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.DesactivateZone([%s])", veaf.p(zoneName)))
    local zone = veafCombatZone.GetZone(zoneName)
    if zone then
        if not(zone:isActive()) then
            if not silent then
                trigger.action.outText("VeafCombatZone "..zone:getFriendlyName().." is not active.", 10)
            end
            return
        end
        zone:desactivate()
        if not silent then
            trigger.action.outText("VeafCombatZone "..zone:getFriendlyName().." has been desactivated.", 10)
        end
        return zone
    else
        return nil
    end
end

-- print information about a zone
function veafCombatZone.GetInformationOnZone(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.GetInformationOnZone([%s])",veaf.p(parameters)))
    local zoneName, unitName = veaf.safeUnpack(parameters)
    local zone = veafCombatZone.GetZone(zoneName)
    if zone then
        local text = zone:getInformation()
        if unitName then
            veaf.outTextForGroup(unitName, text, 30)
        else
            trigger.action.outText(text, 30)
        end
        return zone
    else
        return nil
    end
end

-- pop a smoke over a zone
function veafCombatZone.SmokeZone(zoneName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.SmokeZone([%s])", veaf.p(zoneName)))
    local zone = veafCombatZone.GetZone(zoneName)
    if zone then
        zone:popSmoke()
        return zone
    else
        return nil
    end
end

-- pop an illumination  flare over a zone
function veafCombatZone.LightUpZone(zoneName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.LightUpZone([%s])", veaf.p(zoneName)))
    local zone = veafCombatZone.GetZone(zoneName)
    if zone then
        zone:popFlare()
        return zone
    else
        return nil
    end
end

-- reset the "pop smoke" menus
function veafCombatZone.SmokeReset(zoneName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.SmokeReset([%s])", veaf.p(zoneName)))
    local zone = veafCombatZone.GetZone(zoneName)
    if zone then
        zone.smokeResetFunctionId = nil
        zone:updateRadioMenu()
        return zone
    else
        return nil
    end
end

-- reset the "pop flare" menus
function veafCombatZone.FlareReset(zoneName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.FlareReset([%s])", veaf.p(zoneName)))
    local zone = veafCombatZone.GetZone(zoneName)
    if zone then
        zone.flareResetFunctionId = nil
        zone:updateRadioMenu()
        return zone
    else
        return nil
    end
end

-- call the completion watchdog methods
function veafCombatZone.CompletionCheck(zoneName)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafCombatZone.CompletionCheck([%s])", veaf.p(zoneName)))
    local zone = veafCombatZone.GetZone(zoneName)
    if zone then
        zone:completionCheck()
        return zone
    else
        return nil
    end
end

--------------------------------------------------------------------------------------------------------------
--- END OF GLOBAL INTERFACE
--------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Build the initial radio menu
function veafCombatZone.buildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("buildRadioMenu()")

    -- don't create an empty menu
    if veaf.length(veafCombatZone.zonesDict) == 0 then
        return
    end

    veafCombatZone.rootPath = veafRadio.addMenu(veafCombatZone.RadioMenuName)
    veafCombatZone.combatZoneRootPath = veafCombatZone.rootPath

    if not(veafRadio.skipHelpMenus) then
        veafRadio.addCommandToSubmenu("HELP", veafCombatZone.rootPath, veafCombatZone.help, nil, veafRadio.USAGE_ForGroup)
    end

    if(veafCombatZone.CombatZoneRadioMenuName) then
        veafCombatZone.combatZoneRootPath = veafRadio.addSubMenu(veafCombatZone.CombatZoneRadioMenuName, veafCombatZone.rootPath)
    end

    if(veafCombatZone.OperationRadioMenuName) then
        veafCombatZone.operationRootPath = veafRadio.addSubMenu(veafCombatZone.OperationRadioMenuName, veafCombatZone.rootPath)
    end

    -- sort the zones alphabetically
    local names = {}
    local sortedZones = {}
    for _, zone in pairs(veafCombatZone.zonesDict) do
        table.insert(sortedZones, {name=zone:getMissionEditorZoneName(), sort=zone:getFriendlyName()})
    end
    local function compare(a,b)
		if not(a) then
			a = {}
		end
		if not(a["sort"]) then
			a["sort"] = 0
		end
		if not(b) then
			b = {}
		end
		if not(b["sort"]) then
			b["sort"] = 0
		end
        return a["sort"] < b["sort"]
    end
    table.sort(sortedZones, compare)
    for i = 1, #sortedZones do
        table.insert(names, sortedZones[i].name)
    end

-- LOGGING DISABLED WHEN COMPILING("veafCombatZone.buildRadioMenu() - dumping names")
    for i = 1, #names do
-- LOGGING DISABLED WHEN COMPILING("veafCombatZone.buildRadioMenu().names -> " .. names[i])
    end

    for _, zoneName in pairs(names) do
        local zone = veafCombatZone.GetZone(zoneName)
        if zone:getRadioGroupName() then
            local radioGroup = veafCombatZone.radioGroupsDict[zone:getRadioGroupName()]
            if not radioGroup then
                -- create the radio group menu
                radioGroup = veafRadio.addSubMenu(zone:getRadioGroupName(), veafCombatZone.combatZoneRootPath)
-- LOGGING DISABLED WHEN COMPILING("created radio group %s", zone:getRadioGroupName())
                veafCombatZone.radioGroupsDict[zone:getRadioGroupName()] = radioGroup
            end
            zone:setRadioParentPath(radioGroup)
        else
            zone:setRadioParentPath(veafCombatZone.combatZoneRootPath)
        end
        zone:updateRadioMenu(true)
    end

    veafRadio.refreshRadioMenu()
end

function veafCombatZone.help(unitName)
    local text =
        'Combat zones are defined by the mission maker\n' ..
        'You can activate and desactivate them at will,\n' ..
        'as well as ask for information, JTAC laser and smoke. \n\n' ..
        'Combat operations are defined by the mission maker\n' ..
        'A combat operation is a series of combat zones to complete,\n' ..
        'You can ask information to get briefing and intel for current tasking orders.'
        veaf.outTextForGroup(unitName, text, 30)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafCombatZone.initialize()
    veaf.loggers.get(veafCombatZone.Id):info("Initializing module")
    veafCombatZone.buildRadioMenu()
end

veaf.loggers.get(veafCombatZone.Id):info(string.format("Loading version %s", veafCombatZone.Version))

------------------ END script veafCombatZone.lua  ------------------


------------------ START script veafGrass.lua  ------------------

------------------------------------------------------------------
-- VEAF grass functions for DCS World
-- By mitch (2018)
--
-- Features:
-- ---------
-- * Script to build units on FARPS and grass runways
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafGrass = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafGrass.Id = "GRASS"

--- Version.
veafGrass.Version = "2.7.4"

-- trace level, specific to this module
--veafGrass.LogLevel = "trace"

veaf.loggers.new(veafGrass.Id, veafGrass.LogLevel)

veafGrass.DelayForStartup = 2

veafGrass.RadiusAroundFarp = 2000

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------


------------------------------------------------------------------------------
-- veafGrass.buildGrassRunway
-- Build a grass runway from grassRunwayUnit
-- @param grassRunwayUnit a static unit object (right side)
-- @return a named point if successful
------------------------------------------------------------------------------
function veafGrass.buildGrassRunway(grassRunwayUnit, hiddenOnMFD)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafGrass.buildGrassRunway()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("grassRunwayUnit=%s",veaf.p(grassRunwayUnit)))
-- LOGGING DISABLED WHEN COMPILING(string.format("hiddenOnMFD=%s",veaf.p(hiddenOnMFD)))

    if not grassRunwayUnit then return nil end

    local name = grassRunwayUnit.unitName
    local runwayOrigin = grassRunwayUnit
	local tower = true
	local endMarkers = false

	-- runway length in meters
	local length = 600;
	-- a plot each XX meters
	local space = 50;
	-- runway width XX meters
	local width = 30;

	-- nb plots
	local nbPlots = math.ceil(length / space);

	local angle = math.floor(mist.utils.toDegree(runwayOrigin.heading)+0.5);

	-- create left origin from right origin
	local leftOrigin = {
		["x"] = runwayOrigin.x + width * math.cos(mist.utils.toRadian(angle-90)),
		["y"] = runwayOrigin.y + width * math.sin(mist.utils.toRadian(angle-90)),
	}

    local template = {
	    ["category"] = runwayOrigin.category,
        ["categoryStatic"] = runwayOrigin.categoryStatic,
        ["coalition"] = runwayOrigin.coalition,
        ["country"] = runwayOrigin.country,
        ["countryId"] = runwayOrigin.countryId,
        ["heading"] = runwayOrigin.heading,
        ["shape_name"] =  runwayOrigin.shape_name,
        ["type"] = runwayOrigin.type,
		["hiddenOnMFD"] = hiddenOnMFD,
	}

	-- leftOrigin plot
	local leftOriginPlot = mist.utils.deepCopy(template)
	leftOriginPlot.x = leftOrigin.x
	leftOriginPlot.y = leftOrigin.y
	mist.dynAddStatic(leftOriginPlot)

	-- place plots
	for i = 1, nbPlots do
		-- right plot
		local leftPlot = mist.utils.deepCopy(template)
		leftPlot.x = runwayOrigin.x + i * space * math.cos(mist.utils.toRadian(angle))
		leftPlot.y = runwayOrigin.y + i * space * math.sin(mist.utils.toRadian(angle))
        mist.dynAddStatic(leftPlot)

		-- right plot
		local rightPlot = mist.utils.deepCopy(template)
		rightPlot.x = leftOrigin.x + i * space * math.cos(mist.utils.toRadian(angle))
		rightPlot.y = leftOrigin.y + i * space * math.sin(mist.utils.toRadian(angle))
        mist.dynAddStatic(rightPlot)
	end

	if (endMarkers) then
		-- close the runway with optional markers (airshow cones)
		template = {
			["category"] = "Fortifications",
			["categoryStatic"] = runwayOrigin.categoryStatic,
			["coalition"] = runwayOrigin.coalition,
			["country"] = runwayOrigin.country,
			["countryId"] = runwayOrigin.countryId,
			["heading"] = runwayOrigin.heading,
			["shape_name"] =  "Comp_cone",
			["type"] = "Airshow_Cone",
			["hiddenOnMFD"] = hiddenOnMFD,
		}
		-- right plot
		local leftPlot = mist.utils.deepCopy(template)
		leftPlot.x = runwayOrigin.x + (nbPlots+1) * space * math.cos(mist.utils.toRadian(angle))
		leftPlot.y = runwayOrigin.y + (nbPlots+1) * space * math.sin(mist.utils.toRadian(angle))
		mist.dynAddStatic(leftPlot)

		-- right plot
		local rightPlot = mist.utils.deepCopy(template)
		rightPlot.x = leftOrigin.x + (nbPlots+1) * space * math.cos(mist.utils.toRadian(angle))
		rightPlot.y = leftOrigin.y + (nbPlots+1) * space * math.sin(mist.utils.toRadian(angle))
		mist.dynAddStatic(rightPlot)
	end

	if (tower) then
		-- optionally add a tower at the start of the runway
		template = {
			["category"] = "Fortifications",
			["categoryStatic"] = runwayOrigin.categoryStatic,
			["coalition"] = runwayOrigin.coalition,
			["country"] = runwayOrigin.country,
			["countryId"] = runwayOrigin.countryId,
			["heading"] = runwayOrigin.heading,
			["type"] = "house2arm",
			["hiddenOnMFD"] = hiddenOnMFD,
		}

		-- tower
		local tower = mist.utils.deepCopy(template)
		tower.x = leftOrigin.x-60 + (nbPlots+1.2) * space * math.cos(mist.utils.toRadian(angle))
		tower.y = leftOrigin.y-60 + (nbPlots+1.2) * space * math.sin(mist.utils.toRadian(angle))
		mist.dynAddStatic(tower)
	end

	-- add the runway to the named points
	local point = {
		x = runwayOrigin.x+20 + (nbPlots+1) * space * math.cos(mist.utils.toRadian(angle)) + width/2 * math.cos(mist.utils.toRadian(angle-90)),
		y = math.floor(land.getHeight(leftOrigin) + 1),
		z = runwayOrigin.y+20 + (nbPlots+1) * space * math.sin(mist.utils.toRadian(angle)) + width/2 * math.cos(mist.utils.toRadian(angle-90)),
		atc = true,
		runways = {
			{ hdg = (angle + 180) % 360, flare = "red"}
		}
	}
	return point
end

------------------------------------------------------------------------------
-- veafGrass.buildFarpsUnits
-- build FARP units on FARP with group name like "FARP "
------------------------------------------------------------------------------
function veafGrass.buildFarpsUnits(hiddenOnMFD)
    local farpUnits = {}
    local grassRunwayUnits = {}
	for name, unit in pairs(mist.DBs.unitsByName) do
-- LOGGING DISABLED WHEN COMPILING("buildFarpsUnits: testing " .. unit.type .. " " .. name)
        if name:upper():find('GRASS_RUNWAY') then
            grassRunwayUnits[name] = unit
-- LOGGING DISABLED WHEN COMPILING(string.format("found grassRunwayUnits[%s]= %s", name, veaf.p(unit)))
        end
		--first two types should represent the same object depending on if you're on the MIST side or DCS side, as a safety added both
        if (unit.type == "SINGLE_HELIPAD" or unit.type == "FARP_SINGLE_01" or unit.type == "FARP" or unit.type == "Invisible FARP") and name:upper():sub(1,5)=="FARP " then
            farpUnits[name] = unit
-- LOGGING DISABLED WHEN COMPILING(string.format("found farpUnits[%s]= %s", name, veaf.p(unit)))
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("farpUnits=%s",veaf.p(farpUnits)))
-- LOGGING DISABLED WHEN COMPILING(string.format("grassRunwayUnits=%s",veaf.p(grassRunwayUnits)))
    for name, unit in pairs(farpUnits) do
-- LOGGING DISABLED WHEN COMPILING(string.format("calling buildFarpsUnits(%s)",name))
        veafGrass.buildFarpUnits(unit, grassRunwayUnits, nil, hiddenOnMFD)
    end
end

---Browse all the FARP-type units and refill their warehouses
function veafGrass.fillAllFarpWarehouses()
-- LOGGING DISABLED WHEN COMPILING("veafGrass.fillAllFarpWarehouses()")
    local farpBases = {}
    local grassBases = {}
	local bases = world.getAirbases()
    for _, base in pairs(bases) do
		local name = base:getName()
-- LOGGING DISABLED WHEN COMPILING("fillAllFarpWarehouse: testing %s", veaf.p(name))
		local status, typeName = pcall(base.getTypeName, base) -- test cautiously if the base is a valid airbase, since DCS will either fail the getTypeName call or even crash when the airbase has been "moved" (e.g., by creating a new FARP with the same name)
		if status then
			if name:upper():find('GRASS_RUNWAY') then
				grassBases[name] = base
-- LOGGING DISABLED WHEN COMPILING("found grassBase [%s]", veaf.p(name))
			end
			--first two types should represent the same object depending on if you're on the MIST side or DCS side, as a safety added both
			if (typeName == "SINGLE_HELIPAD" or typeName == "FARP_SINGLE_01" or typeName == "FARP" or typeName == "Invisible FARP") then
				farpBases[name] = base
-- LOGGING DISABLED WHEN COMPILING("found farpBase [%s]", veaf.p(name))
			end
		else
			veaf.loggers.get(veafGrass.Id):warn("Airbase is not a valid object - getTypeName crashed - [%s]", veaf.p(name))
		end
    end

    for _, base in pairs(grassBases) do
        veafGrass.fillFarpWarehouse(base)
    end

	for _, base in pairs(farpBases) do
        veafGrass.fillFarpWarehouse(base)
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Very long table used to fill FARP warehouses
-------------------------------------------------------------------------------------------------------------------------------------------------------------
veafGrass.WAREHOUSE_ITEMS={[1]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=10},["initialAmount"]=100},[2]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=103},["initialAmount"]=100},[3]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1056},["initialAmount"]=100},[4]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=107},["initialAmount"]=100},[5]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=11},["initialAmount"]=100},[6]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=12},["initialAmount"]=100},[7]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=13},["initialAmount"]=100},[8]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=14},["initialAmount"]=100},[9]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1469},["initialAmount"]=100},[10]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1470},["initialAmount"]=100},[11]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=15},["initialAmount"]=100},[12]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=152},["initialAmount"]=100},[13]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1551},["initialAmount"]=100},[14]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1552},["initialAmount"]=100},[15]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1553},["initialAmount"]=100},[16]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1554},["initialAmount"]=100},[17]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1555},["initialAmount"]=100},[18]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1556},["initialAmount"]=100},[19]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1572},["initialAmount"]=100},[20]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1573},["initialAmount"]=100},[21]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=16},["initialAmount"]=100},[22]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1640},["initialAmount"]=100},[23]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1641},["initialAmount"]=100},[24]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1642},["initialAmount"]=100},[25]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=17},["initialAmount"]=100},[26]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1700},["initialAmount"]=100},[27]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1715},["initialAmount"]=100},[28]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=1716},["initialAmount"]=100},[29]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=2144},["initialAmount"]=100},[30]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=2145},["initialAmount"]=100},[31]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=2146},["initialAmount"]=100},[32]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=2380},["initialAmount"]=100},[33]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=2381},["initialAmount"]=100},[34]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=2382},["initialAmount"]=100},[35]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=2383},["initialAmount"]=100},[36]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=263},["initialAmount"]=100},[37]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=264},["initialAmount"]=100},[38]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=265},["initialAmount"]=100},[39]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=266},["initialAmount"]=100},[40]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=267},["initialAmount"]=100},[41]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=274},["initialAmount"]=100},[42]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=275},["initialAmount"]=100},[43]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=294},["initialAmount"]=100},[44]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=36},["initialAmount"]=100},[45]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=38},["initialAmount"]=100},[46]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=39},["initialAmount"]=100},[47]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=41},["initialAmount"]=100},[48]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=42},["initialAmount"]=100},[49]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=465},["initialAmount"]=100},[50]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=466},["initialAmount"]=100},[51]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=468},["initialAmount"]=100},[52]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=469},["initialAmount"]=100},[53]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=484},["initialAmount"]=100},[54]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=485},["initialAmount"]=100},[55]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=5},["initialAmount"]=100},[56]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=53},["initialAmount"]=100},[57]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=54},["initialAmount"]=100},[58]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=55},["initialAmount"]=100},[59]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=56},["initialAmount"]=100},[60]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=587},["initialAmount"]=100},[61]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=589},["initialAmount"]=100},[62]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=590},["initialAmount"]=100},[63]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=593},["initialAmount"]=100},[64]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=603},["initialAmount"]=100},[65]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=604},["initialAmount"]=100},[66]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=605},["initialAmount"]=100},[67]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=609},["initialAmount"]=100},[68]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=61},["initialAmount"]=100},[69]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=610},["initialAmount"]=100},[70]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=611},["initialAmount"]=100},[71]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=616},["initialAmount"]=100},[72]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=617},["initialAmount"]=100},[73]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=662},["initialAmount"]=100},[74]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=663},["initialAmount"]=100},[75]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=664},["initialAmount"]=100},[76]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=782},["initialAmount"]=100},[77]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=783},["initialAmount"]=100},[78]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=855},["initialAmount"]=100},[79]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=928},["initialAmount"]=100},[80]={["wsType"]={[1]=1,[2]=3,[3]=43,[4]=929},["initialAmount"]=100},[81]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=101},["initialAmount"]=5550},[82]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=1548},["initialAmount"]=5550},[83]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=1717},["initialAmount"]=5550},[84]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=1718},["initialAmount"]=5550},[85]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=1719},["initialAmount"]=5550},[86]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=1720},["initialAmount"]=5550},[87]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=1721},["initialAmount"]=5550},[88]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=19},["initialAmount"]=5550},[89]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2114},["initialAmount"]=1254},[90]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2138},["initialAmount"]=5550},[91]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2139},["initialAmount"]=5550},[92]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2140},["initialAmount"]=5550},[93]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2141},["initialAmount"]=5550},[94]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2142},["initialAmount"]=5550},[95]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2148},["initialAmount"]=5550},[96]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2149},["initialAmount"]=5550},[97]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2286},["initialAmount"]=5550},[98]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2287},["initialAmount"]=5550},[99]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2288},["initialAmount"]=5550},[100]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=2475},["initialAmount"]=5550},[101]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=26},["initialAmount"]=5550},[102]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=28},["initialAmount"]=5550},[103]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=424},["initialAmount"]=5550},[104]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=425},["initialAmount"]=5550},[105]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=426},["initialAmount"]=5550},[106]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=461},["initialAmount"]=5550},[107]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=463},["initialAmount"]=5550},[108]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=486},["initialAmount"]=5550},[109]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=59},["initialAmount"]=5550},[110]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=62},["initialAmount"]=5550},[111]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=63},["initialAmount"]=5550},[112]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=64},["initialAmount"]=5550},[113]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=65},["initialAmount"]=5550},[114]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=74},["initialAmount"]=5550},[115]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=78},["initialAmount"]=5550},[116]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=808},["initialAmount"]=5550},[117]={["wsType"]={[1]=4,[2]=15,[3]=44,[4]=95},["initialAmount"]=5550},[118]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=142},["initialAmount"]=5550},[119]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=173},["initialAmount"]=5550},[120]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=1762},["initialAmount"]=5550},[121]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=1763},["initialAmount"]=5550},[122]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=25},["initialAmount"]=5550},[123]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=29},["initialAmount"]=5550},[124]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=295},["initialAmount"]=5550},[125]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=296},["initialAmount"]=5550},[126]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=30},["initialAmount"]=5550},[127]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=301},["initialAmount"]=5550},[128]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=37},["initialAmount"]=5550},[129]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=462},["initialAmount"]=5550},[130]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=464},["initialAmount"]=5550},[131]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=665},["initialAmount"]=5550},[132]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=681},["initialAmount"]=5550},[133]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=94},["initialAmount"]=5550},[134]={["wsType"]={[1]=4,[2]=15,[3]=45,[4]=968},["initialAmount"]=5550},[135]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1057},["initialAmount"]=5550},[136]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1294},["initialAmount"]=5550},[137]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1295},["initialAmount"]=5550},[138]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=145},["initialAmount"]=5550},[139]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1544},["initialAmount"]=5550},[140]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1545},["initialAmount"]=5550},[141]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1546},["initialAmount"]=5550},[142]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1547},["initialAmount"]=5550},[143]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=160},["initialAmount"]=5550},[144]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=161},["initialAmount"]=5550},[145]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=170},["initialAmount"]=5550},[146]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=171},["initialAmount"]=5550},[147]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=174},["initialAmount"]=5550},[148]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=175},["initialAmount"]=5550},[149]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=176},["initialAmount"]=5550},[150]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1764},["initialAmount"]=5550},[151]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1765},["initialAmount"]=5550},[152]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1766},["initialAmount"]=5550},[153]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1767},["initialAmount"]=5550},[154]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1768},["initialAmount"]=5550},[155]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1769},["initialAmount"]=5550},[156]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=177},["initialAmount"]=5550},[157]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1770},["initialAmount"]=5550},[158]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1771},["initialAmount"]=5550},[159]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=18},["initialAmount"]=5550},[160]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1813},["initialAmount"]=5550},[161]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=183},["initialAmount"]=5550},[162]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=184},["initialAmount"]=5550},[163]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=1919},["initialAmount"]=5550},[164]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=20},["initialAmount"]=5550},[165]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2143},["initialAmount"]=5550},[166]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2476},["initialAmount"]=5550},[167]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2477},["initialAmount"]=5550},[168]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2478},["initialAmount"]=5550},[169]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2479},["initialAmount"]=5550},[170]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2480},["initialAmount"]=5550},[171]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2481},["initialAmount"]=5550},[172]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2482},["initialAmount"]=5550},[173]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2483},["initialAmount"]=5550},[174]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2484},["initialAmount"]=5550},[175]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2574},["initialAmount"]=5550},[176]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2575},["initialAmount"]=5550},[177]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2576},["initialAmount"]=5550},[178]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2577},["initialAmount"]=5550},[179]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=2578},["initialAmount"]=5550},[180]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=286},["initialAmount"]=5550},[181]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=300},["initialAmount"]=5550},[182]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=428},["initialAmount"]=5550},[183]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=429},["initialAmount"]=5550},[184]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=588},["initialAmount"]=5550},[185]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=596},["initialAmount"]=5550},[186]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=824},["initialAmount"]=5550},[187]={["wsType"]={[1]=4,[2]=15,[3]=46,[4]=825},["initialAmount"]=5550},[188]={["wsType"]={[1]=4,[2]=15,[3]=47,[4]=104},["initialAmount"]=5550},[189]={["wsType"]={[1]=4,[2]=15,[3]=47,[4]=108},["initialAmount"]=5550},[190]={["wsType"]={[1]=4,[2]=15,[3]=47,[4]=1100},["initialAmount"]=5550},[191]={["wsType"]={[1]=4,[2]=15,[3]=47,[4]=1549},["initialAmount"]=5550},[192]={["wsType"]={[1]=4,[2]=15,[3]=47,[4]=4},["initialAmount"]=5550},[193]={["wsType"]={[1]=4,[2]=15,[3]=47,[4]=679},["initialAmount"]=5550},[194]={["wsType"]={[1]=4,[2]=15,[3]=47,[4]=680},["initialAmount"]=5550},[195]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=1168},["initialAmount"]=5550},[196]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=1169},["initialAmount"]=5550},[197]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=1170},["initialAmount"]=5550},[198]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=1171},["initialAmount"]=5550},[199]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=1172},["initialAmount"]=5550},[200]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=1173},["initialAmount"]=5550},[201]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=1174},["initialAmount"]=5550},[202]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=297},["initialAmount"]=5550},[203]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=58},["initialAmount"]=5550},[204]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=608},["initialAmount"]=5550},[205]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=666},["initialAmount"]=5550},[206]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=765},["initialAmount"]=5550},[207]={["wsType"]={[1]=4,[2]=15,[3]=48,[4]=766},["initialAmount"]=5550},[208]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=1550},["initialAmount"]=5550},[209]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=172},["initialAmount"]=5550},[210]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=268},["initialAmount"]=5550},[211]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=269},["initialAmount"]=5550},[212]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=270},["initialAmount"]=5550},[213]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=271},["initialAmount"]=5550},[214]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=272},["initialAmount"]=5550},[215]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=273},["initialAmount"]=5550},[216]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=298},["initialAmount"]=5550},[217]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=427},["initialAmount"]=5550},[218]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=467},["initialAmount"]=5550},[219]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=470},["initialAmount"]=5550},[220]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=66},["initialAmount"]=5550},[221]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=667},["initialAmount"]=5550},[222]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=668},["initialAmount"]=5550},[223]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=67},["initialAmount"]=5550},[224]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=82},["initialAmount"]=5550},[225]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=83},["initialAmount"]=5550},[226]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=84},["initialAmount"]=5550},[227]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=85},["initialAmount"]=5550},[228]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=86},["initialAmount"]=5550},[229]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=87},["initialAmount"]=5550},[230]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=88},["initialAmount"]=5550},[231]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=89},["initialAmount"]=5550},[232]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=90},["initialAmount"]=5550},[233]={["wsType"]={[1]=4,[2]=15,[3]=50,[4]=91},["initialAmount"]=5550},[234]={["wsType"]={[1]=4,[2]=4,[3]=100,[4]=143},["initialAmount"]=100},[235]={["wsType"]={[1]=4,[2]=4,[3]=101,[4]=140},["initialAmount"]=100},[236]={["wsType"]={[1]=4,[2]=4,[3]=101,[4]=141},["initialAmount"]=100},[237]={["wsType"]={[1]=4,[2]=4,[3]=101,[4]=142},["initialAmount"]=100},[238]={["wsType"]={[1]=4,[2]=4,[3]=101,[4]=154},["initialAmount"]=100},[239]={["wsType"]={[1]=4,[2]=4,[3]=32,[4]=719},["initialAmount"]=100},[240]={["wsType"]={[1]=4,[2]=4,[3]=32,[4]=849},["initialAmount"]=100},[241]={["wsType"]={[1]=4,[2]=4,[3]=34,[4]=291},["initialAmount"]=100},[242]={["wsType"]={[1]=4,[2]=4,[3]=34,[4]=91},["initialAmount"]=100},[243]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=1},["initialAmount"]=100},[244]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=10},["initialAmount"]=100},[245]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=106},["initialAmount"]=100},[246]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=11},["initialAmount"]=100},[247]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=11037},["initialAmount"]=100},[248]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=11038},["initialAmount"]=100},[249]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=11039},["initialAmount"]=100},[250]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=13},["initialAmount"]=100},[251]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=135},["initialAmount"]=100},[252]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=136},["initialAmount"]=100},[253]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=14},["initialAmount"]=100},[254]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=15},["initialAmount"]=100},[255]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=16},["initialAmount"]=100},[256]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=18},["initialAmount"]=100},[257]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=19},["initialAmount"]=100},[258]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=2},["initialAmount"]=100},[259]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=21},["initialAmount"]=100},[260]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=22},["initialAmount"]=100},[261]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=23},["initialAmount"]=100},[262]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=24},["initialAmount"]=100},[263]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=26},["initialAmount"]=100},[264]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=265},["initialAmount"]=100},[265]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=266},["initialAmount"]=100},[266]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=267},["initialAmount"]=100},[267]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=268},["initialAmount"]=100},[268]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=269},["initialAmount"]=100},[269]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=27},["initialAmount"]=100},[270]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=270},["initialAmount"]=100},[271]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=3},["initialAmount"]=100},[272]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=306},["initialAmount"]=100},[273]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=307},["initialAmount"]=100},[274]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=308},["initialAmount"]=100},[275]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=309},["initialAmount"]=100},[276]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=310},["initialAmount"]=100},[277]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=320},["initialAmount"]=100},[278]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=321},["initialAmount"]=100},[279]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=322},["initialAmount"]=100},[280]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=327},["initialAmount"]=100},[281]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=333},["initialAmount"]=100},[282]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=334},["initialAmount"]=100},[283]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=335},["initialAmount"]=100},[284]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=336},["initialAmount"]=100},[285]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=337},["initialAmount"]=100},[286]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=338},["initialAmount"]=100},[287]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=339},["initialAmount"]=100},[288]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=368},["initialAmount"]=100},[289]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=371},["initialAmount"]=100},[290]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=372},["initialAmount"]=100},[291]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=395},["initialAmount"]=100},[292]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=396},["initialAmount"]=100},[293]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=397},["initialAmount"]=100},[294]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=4},["initialAmount"]=100},[295]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=403},["initialAmount"]=100},[296]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=405},["initialAmount"]=100},[297]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=409},["initialAmount"]=100},[298]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=410},["initialAmount"]=100},[299]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=412},["initialAmount"]=100},[300]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=425},["initialAmount"]=100},[301]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=426},["initialAmount"]=100},[302]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=429},["initialAmount"]=100},[303]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=446},["initialAmount"]=100},[304]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=7},["initialAmount"]=100},[305]={["wsType"]={[1]=4,[2]=4,[3]=7,[4]=9},["initialAmount"]=100},[306]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11031},["initialAmount"]=100},[307]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11035},["initialAmount"]=100},[308]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11040},["initialAmount"]=100},[309]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11050},["initialAmount"]=100},[310]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11051},["initialAmount"]=100},[311]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11052},["initialAmount"]=100},[312]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11053},["initialAmount"]=100},[313]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11054},["initialAmount"]=100},[314]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11092},["initialAmount"]=100},[315]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=11093},["initialAmount"]=100},[316]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=130},["initialAmount"]=100},[317]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=132},["initialAmount"]=100},[318]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=133},["initialAmount"]=100},[319]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=138},["initialAmount"]=100},[320]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=139},["initialAmount"]=100},[321]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=263},["initialAmount"]=100},[322]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=264},["initialAmount"]=100},[323]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=271},["initialAmount"]=100},[324]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=272},["initialAmount"]=100},[325]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=273},["initialAmount"]=100},[326]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=274},["initialAmount"]=100},[327]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=278},["initialAmount"]=100},[328]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=279},["initialAmount"]=100},[329]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=280},["initialAmount"]=100},[330]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=281},["initialAmount"]=100},[331]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=282},["initialAmount"]=100},[332]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=283},["initialAmount"]=100},[333]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=284},["initialAmount"]=100},[334]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=287},["initialAmount"]=100},[335]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=289},["initialAmount"]=100},[336]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=290},["initialAmount"]=100},[337]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=292},["initialAmount"]=100},[338]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=293},["initialAmount"]=100},[339]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=295},["initialAmount"]=100},[340]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=296},["initialAmount"]=100},[341]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=297},["initialAmount"]=100},[342]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=298},["initialAmount"]=100},[343]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=301},["initialAmount"]=100},[344]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=303},["initialAmount"]=100},[345]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=304},["initialAmount"]=100},[346]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=305},["initialAmount"]=100},[347]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=311},["initialAmount"]=100},[348]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=332},["initialAmount"]=100},[349]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=352},["initialAmount"]=100},[350]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=353},["initialAmount"]=100},[351]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=354},["initialAmount"]=100},[352]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=355},["initialAmount"]=100},[353]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=362},["initialAmount"]=100},[354]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=363},["initialAmount"]=100},[355]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=373},["initialAmount"]=100},[356]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=39},["initialAmount"]=100},[357]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=399},["initialAmount"]=100},[358]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=40},["initialAmount"]=100},[359]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=407},["initialAmount"]=100},[360]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=41},["initialAmount"]=100},[361]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=415},["initialAmount"]=100},[362]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=416},["initialAmount"]=100},[363]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=422},["initialAmount"]=100},[364]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=423},["initialAmount"]=100},[365]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=424},["initialAmount"]=100},[366]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=430},["initialAmount"]=100},[367]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=431},["initialAmount"]=100},[368]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=432},["initialAmount"]=100},[369]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=433},["initialAmount"]=100},[370]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=434},["initialAmount"]=100},[371]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=435},["initialAmount"]=100},[372]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=436},["initialAmount"]=100},[373]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=437},["initialAmount"]=100},[374]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=44},["initialAmount"]=100},[375]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=443},["initialAmount"]=100},[376]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=445},["initialAmount"]=100},[377]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=45},["initialAmount"]=100},[378]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=46},["initialAmount"]=100},[379]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=47},["initialAmount"]=100},[380]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=48},["initialAmount"]=100},[381]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=49},["initialAmount"]=100},[382]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=51},["initialAmount"]=100},[383]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=53},["initialAmount"]=100},[384]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=54},["initialAmount"]=100},[385]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=55},["initialAmount"]=100},[386]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=56},["initialAmount"]=100},[387]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=58},["initialAmount"]=100},[388]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=59},["initialAmount"]=100},[389]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=60},["initialAmount"]=100},[390]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=61},["initialAmount"]=100},[391]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=62},["initialAmount"]=100},[392]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=63},["initialAmount"]=100},[393]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=64},["initialAmount"]=100},[394]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=65},["initialAmount"]=100},[395]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=66},["initialAmount"]=100},[396]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=68},["initialAmount"]=100},[397]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=70},["initialAmount"]=100},[398]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=71},["initialAmount"]=100},[399]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=72},["initialAmount"]=100},[400]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=73},["initialAmount"]=100},[401]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=74},["initialAmount"]=100},[402]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=75},["initialAmount"]=100},[403]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=76},["initialAmount"]=100},[404]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=77},["initialAmount"]=100},[405]={["wsType"]={[1]=4,[2]=4,[3]=8,[4]=78},["initialAmount"]=100},[406]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=1000},["initialAmount"]=100},[407]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=1002},["initialAmount"]=100},[408]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=1003},["initialAmount"]=100},[409]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=1004},["initialAmount"]=100},[410]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=1005},["initialAmount"]=100},[411]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=1006},["initialAmount"]=100},[412]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=1007},["initialAmount"]=100},[413]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=1009},["initialAmount"]=100},[414]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=2558},["initialAmount"]=100},[415]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=2559},["initialAmount"]=100},[416]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=2560},["initialAmount"]=100},[417]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=2561},["initialAmount"]=100},[418]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=2562},["initialAmount"]=100},[419]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=2563},["initialAmount"]=100},[420]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=837},["initialAmount"]=100},[421]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=839},["initialAmount"]=100},[422]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=94},["initialAmount"]=100},[423]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=95},["initialAmount"]=100},[424]={["wsType"]={[1]=4,[2]=5,[3]=32,[4]=999},["initialAmount"]=100},[425]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=11},["initialAmount"]=100},[426]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=12},["initialAmount"]=100},[427]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=14},["initialAmount"]=100},[428]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=287},["initialAmount"]=100},[429]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=288},["initialAmount"]=100},[430]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=289},["initialAmount"]=100},[431]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=290},["initialAmount"]=100},[432]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=291},["initialAmount"]=100},[433]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=292},["initialAmount"]=100},[434]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=293},["initialAmount"]=100},[435]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=351},["initialAmount"]=100},[436]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=36},["initialAmount"]=100},[437]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=38},["initialAmount"]=100},[438]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=39},["initialAmount"]=100},[439]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=41},["initialAmount"]=100},[440]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=42},["initialAmount"]=100},[441]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=43},["initialAmount"]=100},[442]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=448},["initialAmount"]=100},[443]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=459},["initialAmount"]=100},[444]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=469},["initialAmount"]=100},[445]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=47},["initialAmount"]=100},[446]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=476},["initialAmount"]=100},[447]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=48},["initialAmount"]=100},[448]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=72},["initialAmount"]=100},[449]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=85},["initialAmount"]=100},[450]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=86},["initialAmount"]=100},[451]={["wsType"]={[1]=4,[2]=5,[3]=36,[4]=92},["initialAmount"]=100},[452]={["wsType"]={[1]=4,[2]=5,[3]=37,[4]=3},["initialAmount"]=100},[453]={["wsType"]={[1]=4,[2]=5,[3]=37,[4]=330},["initialAmount"]=100},[454]={["wsType"]={[1]=4,[2]=5,[3]=37,[4]=347},["initialAmount"]=100},[455]={["wsType"]={[1]=4,[2]=5,[3]=37,[4]=384},["initialAmount"]=100},[456]={["wsType"]={[1]=4,[2]=5,[3]=37,[4]=4},["initialAmount"]=100},[457]={["wsType"]={[1]=4,[2]=5,[3]=37,[4]=437},["initialAmount"]=100},[458]={["wsType"]={[1]=4,[2]=5,[3]=37,[4]=62},["initialAmount"]=100},[459]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=18},["initialAmount"]=100},[460]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=20},["initialAmount"]=100},[461]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=23},["initialAmount"]=100},[462]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=263},["initialAmount"]=100},[463]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=265},["initialAmount"]=100},[464]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=267},["initialAmount"]=100},[465]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=295},["initialAmount"]=100},[466]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=299},["initialAmount"]=100},[467]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=301},["initialAmount"]=100},[468]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=302},["initialAmount"]=100},[469]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=319},["initialAmount"]=100},[470]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=324},["initialAmount"]=100},[471]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=35},["initialAmount"]=100},[472]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=45},["initialAmount"]=100},[473]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=480},["initialAmount"]=100},[474]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=481},["initialAmount"]=100},[475]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=482},["initialAmount"]=100},[476]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=77},["initialAmount"]=100},[477]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=87},["initialAmount"]=100},[478]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=88},["initialAmount"]=100},[479]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=91},["initialAmount"]=100},[480]={["wsType"]={[1]=4,[2]=5,[3]=38,[4]=93},["initialAmount"]=100},[481]={["wsType"]={[1]=4,[2]=5,[3]=49,[4]=11086},["initialAmount"]=100},[482]={["wsType"]={[1]=4,[2]=5,[3]=49,[4]=11087},["initialAmount"]=100},[483]={["wsType"]={[1]=4,[2]=5,[3]=49,[4]=11088},["initialAmount"]=100},[484]={["wsType"]={[1]=4,[2]=5,[3]=49,[4]=11089},["initialAmount"]=100},[485]={["wsType"]={[1]=4,[2]=5,[3]=49,[4]=427},["initialAmount"]=100},[486]={["wsType"]={[1]=4,[2]=5,[3]=49,[4]=63},["initialAmount"]=100},[487]={["wsType"]={[1]=4,[2]=5,[3]=49,[4]=64},["initialAmount"]=100},[488]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=11033},["initialAmount"]=100},[489]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=11034},["initialAmount"]=100},[490]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=255},["initialAmount"]=100},[491]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=256},["initialAmount"]=100},[492]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=257},["initialAmount"]=100},[493]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=258},["initialAmount"]=100},[494]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=259},["initialAmount"]=100},[495]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=260},["initialAmount"]=100},[496]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=261},["initialAmount"]=100},[497]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=268},["initialAmount"]=100},[498]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=269},["initialAmount"]=100},[499]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=270},["initialAmount"]=100},[500]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=271},["initialAmount"]=100},[501]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=272},["initialAmount"]=100},[502]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=273},["initialAmount"]=100},[503]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=274},["initialAmount"]=100},[504]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=275},["initialAmount"]=100},[505]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=276},["initialAmount"]=100},[506]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=277},["initialAmount"]=100},[507]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=278},["initialAmount"]=100},[508]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=279},["initialAmount"]=100},[509]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=280},["initialAmount"]=100},[510]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=281},["initialAmount"]=100},[511]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=282},["initialAmount"]=100},[512]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=283},["initialAmount"]=100},[513]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=284},["initialAmount"]=100},[514]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=285},["initialAmount"]=100},[515]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=30},["initialAmount"]=100},[516]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=31},["initialAmount"]=100},[517]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=312},["initialAmount"]=100},[518]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=313},["initialAmount"]=100},[519]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=314},["initialAmount"]=100},[520]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=315},["initialAmount"]=100},[521]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=316},["initialAmount"]=100},[522]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=317},["initialAmount"]=100},[523]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=318},["initialAmount"]=100},[524]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=32},["initialAmount"]=100},[525]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=321},["initialAmount"]=100},[526]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=322},["initialAmount"]=100},[527]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=323},["initialAmount"]=100},[528]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=325},["initialAmount"]=100},[529]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=326},["initialAmount"]=100},[530]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=327},["initialAmount"]=100},[531]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=328},["initialAmount"]=100},[532]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=329},["initialAmount"]=100},[533]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=33},["initialAmount"]=100},[534]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=331},["initialAmount"]=100},[535]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=332},["initialAmount"]=100},[536]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=333},["initialAmount"]=100},[537]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=334},["initialAmount"]=100},[538]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=335},["initialAmount"]=100},[539]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=336},["initialAmount"]=100},[540]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=337},["initialAmount"]=100},[541]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=338},["initialAmount"]=100},[542]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=339},["initialAmount"]=100},[543]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=34},["initialAmount"]=100},[544]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=363},["initialAmount"]=100},[545]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=364},["initialAmount"]=100},[546]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=374},["initialAmount"]=100},[547]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=38},["initialAmount"]=100},[548]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=385},["initialAmount"]=100},[549]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=386},["initialAmount"]=100},[550]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=387},["initialAmount"]=100},[551]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=388},["initialAmount"]=100},[552]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=389},["initialAmount"]=100},[553]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=390},["initialAmount"]=100},[554]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=391},["initialAmount"]=100},[555]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=392},["initialAmount"]=100},[556]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=412},["initialAmount"]=100},[557]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=413},["initialAmount"]=100},[558]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=449},["initialAmount"]=100},[559]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=483},["initialAmount"]=100},[560]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=484},["initialAmount"]=100},[561]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=485},["initialAmount"]=100},[562]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=486},["initialAmount"]=100},[563]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=487},["initialAmount"]=100},[564]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=488},["initialAmount"]=100},[565]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=5},["initialAmount"]=100},[566]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=6},["initialAmount"]=100},[567]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=69},["initialAmount"]=100},[568]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=7},["initialAmount"]=100},[569]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=70},["initialAmount"]=100},[570]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=71},["initialAmount"]=100},[571]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=72},["initialAmount"]=100},[572]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=75},["initialAmount"]=100},[573]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=79},["initialAmount"]=100},[574]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=9},["initialAmount"]=100},[575]={["wsType"]={[1]=4,[2]=5,[3]=9,[4]=90},["initialAmount"]=100},[576]={["wsType"]={[1]=4,[2]=7,[3]=32,[4]=11048},["initialAmount"]=100},[577]={["wsType"]={[1]=4,[2]=7,[3]=32,[4]=11056},["initialAmount"]=100},[578]={["wsType"]={[1]=4,[2]=7,[3]=32,[4]=11090},["initialAmount"]=100},[579]={["wsType"]={[1]=4,[2]=7,[3]=32,[4]=619},["initialAmount"]=100},[580]={["wsType"]={[1]=4,[2]=7,[3]=32,[4]=659},["initialAmount"]=100},[581]={["wsType"]={[1]=4,[2]=7,[3]=32,[4]=661},["initialAmount"]=100},[582]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=11044},["initialAmount"]=100},[583]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=11049},["initialAmount"]=100},[584]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=11091},["initialAmount"]=100},[585]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=144},["initialAmount"]=100},[586]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=145},["initialAmount"]=100},[587]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=146},["initialAmount"]=100},[588]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=147},["initialAmount"]=100},[589]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=148},["initialAmount"]=100},[590]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=149},["initialAmount"]=100},[591]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=150},["initialAmount"]=100},[592]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=151},["initialAmount"]=100},[593]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=155},["initialAmount"]=100},[594]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=158},["initialAmount"]=100},[595]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=159},["initialAmount"]=100},[596]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=181},["initialAmount"]=100},[597]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=182},["initialAmount"]=100},[598]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=183},["initialAmount"]=100},[599]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=184},["initialAmount"]=100},[600]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=185},["initialAmount"]=100},[601]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=186},["initialAmount"]=100},[602]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=256},["initialAmount"]=100},[603]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=257},["initialAmount"]=100},[604]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=258},["initialAmount"]=100},[605]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=275},["initialAmount"]=100},[606]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=276},["initialAmount"]=100},[607]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=277},["initialAmount"]=100},[608]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=299},["initialAmount"]=100},[609]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=30},["initialAmount"]=100},[610]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=31},["initialAmount"]=100},[611]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=32},["initialAmount"]=100},[612]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=326},["initialAmount"]=100},[613]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=329},["initialAmount"]=100},[614]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=33},["initialAmount"]=100},[615]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=330},["initialAmount"]=100},[616]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=331},["initialAmount"]=100},[617]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=34},["initialAmount"]=100},[618]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=340},["initialAmount"]=100},[619]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=341},["initialAmount"]=100},[620]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=342},["initialAmount"]=100},[621]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=35},["initialAmount"]=100},[622]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=350},["initialAmount"]=100},[623]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=359},["initialAmount"]=100},[624]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=360},["initialAmount"]=100},[625]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=361},["initialAmount"]=100},[626]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=364},["initialAmount"]=100},[627]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=365},["initialAmount"]=100},[628]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=366},["initialAmount"]=100},[629]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=367},["initialAmount"]=100},[630]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=37},["initialAmount"]=100},[631]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=374},["initialAmount"]=100},[632]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=375},["initialAmount"]=100},[633]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=376},["initialAmount"]=100},[634]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=377},["initialAmount"]=100},[635]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=378},["initialAmount"]=100},[636]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=379},["initialAmount"]=100},[637]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=380},["initialAmount"]=100},[638]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=381},["initialAmount"]=100},[639]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=382},["initialAmount"]=100},[640]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=383},["initialAmount"]=100},[641]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=384},["initialAmount"]=100},[642]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=385},["initialAmount"]=100},[643]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=386},["initialAmount"]=100},[644]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=387},["initialAmount"]=100},[645]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=388},["initialAmount"]=100},[646]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=389},["initialAmount"]=100},[647]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=390},["initialAmount"]=100},[648]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=391},["initialAmount"]=100},[649]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=392},["initialAmount"]=100},[650]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=393},["initialAmount"]=100},[651]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=401},["initialAmount"]=100},[652]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=402},["initialAmount"]=100},[653]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=440},["initialAmount"]=100},[654]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=441},["initialAmount"]=100},[655]={["wsType"]={[1]=4,[2]=7,[3]=33,[4]=442},["initialAmount"]=100},[656]={["wsType"]={[1]=4,[2]=8,[3]=10,[4]=255},["initialAmount"]=100},[657]={["wsType"]={[1]=4,[2]=8,[3]=10,[4]=406},["initialAmount"]=100},[658]={["wsType"]={[1]=4,[2]=8,[3]=11,[4]=319},["initialAmount"]=100},[659]={["wsType"]={[1]=4,[2]=8,[3]=11,[4]=398},["initialAmount"]=100}}

---Add everything to the FARP warehouse; since 2.8.x, the FARPs spawn empty, hence making it impossible to rearm or refuel (even with all the necessary vehicles)
---@param farp any the FARP to be filled
function veafGrass.fillFarpWarehouse(farp)
-- LOGGING DISABLED WHEN COMPILING("veafGrass.fillFarpWarehouse()")
-- LOGGING DISABLED WHEN COMPILING("farp=[%s]", veaf.p(farp))
	local farpName = farp.name
-- LOGGING DISABLED WHEN COMPILING("farpName=[%s]", veaf.p(farpName))
	local result = farpName ~= nil
	if not result then result, farpName = pcall(Unit.getUnitName, farp) end
-- LOGGING DISABLED WHEN COMPILING("farpName=[%s]", veaf.p(farpName))
	if not result then result, farpName = pcall(Group.getGroupName, farp) end
-- LOGGING DISABLED WHEN COMPILING("farpName=[%s]", veaf.p(farpName))
	if not result then result, farpName = pcall(Object.getName, farp) end
-- LOGGING DISABLED WHEN COMPILING("farpName=[%s]", veaf.p(farpName))
	if farpName then
		local farpAirbase = Airbase.getByName(farpName)
		if farpAirbase then
			local farpWarehouse = farpAirbase:getWarehouse()
			if farpWarehouse then
-- LOGGING DISABLED WHEN COMPILING("inventory = %s", veaf.p(farpWarehouse:getInventory()))
				for _, datas in ipairs(veafGrass.WAREHOUSE_ITEMS) do
					local rnd = math.random(1, 100)
					farpWarehouse:setItem(datas.wsType, 5000+rnd)
				end
				for i = 0, 3, 1 do
					local rnd = math.random(1, 100)
					farpWarehouse:setLiquidAmount(i,50000+rnd)
				end
				for i = 0, 3 do
-- LOGGING DISABLED WHEN COMPILING("getLiquidAmount(%s) = %s", i, veaf.p(farpWarehouse:getLiquidAmount(i)))
				end
			else
				veaf.loggers.get(veafGrass.Id):error("Airbase.getByName([%s]):getWarehouse() returned null", veaf.p(farpName))
			end
		else
			veaf.loggers.get(veafGrass.Id):error("Airbase.getByName([%s]) returned null", veaf.p(farpName))
		end
-- LOGGING DISABLED WHEN COMPILING("FARP [%s] successfully replenished", veaf.p(farpName))
	end
end

------------------------------------------------------------------------------
-- build nice FARP units arround the FARP
-- @param unit farp : the FARP unit
------------------------------------------------------------------------------
function veafGrass.buildFarpUnits(farp, grassRunwayUnits, groupName, hiddenOnMFD, noFarpMarkers, code, freq, mod)
-- LOGGING DISABLED WHEN COMPILING(string.format("buildFarpUnits()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("farp=%s",veaf.p(farp)))
-- LOGGING DISABLED WHEN COMPILING(string.format("grassRunwayUnits=%s",veaf.p(grassRunwayUnits)))
-- LOGGING DISABLED WHEN COMPILING(string.format("hiddenOnMFD=%s",veaf.p(hiddenOnMFD)))
-- LOGGING DISABLED WHEN COMPILING(string.format("noFarpMarkers=%s",veaf.p(noFarpMarkers)))
-- LOGGING DISABLED WHEN COMPILING(string.format("code=%s",veaf.p(code)))
-- LOGGING DISABLED WHEN COMPILING(string.format("freq=%s",veaf.p(freq)))
-- LOGGING DISABLED WHEN COMPILING(string.format("mod=%s",veaf.p(mod)))

	local freq = freq or math.random(90)+100
	local mod = mod or "X"
	local code = code or "FRP"

	-- add FARP to CTLD FOBs and logistic units
	local name = farp.name
	if not name then name = farp.unitName end
	if not name then name = farp.groupName end
	if ctld then
		table.insert(ctld.builtFOBS, name)
		table.insert(ctld.logisticUnits, name)
	end

	local farpUnitNameCounter=1
	local farpCoalition = farp.coalition
	local farpCoalitionNumber = farp.coalition
	if type(farpCoalition == "number") then
		if farpCoalition == 1 then
			farpCoalition = "red"
		else
			farpCoalition = "blue"
		end
	end
	if type(farpCoalition == 'string') then
		if farpCoalition == "red" then
			farpCoalitionNumber = 1
		else
			farpCoalitionNumber = 2
		end
	end

	local farpHeading = farp.heading or 0
	local angle = mist.utils.toDegree(farpHeading)
	local tentDistance = 100
	local tentSpacing = 30
	local otherDistance = 85
	local otherSpacing = 15
	local unitsDistance = 75

	-- fix distances on FARPs
	if farp.type == "SINGLE_HELIPAD" or farp.type == "FARP_SINGLE_01" or farp.type == "FARP" or farp.type == "Invisible FARP" then
		tentDistance = 200
    unitsDistance = 150
	  otherDistance = 130
	end

	local tentOrigin = {
		["x"] = farp.x + tentDistance * math.cos(mist.utils.toRadian(angle)),
		["y"] = farp.y + tentDistance * math.sin(mist.utils.toRadian(angle)),
	}

	-- create tents
	for j = 1,2 do
		for i = 1,3 do
			local tent = {
				["unitName"] = string.format("FARP %s unit #%d", farp.groupName, farpUnitNameCounter),
				["category"] = 'static',
				["categoryStatic"] = 'Fortifications',
				["coalition"] = farpCoalition,
				["country"] = farp.country,
				["countryId"] = farp.countryId,
				["heading"] = mist.utils.toRadian(angle-90),
				["type"] = 'FARP Tent',
				["x"] = tentOrigin.x + (i-1) * tentSpacing * math.cos(mist.utils.toRadian(angle)) - (j-1) * tentSpacing * math.sin(mist.utils.toRadian(angle)),
				["y"] = tentOrigin.y + (i-1) * tentSpacing * math.sin(mist.utils.toRadian(angle)) + (j-1) * tentSpacing *  math.cos(mist.utils.toRadian(angle)),
				["hiddenOnMFD"] = hiddenOnMFD,
			}
			if groupName then
				tent["groupName"] = groupName
			end

			mist.dynAddStatic(tent)
			farpUnitNameCounter = farpUnitNameCounter + 1
		end
	end

	-- add visible markers to the invisible farps
	if farp.type == "Invisible FARP" and not noFarpMarkers then
		local markerDistance = 25
		local markerAngle = -45
		local markerUnit1 = {
			["unitName"] = string.format("FARP %s unit #%d", farp.groupName, farpUnitNameCounter),
			["category"] = "Unarmed",
			["type"] = "M978 HEMTT Tanker",
			["coalition"] = farpCoalition,
			["country"] = farp.country,
			["countryId"] = farp.countryId,
			["heading"] = mist.utils.toRadian(angle-90),
			["x"] = farp.x - markerDistance * math.cos(mist.utils.toRadian(angle + markerAngle)),
			["y"] = farp.y - markerDistance * math.sin(mist.utils.toRadian(angle + markerAngle)),
			["hiddenOnMFD"] = hiddenOnMFD,
		}
		if groupName then
			markerUnit1["groupName"] = groupName
		end
		mist.dynAddStatic(markerUnit1)
		farpUnitNameCounter = farpUnitNameCounter + 1
		local markerUnit2 = {
			["unitName"] = string.format("FARP %s unit #%d", farp.groupName, farpUnitNameCounter),
			["category"] = "Fortifications",
			["shape_name"] = "H-Windsock_RW",
			["type"] = "Windsock",
			["coalition"] = farpCoalition,
			["country"] = farp.country,
			["countryId"] = farp.countryId,
			["heading"] = mist.utils.toRadian(angle-90),
			["x"] = farp.x - markerDistance * math.cos(mist.utils.toRadian(angle - markerAngle)),
			["y"] = farp.y - markerDistance * math.sin(mist.utils.toRadian(angle - markerAngle)),
			["hiddenOnMFD"] = hiddenOnMFD,
		}
		if groupName then
			markerUnit2["groupName"] = groupName
		end
		mist.dynAddStatic(markerUnit2)
		farpUnitNameCounter = farpUnitNameCounter + 1
	end

	-- spawn other static units
	local otherUnits={
		'FARP Fuel Depot',
		'FARP Ammo Dump Coating',
		'GeneratorF',
	}
	local otherOrigin = {
		["x"] = farp.x + otherDistance * math.cos(mist.utils.toRadian(angle)),
		["y"] = farp.y + otherDistance * math.sin(mist.utils.toRadian(angle)),
	}

	for j,typeName in ipairs(otherUnits) do
		local otherUnit = {
			["unitName"] = string.format("FARP %s unit #%d", farp.groupName, farpUnitNameCounter),
			["category"] = 'static',
			["categoryStatic"] = 'Fortifications',
			["coalition"] = farpCoalition,
			["country"] = farp.country,
			["countryId"] = farp.countryId,
			["heading"] = mist.utils.toRadian(angle-90),
			["type"] = typeName,
			["x"] = otherOrigin.x - (j-1) * otherSpacing * math.sin(mist.utils.toRadian(angle)),
			["y"] = otherOrigin.y + (j-1) * otherSpacing * math.cos(mist.utils.toRadian(angle)),
			["hiddenOnMFD"] = hiddenOnMFD,
		}
		if groupName then
			otherUnit["groupName"] = groupName
		end
		mist.dynAddStatic(otherUnit)
		farpUnitNameCounter = farpUnitNameCounter + 1
	end

	-- create Windsock
	local windsockDistance = 50
	local windsockAngle = 45

	-- fix Windsock position on FARPs
  if farp.type == "SINGLE_HELIPAD" or farp.type == "FARP_SINGLE_01" or farp.type == "FARP" or farp.type == "Invisible FARP" then
    windsockDistance = 120
		windsockAngle = 0
	end

	local windsockUnit = {
		["unitName"] = string.format("FARP %s unit #%d", farp.groupName, farpUnitNameCounter),
		["category"] = 'static',
		["categoryStatic"] = 'Fortifications',
		["shape_name"] = "H-Windsock_RW",
		["type"] = "Windsock",
		["coalition"] = farpCoalition,
		["country"] = farp.country,
		["countryId"] = farp.countryId,
		["heading"] = mist.utils.toRadian(angle-90),
		["x"] = farp.x + windsockDistance * math.cos(mist.utils.toRadian(angle + windsockAngle)),
		["y"] = farp.y + windsockDistance * math.sin(mist.utils.toRadian(angle + windsockAngle)),
		["hiddenOnMFD"] = hiddenOnMFD,
	}
	if groupName then
		windsockUnit["groupName"] = groupName
	end
	mist.dynAddStatic(windsockUnit)
	farpUnitNameCounter = farpUnitNameCounter + 1

	-- on FARP unit, place a second windsock, at 90°
	if farp.type == 'FARP' then
		local windsockUnit = {
			["unitName"] = string.format("FARP %s unit #%d", farp.groupName, farpUnitNameCounter),
			["category"] = 'static',
			["categoryStatic"] = 'Fortifications',
			["shape_name"] = "H-Windsock_RW",
			["type"] = "Windsock",
			["coalition"] = farpCoalition,
			["country"] = farp.country,
			["countryId"] = farp.countryId,
			["heading"] = mist.utils.toRadian(angle-90),
			["x"] = farp.x + windsockDistance * math.cos(mist.utils.toRadian(angle + windsockAngle - 90)),
			["y"] = farp.y + windsockDistance * math.sin(mist.utils.toRadian(angle + windsockAngle - 90)),
			["hiddenOnMFD"] = hiddenOnMFD,
		}
		if groupName then
			windsockUnit["groupName"] = groupName
		end
		mist.dynAddStatic(windsockUnit)
		farpUnitNameCounter = farpUnitNameCounter + 1
	end

	-- spawn a FARP escort group
	local farpEscortUnitsNames={
		blue = {
			"Hummer",
			"M978 HEMTT Tanker",
			"M 818",
			"M 818",
			"Hummer",
		},
		red = {
			"ATZ-10",
			"ATZ-10",
			"Ural-4320 APA-5D",
			"Ural-375",
			"Ural-375",
			"Ural-375 PBU",
		}
	}

	local unitsSpacing=6
	local unitsOrigin = {
		x = farp.x + unitsDistance * math.cos(mist.utils.toRadian(angle)),
		y = farp.y + unitsDistance * math.sin(mist.utils.toRadian(angle)),
	}

	local farpEscortGroup = {
		["category"] = 'vehicle',
		["coalition"] = farpCoalition,
		["country"] = farp.country,
		["countryId"] = farp.countryId,
		["groupName"] = farp.groupName,
		["units"] = {},
		["hiddenOnMFD"] = hiddenOnMFD,
	}
	if groupName then
		farpEscortGroup["groupName"] = groupName
	end

	for j,typeName in ipairs(farpEscortUnitsNames[farpCoalition]) do
		local escortUnit = {
			["unitName"] = string.format("FARP %s unit #%d", farp.groupName, farpUnitNameCounter),
			["heading"] = mist.utils.toRadian(angle-135), -- parked \\\\\
			["type"] = typeName,
			["x"] = unitsOrigin.x - (j-1) * unitsSpacing * math.sin(mist.utils.toRadian(angle)),
			["y"] = unitsOrigin.y + (j-1) * unitsSpacing * math.cos(mist.utils.toRadian(angle)),
			["skill"] = "Random",
		}
		table.insert(farpEscortGroup.units, escortUnit)
		farpUnitNameCounter = farpUnitNameCounter + 1

	end

	mist.dynAdd(farpEscortGroup)

    -- add the FARP to the named points
    local farpNamedPoint = {
        x = farp.x,
        y = math.floor(land.getHeight(farp) + 1),
        z = farp.y,
        atc = true,
        runways = {}
    }

    -- add the FARP to the named points
    local beaconPoint = {
        x = farp.x + 250,
        y = math.floor(land.getHeight(farp) + 1),
        z = farp.y
    }

	farpNamedPoint.tower = "No Control"

	if ctld then
		-- spawn tacan
		mod = string.upper(mod)
		local tacanGroupName = string.format("TACAN %s - %s%s", tostring(code), tostring(freq), tostring(mod))
-- LOGGING DISABLED WHEN COMPILING(string.format("tacanGroupName=%s", tostring(tacanGroupName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("freq=%s", tostring(freq)))
-- LOGGING DISABLED WHEN COMPILING(string.format("mod=%s", tostring(mod)))
		local txFreq = (1025 + freq - 1) * 1000000
		local rxFreq = (962 + freq - 1) * 1000000
		if (freq < 64 and mod == "Y") or (freq >= 64 and mod == "X") then
				rxFreq = (1088 + freq - 1) * 1000000
		end
-- LOGGING DISABLED WHEN COMPILING(string.format("txFreq=%s", tostring(txFreq)))
-- LOGGING DISABLED WHEN COMPILING(string.format("rxFreq=%s", tostring(rxFreq)))

		local command = {
				id = 'ActivateBeacon',
				params = {
						type = 4,
						system = 18,
						callsign = code,
						frequency = rxFreq,
						AA = false,
						channel = freq,
						bearing = true,
						modeChannel = mod,
				}
		}
-- LOGGING DISABLED WHEN COMPILING(string.format("setting %s", veaf.p(command)))
    local spawnedGroup = ctld.spawnRadioBeaconUnit(beaconPoint, farp.country, tacanGroupName, tacanGroupName)
		local controller = spawnedGroup:getController()
		controller:setCommand(command)
-- LOGGING DISABLED WHEN COMPILING(string.format("done setting TACAN command"))
		-- spawn CTLD beacon
		local _beaconInfo = ctld.createRadioBeacon(beaconPoint, farpCoalitionNumber, farp.country, farp.unitName or farp.name, -1, true)
		if _beaconInfo ~= nil then
			farpNamedPoint.tacan = string.format("ADF : %.2f KHz - %.2f MHz - %.2f MHz FM - %s", _beaconInfo.vhf / 1000, _beaconInfo.uhf / 1000000, _beaconInfo.fm / 1000000, tacanGroupName)
-- LOGGING DISABLED WHEN COMPILING(string.format("farpNamedPoint.tacan=%s", veaf.p(farpNamedPoint.tacan)))
		end	
	end

    -- search for an associated grass runway
    if (grassRunwayUnits) then
        local grassRunwayUnit = nil
        for name, unitDef in pairs(grassRunwayUnits) do
            local unit = Unit.getByName(name)
            if not unit then
                unit = StaticObject.getByName(name)
            end
            if unit then
                local pos = unit:getPosition().p
                if pos then -- you never know O.o
                    local distanceFromCenter = ((pos.x - farp.x)^2 + (pos.z - farp.y)^2)^0.5
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s; distanceFromCenter=%s", tostring(name), veaf.p(distanceFromCenter)))
                    if distanceFromCenter <= veafGrass.RadiusAroundFarp then
                        grassRunwayUnit = unitDef
                        break
                    end
                end
            end
        end
        if grassRunwayUnit then
-- LOGGING DISABLED WHEN COMPILING(string.format("found grassRunwayUnit %s", veaf.p(grassRunwayUnit)))
			local grassNamedPoint = veafGrass.buildGrassRunway(grassRunwayUnit, hiddenOnMFD)
			if grassNamedPoint then
				farpNamedPoint.x = grassNamedPoint.x
				farpNamedPoint.y = grassNamedPoint.y
				farpNamedPoint.z = grassNamedPoint.z
				farpNamedPoint.atc = grassNamedPoint.atc
				farpNamedPoint.runways = grassNamedPoint.runways
			end
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("farpNamedPoint=%s", veaf.p(farpNamedPoint)))

	veafNamedPoints.addPoint(farp.unitName or farp.name, farpNamedPoint)

-- LOGGING DISABLED WHEN COMPILING(string.format("calling fillFarpWarehouse(%s)",name))
	veafGrass.fillFarpWarehouse(farp)
end

---
--- called from veafEventHandler when a unit is created
function veafGrass.onBirth(event)
-- LOGGING DISABLED WHEN COMPILING(string.format("onBirth(%s)",veaf.p(event)))

	-- find the originator unit
    local unitName = nil
    if event.initiator ~= nil then
        unitName = event.initiator.unitName
    end
    if not unitName then 
        veaf.loggers.get(veafGrass.Id):warn("no unitname found in event %s", veaf.p(event))
        return
    end

    if mist.DBs.humansByName[unitName] then -- it's a human unit
-- LOGGING DISABLED WHEN COMPILING("caught event BIRTH for human unit [%s]", veaf.p(unitName))
        local _unit = event.initiator
        if _unit ~= nil then
			-- refill all farp warehouses, to work around a DCS bug where the warehouses are spawned empty and their content is not synced over the network
			veafGrass.fillAllFarpWarehouses()
        end
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafGrass.initialize()
	-- delay all these functions 30 seconds (to ensure that the other modules are loaded)

	-- auto generate FARP units (hide these units on MFDs as they create clutter for nothing since the FARP already shows or not depending on what the Mission maker wanted, regardless, don't show them)
    mist.scheduleFunction(veafGrass.buildFarpsUnits,{true},timer.getTime()+veafGrass.DelayForStartup)

	veafEventHandler.addCallback("veafGrass.OnBirth", {"S_EVENT_BIRTH", "S_EVENT_PLAYER_ENTER_UNIT"}, veafGrass.onBirth)
end

veaf.loggers.get(veafGrass.Id):info(string.format("Loading version %s", veafGrass.Version))

------------------ END script veafGrass.lua  ------------------


------------------ START script veafHoundElintHelper.lua  ------------------

------------------------------------------------------------------
-- VEAF helper for Hound-Elint
-- By zip (2021)
--
-- Features:
-- ---------
-- * This module offers support for integrating Hound-Elint in a mission
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafHoundElint = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafHoundElint.Id = "HOUND"

--- Version.
veafHoundElint.Version = "1.1.1"

-- trace level, specific to this module
--veafHoundElint.LogLevel = "debug"

veaf.loggers.new(veafHoundElint.Id, veafHoundElint.LogLevel)

-- delay before the mission groups are added to the Hound system at start
veafHoundElint.DelayForStartup = 1

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafHoundElint.initialized = false
veafHoundElint.prefix = nil
veafHoundElint.redParameters = {}
veafHoundElint.blueParameters = {}
veafHoundElint.redHound = nil
veafHoundElint.blueHound = nil
veafHoundElint.elintUnitsTypes = {}

veafHoundElint.globalSectorName = "GLOBAL"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- core functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafHoundElint.getHoundOfCoalition(coa)
-- LOGGING DISABLED WHEN COMPILING(string.format("getHoundOfCoalition(%s)", veaf.p(coa)))
    local hound = nil
    if coa == coalition.side.RED then
        hound = veafHoundElint.redHound
    elseif coa == coalition.side.BLUE then
        hound = veafHoundElint.blueHound
    end
    return hound
end


function veafHoundElint.addPlatformToSystem(dcsGroup, alreadyAddedUnits, atMissionStart)
    if not veafHoundElint.initialized then 
        return false 
    end
    
    if not dcsGroup then
        veaf.loggers.get(veafHoundElint.Id):error("group does not exist")
        return false
    end
    
    local groupName = dcsGroup:getName()
    local coa = dcsGroup:getCoalition()
    local hound = veafHoundElint.getHoundOfCoalition(coa)
-- LOGGING DISABLED WHEN COMPILING(string.format("addPlatformToSystem(%s) to %s", veaf.p(groupName), veaf.p(veaf.ifnn(hound,"name"))))
-- LOGGING DISABLED WHEN COMPILING(string.format("atMissionStart=%s", veaf.p(atMissionStart)))

    
    local batchMode = (alreadyAddedUnits ~= nil)
    local alreadyAddedUnits = alreadyAddedUnits or {}
    if not(hound) then
        veaf.loggers.get(veafHoundElint.Id):error(string.format("no Hound system for the coalition of %s", veaf.p(groupName)))
        return false
    end

    local didSomething = false

    local _addUnitToSystem = function(dcsUnit, isFunctional) 
        if not(atMissionStart) or isFunctional then
            local unitName = dcsUnit:getName()
            local unitType = dcsUnit:getDesc()["typeName"]
-- LOGGING DISABLED WHEN COMPILING(string.format("checking unit %s of type %s", veaf.p(unitName), veaf.p(unitType)))

            -- check if the unitType is supported by Hound Elint
            if veafHoundElint.elintUnitsTypes[unitType] then
-- LOGGING DISABLED WHEN COMPILING(string.format("-> supported elint type"))
                -- check the unit name vs the prefix
                if veafHoundElint.prefix then 
                    local _p1, _p2 = unitName:lower():find(veafHoundElint.prefix:lower())
-- LOGGING DISABLED WHEN COMPILING(string.format("_p1=%s", veaf.p(_p1)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_p2=%s", veaf.p(_p2)))
                    if _p2 and _p1 == 1 then
                        -- found the prefix at the beginning of the name
                        if not(alreadyAddedUnits[unitName]) then
-- LOGGING DISABLED WHEN COMPILING(string.format("adding a platform : %s", unitName))
                            local added = hound:addPlatform(unitName) -- no actual return value
                            -- todo check if ok when HoundElint will give us a return value
                            if added then 
                                didSomething = true
                                alreadyAddedUnits[unitName] = true
-- LOGGING DISABLED WHEN COMPILING(string.format("adding a platform -> OK"))
                            end
                        end
                    end
                end
            end
        end
    end

-- LOGGING DISABLED WHEN COMPILING(string.format("batchMode = %s", veaf.p(batchMode)))
-- LOGGING DISABLED WHEN COMPILING(string.format("dcsGroup=%s", veaf.p(mist.utils.deepCopy(dcsGroup))))

    if Group.getByName(groupName) then
        for _, dcsUnit in pairs(dcsGroup:getUnits()) do
-- LOGGING DISABLED WHEN COMPILING(string.format("dcsUnit.getName=%s", veaf.p(veaf.ifnn(dcsUnit, "getName"))))
-- LOGGING DISABLED WHEN COMPILING(string.format("dcsUnit:isActive()=%s", veaf.p(dcsUnit:isActive())))
            _addUnitToSystem(dcsUnit, dcsUnit:isActive())
        end
    elseif StaticObject.getByName(groupName) then
-- LOGGING DISABLED WHEN COMPILING("Group is Static")
-- LOGGING DISABLED WHEN COMPILING(string.format("dcsGroup:isExist()=%s", veaf.p(dcsGroup:isExist())))
        _addUnitToSystem(dcsGroup, dcsGroup:isExist())
    end

    if didSomething and not(batchMode) then
-- LOGGING DISABLED WHEN COMPILING(string.format("reactivating the Elint system"))

        -- reactivate the system
        hound:systemOn()
    end

    return didSomething
end

local function initializeHoundSystem(coa, parameters, atMissionStart)
    local hound = veafHoundElint.getHoundOfCoalition(coa)
-- LOGGING DISABLED WHEN COMPILING(string.format("initializeHoundSystem %s",tostring(hound.name)))
-- LOGGING DISABLED WHEN COMPILING(string.format("atMissionStart=%s",veaf.p(atMissionStart)))

    local alreadyAddedUnits = {}
    local dcsGroups = coalition.getGroups(coa)
    for _, dcsGroup in pairs(dcsGroups) do
        veafHoundElint.addPlatformToSystem(dcsGroup, alreadyAddedUnits, atMissionStart)
    end

    if parameters then
        if parameters.markers then
            hound:enableMarkers(HOUND.MARKER.DIAMOND)
-- LOGGING DISABLED WHEN COMPILING("enabled markers")
        end

        if parameters.platformPositionErrors then
            hound:enablePlatformPosErrors()
-- LOGGING DISABLED WHEN COMPILING("enabled platformPositionErrors")
        end

        if parameters.disableBDA then
            hound:disableBDA()
-- LOGGING DISABLED WHEN COMPILING("disabled BDA")
        end

        if parameters.NATO_SectorCallsigns then
            hound:useNATOCallsignes(true)
-- LOGGING DISABLED WHEN COMPILING("using NATO callsigns for zones")
        end

        if parameters.NATOmessages then
            hound:enableNATO()
-- LOGGING DISABLED WHEN COMPILING("using NATO message format")
        end

        if parameters.ATISinterval and type(parameters.ATISinterval) == 'number' and parameters.ATISinterval > 0 then 
            hound:setAtisUpdateInterval(parameters.ATISinterval) 
-- LOGGING DISABLED WHEN COMPILING(string.format("ATIS interval set to %s", veaf.p(parameters.ATISinterval)))
        end

        if parameters.preBriefedContacts then
            for _,name in pairs(parameters.preBriefedContacts) do
-- LOGGING DISABLED WHEN COMPILING(string.format("Attempting to add Pre-brief target named %s", veaf.p(name)))
                if name and type(name) == 'string' and (Group.getByName(name) or Unit.getByName(name)) then
                    hound:preBriefedContact(name)
-- LOGGING DISABLED WHEN COMPILING("Pre-brief target added")
                end
            end
        end

        if parameters.debug then
            hound:onScreenDebug(true)
-- LOGGING DISABLED WHEN COMPILING("Debug enabled")
        end
    end

    if parameters.sectors and type(parameters.sectors) == 'table' then
-- LOGGING DISABLED WHEN COMPILING("Checking sectors...")
        for SectorName, sectorParameters in pairs(parameters.sectors) do
            local skip = false
        
-- LOGGING DISABLED WHEN COMPILING(string.format("Given Sector Name : %s", veaf.p(SectorName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Sector Params : %s", veaf.p(sectorParameters)))

            local SectorName = SectorName
            if not SectorName then
-- LOGGING DISABLED WHEN COMPILING("No SectorName has been given...")
                SectorName = "default"
                skip = true
            end

            if SectorName ~= "default" and tostring(SectorName) then
                SectorName = tostring(SectorName)
-- LOGGING DISABLED WHEN COMPILING(string.format("Retained Sector Name : %s", veaf.p(SectorName)))

                local sector = hound:addSector(SectorName)
                if sector then 
-- LOGGING DISABLED WHEN COMPILING("Added sector !")
                    if SectorName:lower() ~= veafHoundElint.globalSectorName:lower() then
-- LOGGING DISABLED WHEN COMPILING("Setting zone for sector...")
                        hound:setZone(SectorName, SectorName)
                        if not hound:getZone(SectorName) then
-- LOGGING DISABLED WHEN COMPILING("Could not find zone for sector...")
                            hound:removeSector(SectorName)
                            skip = true
                        else
-- LOGGING DISABLED WHEN COMPILING("Zone found and set")
                        end
                    else
-- LOGGING DISABLED WHEN COMPILING("No zone needs to be set for global sector")
                    end
                else
-- LOGGING DISABLED WHEN COMPILING("No sectors added, Hound problem...")
                    skip = true
                end
            elseif SectorName ~= "default" then
-- LOGGING DISABLED WHEN COMPILING("Given Sector Name could not be converted to string...")
                skip = true
            end

            if not skip then
                if veafHoundElint.hasSectorCallsign(sectorParameters) then
-- LOGGING DISABLED WHEN COMPILING("Setting sector callsign...")
                    if sectorParameters.callsign == true or not tostring(sectorParameters.callsign) then
-- LOGGING DISABLED WHEN COMPILING("Using sector name as callsign")
                        sectorParameters.callsign = SectorName
                    end
-- LOGGING DISABLED WHEN COMPILING(string.format("Callsign : %s", veaf.p(tostring(sectorParameters.callsign))))
                    hound:setCallsign(SectorName, tostring(sectorParameters.callsign))
                end

                if veafHoundElint.hasTransmitterUnit(sectorParameters) then
-- LOGGING DISABLED WHEN COMPILING("Setting transmitter unit...")
-- LOGGING DISABLED WHEN COMPILING(string.format("transmitter unitName : %s", veaf.p(tostring(sectorParameters.transmitterUnit))))
                    hound:setTransmitter(SectorName, tostring(sectorParameters.transmitterUnit))
                end

                if veafHoundElint.hasAtis(sectorParameters) then
-- LOGGING DISABLED WHEN COMPILING("Setting up ATIS...")
-- LOGGING DISABLED WHEN COMPILING(string.format("ATIS params : %s", veaf.p(sectorParameters.atis)))
                    hound:enableAtis(SectorName, sectorParameters.atis)
                    if sectorParameters.atis.reportEWR then 
-- LOGGING DISABLED WHEN COMPILING("ATIS will report EWRs as threats")
                        hound:reportEWR(SectorName, sectorParameters.atis.reportEWR) 
                    end
                end

                if veafHoundElint.hasController(sectorParameters) then
-- LOGGING DISABLED WHEN COMPILING("Setting up Controller...")
-- LOGGING DISABLED WHEN COMPILING(string.format("Controller params : %s", veaf.p(sectorParameters.controller)))
                    hound:enableController(SectorName, sectorParameters.controller)
                    local textMode = not(veafHoundElint.hasControllerVoice(sectorParameters))
                    if textMode then 
-- LOGGING DISABLED WHEN COMPILING("Controller is text only")
                        hound:enableText(SectorName)
                    end
                end

                if veafHoundElint.hasNotifier(sectorParameters) then
-- LOGGING DISABLED WHEN COMPILING("Setting up Notifier...")
-- LOGGING DISABLED WHEN COMPILING(string.format("Notifier params : %s", veaf.p(sectorParameters.notifier)))
                    hound:enableNotifier(SectorName, sectorParameters.notifier)
                end

                if veafHoundElint.hasNoAlerts(sectorParameters) then
-- LOGGING DISABLED WHEN COMPILING("Disabling alerts for controller/ATIS")
                    hound:disableAlerts(SectorName)
                end

                if veafHoundElint.hasNoTTS(sectorParameters) then
-- LOGGING DISABLED WHEN COMPILING("Disabling TTS overall")
                    hound:disableTTS(SectorName)
                end
            else
-- LOGGING DISABLED WHEN COMPILING("Skipping sector")
            end
        end
    else
-- LOGGING DISABLED WHEN COMPILING("No sectors to add/configure")
    end

    --activate the Hound system
    hound:systemOn()
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

local function createSystems(loadUnits, atMissionStart)
-- LOGGING DISABLED WHEN COMPILING(string.format("createSystems(%s, %s)", veaf.p(loadUnits), veaf.p(atMissionStart)))

    veafHoundElint.redHound = HoundElint:create(coalition.side.RED)
    veafHoundElint.redHound.name = "RED Hound"
    veafHoundElint.blueHound = HoundElint:create(coalition.side.BLUE)
    veafHoundElint.blueHound.name = "BLUE Hound"
    if loadUnits then
        initializeHoundSystem(coalition.side.RED, veafHoundElint.redParameters, atMissionStart)
        initializeHoundSystem(coalition.side.BLUE, veafHoundElint.blueParameters, atMissionStart)
    end
end

-- reset the Hound networks and rebuild them. Useful when a dynamic combat zone is deactivated
function veafHoundElint.reinitialize(delay)
-- LOGGING DISABLED WHEN COMPILING(string.format("reinitialize(%s)", veaf.p(delay)))
    if not veafHoundElint.reinitializeTaskID then
        if delay then
            veafHoundElint.reinitializeTaskID = mist.scheduleFunction(veafHoundElint._reinitialize , nil, veafHoundElint.DelayForStartup)
        end
    end
end

function veafHoundElint._reinitialize()
-- LOGGING DISABLED WHEN COMPILING(string.format("_reinitialize()"))

    if not veafHoundElint.initialized then 
        return false 
    end

    if veafHoundElint.redHound then
        veafHoundElint.redHound:systemOff()
    end
    if veafHoundElint.blueHound then
        veafHoundElint.blueHound:systemOff()
    end
    createSystems(true, false)

    if veafHoundElint.reinitializeTaskID then
        veafHoundElint.reinitializeTaskID = nil
    end
end

function veafHoundElint.hasSectorCallsign(parameters)
    return parameters.callsign and tostring(parameters.callsign)
end

function veafHoundElint.hasTransmitterUnit(parameters)
    return parameters and parameters.transmitterUnit and tostring(parameters.transmitterUnit) and Group.getByName(parameters.transmitterUnit)
end

function veafHoundElint.hasAtis(parameters)
    return parameters and parameters.atis
end

function veafHoundElint.hasController(parameters)
    return parameters and parameters.controller
end

function veafHoundElint.hasControllerVoice(parameters)
    return parameters and parameters.controller and parameters.controller.voiceEnabled
end

function veafHoundElint.hasNotifier(parameters)
    return parameters and parameters.notifier
end

function veafHoundElint.hasNoTTS(parameters)
    return parameters and parameters.disableTTS
end

function veafHoundElint.hasNoAlerts(parameters)
    return parameters and parameters.disableAlerts
end

function veafHoundElint.initialize(prefix, red, blue)
    veafHoundElint.prefix = prefix -- if nil, all capable units will be set as Elint platforms
    veafHoundElint.redParameters = red or {}
    veafHoundElint.blueParameters = blue or {}
    
    veaf.loggers.get(veafHoundElint.Id):info("Initializing module")
    
-- LOGGING DISABLED WHEN COMPILING(string.format("red=%s",veaf.p(red)))
-- LOGGING DISABLED WHEN COMPILING(string.format("blue=%s",veaf.p(blue)))
    
    -- prepare the list of units supported by Hound Elint
    for platformType, platformData in pairs(HOUND.DB.Platform[Object.Category.STATIC]) do
        veafHoundElint.elintUnitsTypes[platformType] = true
    end
    for platformType, platformData in pairs(HOUND.DB.Platform[Object.Category.UNIT]) do
        veafHoundElint.elintUnitsTypes[platformType] = true
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("veafHoundElint.elintUnitsTypes=%s",veaf.p(veafHoundElint.elintUnitsTypes)))
    veafHoundElint.initialized = true

    veaf.loggers.get(veafHoundElint.Id):info(string.format("Loading units"))
    createSystems(true, true)

    veaf.loggers.get(veafHoundElint.Id):info(string.format("Hound Elint has been initialized"))
end

veaf.loggers.get(veafHoundElint.Id):info(string.format("Loading version %s", veafHoundElint.Version))
HOUND.Utils.Marker._MarkId = 1235634 -- select a less obvious marker id range that `9999`

------------------ END script veafHoundElintHelper.lua  ------------------


------------------ START script veafMissileGuardian.lua  ------------------

------------------------------------------------------------------
-- VEAF missile guardian functions for DCS World
-- By zip (2020)
--
-- Features:
-- ---------
-- * GUARDIAN objects that are configured to warn and protect specific units (helos, airplanes) of weapons fired in their direction
-- * ANGEL objects following these weapons, warning their targets (distance, aspect, danger) and optionnaly destroy weapons in the air before impact (training, protection)
-- * Works with all current and future maps (Caucasus, NTTR, Normandy, PG, ...)
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafMissileGuardian = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafMissileGuardian.Id = "MISSILEGUARDIAN"

--- Version.
veafMissileGuardian.Version = "0.0.1"

-- trace level, specific to this module
--veafMissileGuardian.LogLevel = "trace"

veaf.loggers.new(veafMissileGuardian.Id, veafMissileGuardian.LogLevel)

--- Number of seconds between each check of the WIDE ZONE watchdog function
veafMissileGuardian.SecondsBetweenWideZoneWatchdogChecks = 5
--- Number of seconds between each check of the DANGER ZONE watchdog function
veafMissileGuardian.SecondsBetweenDangerZoneWatchdogChecks = 0.5

veafMissileGuardian.RadioMenuName = "GUARDIAN"

veafMissileGuardian.RemoteCommandParser = "([[a-zA-Z0-9]+)%s?([^%s]*)%s?(.*)"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Radio menus paths
veafMissileGuardian.rootPath = nil

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafMG_Weapon object ; represents a weapon in flight, detected by a Guardian and managed by an Protector
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafMG_Weapon =
{
    -- technical name
    name = nil,
    -- DCS weapon object
    dcsWeapon = nil,
    -- shooter
    shooter = nil,
    -- shooter name (if the shooter unit gets destroyed, keep its name)
    shooterName = nil,
}
VeafMG_Weapon.__index = VeafMG_Weapon

function VeafMG_Weapon:new()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Weapon:new()"))
    local self = setmetatable({}, VeafMG_Weapon)
    self.name = nil
    self.dcsWeapon = nil
    self.shooter = nil
    self.shooterName = nil
    return self
end

function VeafMG_Weapon:copy()
    local copy = VeafMG_Weapon:new()

    -- copy the attributes
    copy.name = self.name
    copy.dcsWeapon = self.dcsWeapon
    copy.shooter = self.shooter
    copy.shooterName = self.shooterName

    return copy
end

---
--- setters and getters
---

function VeafMG_Weapon:setName(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Weapon.setName([%s])",value or ""))
    self.name = value
    return self
end

function VeafMG_Weapon:getName()
    return self.name
end

function VeafMG_Weapon:setDcsWeapon(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Weapon[%s].setDcsWeapon()",self:getName() or ""))
    self.dcsWeapon = value
    if self.dcsWeapon then
        self.shooter = self.dcsWeapon:getLauncher()
        self.shooterName = veafMissileGuardian.getUnitName(self.shooter)
    end
    return self
end

function VeafMG_Weapon:getDcsWeapon()
    return self.dcsWeapon
end

function VeafMG_Weapon:getShooter()
    return self.shooter
end

function VeafMG_Weapon:getShooterName()
    return self.shooterName
end

---
--- other methods
---

function VeafMG_Weapon:getCurrentPosition()
    local _result = nil
    if self:getDcsWeapon() then 
        _result = self:getDcsWeapon():getPoint()
    end
    return _result
end

function VeafMG_Weapon:getCurrentTarget()
    local _result = nil
    if self:getDcsWeapon() then 
        _result = self:getDcsWeapon():getTarget()
    end
    return _result
end

function VeafMG_Weapon:getCurrentEnergy()
    local _result = nil
    if self:getDcsWeapon() then 
        local _mass = 250 -- let's say the missile weights 250kg
        local _vector = self:getDcsWeapon():getVelocity()
        local _absVelocity = mist.vec.mag(_vector)
        local _kinetic = (_mass / 2) * _absVelocity * _absVelocity
        local _alt = self:getDcsWeapon():getPoint().z
        local _potential = _mass * 9.81 * _alt
        _result = _kinetic + _potential
    end
    return _result
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafMG_Guardian object
-- The Guardian observes units and reacts if one of them fires a weapon by encapsulating it into a VeafMG_Weapon and passing it to a VeafMG_Protector for observation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafMG_Guardian =
{
    -- technical name
    name = nil,
    -- human-friendly name
    friendlyName = nil,
    -- list of units that should be protected from missiles
    protectedUnits = nil,
    -- polygon describing a zone when the units should be to be protected
    protectedZone = nil,
}
VeafMG_Guardian.__index = VeafMG_Guardian

VeafMG_Guardian.WARNING_MESSAGE = "Warning, %s : you've been attacked by %s and a missile is in the air"
VeafMG_Guardian.WARNING_MESSAGE_TIME = 10

function VeafMG_Guardian:new()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Guardian:new()"))
    local self = setmetatable({}, VeafMG_Guardian)
    self.name = nil
    self.friendlyName = nil
    self.protectedUnits = {}
    self.protectedZone = {}
    return self
end

function VeafMG_Guardian:copy()
    local copy = VeafMG_Guardian:new()

    -- copy the attributes
    copy.name = self.name
    copy.friendlyName = self.friendlyName
        
    -- deep copy the collections
    copy.protectedUnits = {}
    for unitName, value in pairs(self.protectedUnits) do
        copy.protectedZone[unitName] = value
    end

    copy.protectedZone = {}
    for _, value in pairs(self.protectedZone) do
        table.insert(copy.protectedZone, value)
    end

    return copy
end

---
--- setters and getters
---

function VeafMG_Guardian:setName(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Guardian[]:setName([%s])",value or ""))
    self.name = value
    return self
end

function VeafMG_Guardian:getName()
    return self.name
end

function VeafMG_Guardian:setFriendlyName(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Guardian[%s]:setFriendlyName()",self:getName() or ""))
    self.friendlyName = value
    return self
end

function VeafMG_Guardian:getFriendlyName()
    return self.friendlyName
end

function VeafMG_Guardian:addProtectedUnit(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Guardian[%s]:addProtectedUnit()",self:getName() or ""))
    if type(value) ~= "string" then
        value = value:getName()
    end
    self.protectedUnits[value] = "protected"
    return self
end

function VeafMG_Guardian:setProtectedZone(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Guardian[%s]:setProtectedZone()",self:getName() or ""))
    self.protectedZone = value
    return self
end

---
--- other methods
---

--- event handler
function VeafMG_Guardian:onEvent(event)
    -- only react to S_EVENT_SHOT events
    if event and event.id == world.event.S_EVENT_SHOT then
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Protector:onEvent(S_EVENT_SHOT) : %s", veaf.p(event)))
        
        if event.weapon then
            -- check if the target is one of the protected units
            local _target = event.weapon:getTarget()
            if _target then
                local _targetName = _target:getName()
-- LOGGING DISABLED WHEN COMPILING(string.format("_targetName = %s", veaf.p(_targetName)))
                if self.protectedUnits[_targetName] then 
                    -- check if the target is in the protected zone
                    local _inZone = mist.pointInPolygon(_target:getPoint(), self.protectedZone)
-- LOGGING DISABLED WHEN COMPILING(string.format("_inZone = %s", veaf.p(_inZone)))
                    if _inZone then 
                        -- encapsulate the event weapon
                        local _weapon = VeafMG_Weapon:new():setDcsWeapon(event.weapon)

                        -- message the target unit
                        local _groupId = _target:getGroup():getID()
                        local _playername = _target:getPlayerName()
                        if _playername then
-- LOGGING DISABLED WHEN COMPILING(string.format("Issuing a warning to unit %s", veaf.p(_playername)))
                            trigger.action.outTextForGroup(_groupId, string.format(VeafMG_Guardian.WARNING_MESSAGE, _playername, _weapon:getShooterName()), VeafMG_Guardian.WARNING_MESSAGE_TIME)
                        end

                        -- pass the weapon to the large-scale protector
                        veafMissileGuardian.getLargeScaleProtector():setWeapon(_weapon)    
                    end
                end
            end
        end
    end
end

function VeafMG_Guardian:start()
    -- register event handler
    world.addEventHandler(self)
end

function VeafMG_Guardian:stop()
    -- deregister event handler
    world.removeEventHandler(self)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafMG_Protector object
-- Responsible for observing and reporting VeafMG_Weapons behavior, and protecting the registered units by destroying the weapons in flight when needed
-------------------------------------------------------------------------------------------------------------------------------------------------------------
VeafMG_Protector =
{
    -- technical name
    name = nil,
    -- seconds between watchdog checks
    secondsBetweenWatchdogChecks = nil,
    -- weapon we're trying to protect from
    weapon = nil,
}
VeafMG_Protector.__index = VeafMG_Protector

function VeafMG_Protector:new()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Protector:new()"))
    local self = setmetatable({}, VeafMG_Protector)
    self.name = nil
    self.secondsBetweenWatchdogChecks = nil
    self.weapon = nil
    return self
end

function VeafMG_Protector:copy()
    local copy = VeafMG_Protector:new()

    -- copy the attributes
    copy.name = self.name
    copy.secondsBetweenWatchdogChecks = self.secondsBetweenWatchdogChecks
    copy.weapon = self.weapon
        
    -- deep copy the collections
    -- copy.parameters = {}
    -- for name, value in pairs(self.parameters) do
-- LOGGING DISABLED WHEN COMPILING(string.format("copying parameter %s : ",tostring(name)))
    --     copy.parameters[name]=value
    -- end

    return copy
end

---
--- setters and getters
---

function VeafMG_Protector:setName(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Protector[]:setName([%s])",value or ""))
    self.name = value
    return self
end

function VeafMG_Protector:getName()
    return self.name
end

function VeafMG_Protector:setSecondsBetweenWatchdogChecks(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Protector[%s]:setSecondsBetweenWatchdogChecks()",self:getName() or ""))
    self.secondsBetweenWatchdogChecks = value
    return self
end

function VeafMG_Protector:getSecondsBetweenWatchdogChecks()
    return self.secondsBetweenWatchdogChecks
end

function VeafMG_Protector:setWeapon(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafMG_Protector[%s]:setWeapon()",self:getName() or ""))
    self.weapon = value
    return self
end

---
--- other methods
---
---

function VeafMG_Protector:start()
    -- schedule the watchdog function
end

function VeafMG_Protector:stop()
    -- unschedule the watchdog function
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- local functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafMissileGuardian.getUnitName(unit)
    return unit:getName() -- TODO make this useful (add the player name if possible)
end

function veafMissileGuardian.getLargeScaleProtector()
    -- TODO
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- global functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- add a new guardian
function veafMissileGuardian.AddGuardian(guardian)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMissileGuardian.AddGuardian([%s])",guardian:getName() or ""))
    return guardian
end

-- activate a guardian
function veafMissileGuardian.ActivateGuardian(name, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMissileGuardian.ActivateGuardian([%s])",name or ""))
    local guardian = veafMissileGuardian.GetGuardian(name)
    local result = guardian:activate(silent)
    if not silent and not guardian:isSilent() then
        if result then
            trigger.action.outText("VeafMG_Guardian "..guardian:getFriendlyName().." has been activated.", 10)
        else
            trigger.action.outText("VeafMG_Guardian "..guardian:getFriendlyName().." was already active.", 10)
        end
    end
    veafMissileGuardian.buildRadioMenu()
end

-- desactivate a guardian
function veafMissileGuardian.DesactivateGuardian(name, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMissileGuardian.DesactivateGuardian([%s])",name or ""))
    local guardian = veafMissileGuardian.GetGuardian(name)
    local result = guardian:desactivate(silent)
    if not silent and not guardian:isSilent() then
        if result then
            trigger.action.outText("VeafMG_Guardian "..guardian:getFriendlyName().." has been desactivated.", 10)
        else
            trigger.action.outText("VeafMG_Guardian "..guardian:getFriendlyName().." was already inactive.", 10)
        end
    end
    veafMissileGuardian.buildRadioMenu()
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafMissileGuardian._buildMissionRadioMenu(menu, title, element)
    local missions = element.missions
    if #missions == 1 then
        -- one simple mission
        local mission = missions[1]
        if mission:isActive() then title = "* "..title end
        mission.radioRootPath = veafRadio.addSubMenu(title, menu)
        mission:updateRadioMenu(true)
    else
        -- group by skill and scale
-- LOGGING DISABLED WHEN COMPILING("group by skill and scale")
        local skills = {}
        for _, mission in pairs(missions) do
            local regex = ("^([^/]+)/([^/]+)/(%d+)$")
            local name, skill, scale = mission:getName():match(regex)
-- LOGGING DISABLED WHEN COMPILING(string.format("missionName=[%s], name=%s, skill=%s, scale=%s", tostring(mission:getName()), tostring(name), tostring(skill), tostring(scale)))
            if not skills[skill] then 
                skills[skill] = {} 
            end
            skills[skill][scale] = mission 
        end
        
-- LOGGING DISABLED WHEN COMPILING(string.format("skills=%s", veaf.p(skills)))
        
        -- create the radio menus
        local title = title
        if element.activeGroups then title = "* "..title end
        local missionPath = veafRadio.addSubMenu(title, menu)
-- LOGGING DISABLED WHEN COMPILING(string.format("  %s", title))
        local skillsNames = {}
        for skill, _ in pairs(skills) do
            table.insert(skillsNames, skill)
        end
        table.sort(skillsNames)
        for _, skill in pairs(skillsNames) do
            local scales = skills[skill]
            local skillTitle = skill
            if element.activeGroups and element.activeGroups[skill] then skillTitle = "* "..skillTitle end
            local skillPath = veafRadio.addSubMenu(skillTitle, missionPath)
-- LOGGING DISABLED WHEN COMPILING(string.format("    %s", skill))
            local scalesNames = {}
            for scale, _ in pairs(scales) do
                table.insert(scalesNames, scale)
            end
            table.sort(scalesNames)
            for _, scale in pairs(scalesNames) do
                local mission = scales[scale]
                local scaleTitle = "scale "..scale
                if element.activeGroups and element.activeGroups[skill] and element.activeGroups[skill][scale] then scaleTitle = "* "..scaleTitle end
                local scalePath = veafRadio.addSubMenu(scaleTitle, skillPath)
-- LOGGING DISABLED WHEN COMPILING(string.format("      %s", scale))
                mission.radioRootPath = scalePath
                mission:updateRadioMenu(true)
            end
        end
    end
end

--- Build the initial radio menu
function veafMissileGuardian.buildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("buildRadioMenu()")
    if veafMissileGuardian.rootPath then 
        veafRadio.clearSubmenu(veafMissileGuardian.rootPath)
    else
        veafMissileGuardian.rootPath = veafRadio.addMenu(veafMissileGuardian.RadioMenuName)
    end
    if not(veafRadio.skipHelpMenus) then
        veafRadio.addCommandToSubmenu("HELP", veafMissileGuardian.rootPath, veafMissileGuardian.help, nil, veafRadio.USAGE_ForGroup)
    end
    veafRadio.refreshRadioMenu()
end

function veafMissileGuardian.listGuardians() 
    -- sort the missions alphabetically
    local sortedMissions = {}
    table.sort(sortedMissions)
    
    local text =
    'List of all available guardians:\n'

    for _, missionName in pairs(sortedMissions) do
        text = text .. " - " .. missionName .. "\n"
    end
    
    trigger.action.outText(text, 20)
end

function veafMissileGuardian.listActiveMissions() 
    -- sort the missions alphabetically
    local sortedMissions = {}
    for _, mission in pairs(veafMissileGuardian.missionsDict) do
        if mission:isActive() then
            table.insert(sortedMissions, mission:getName() .. ' : ' .. mission:getRemainingEnemiesString())
        end
    end
    table.sort(sortedMissions)
    
    local text =
    'No active combat mission !'

    if #sortedMissions > 0 then
        text =
        'List of active combat missions:\n'

        for _, missionName in pairs(sortedMissions) do
            text = text .. " - " .. missionName .. "\n"
        end
    end    

    trigger.action.outText(text, 20)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- remote interface
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- execute command from the remote interface
function veafMissileGuardian.executeCommandFromRemote(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMissileGuardian.executeCommandFromRemote()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("parameters= %s", veaf.p(parameters)))
    local _pilot, _pilotName, _unitName, _command = unpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilot= %s", veaf.p(_pilot)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilotName= %s", veaf.p(_pilotName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_unitName= %s", veaf.p(_unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_command= %s", veaf.p(_command)))
    if not _pilot or not _command then 
        return false
    end

    if _command then
        -- parse the command
        local _action, _missionName, _parameters = _command:match(veafMissileGuardian.RemoteCommandParser)
-- LOGGING DISABLED WHEN COMPILING(string.format("_action=%s",veaf.p(_action)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_guardianName=%s",veaf.p(_missionName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_parameters=%s",veaf.p(_parameters)))
        if _action and _action:lower() == "list" then 
            veaf.loggers.get(veafMissileGuardian.Id):info(string.format("[%s] is listing air missions)",veaf.p(_pilot.name)))
            veafMissileGuardian.listAvailableMissions()
            return true
        elseif _action and _action:lower() == "start" and _missionName then 
            local _silent = _parameters and _parameters:lower() == "silent"
            veaf.loggers.get(veafMissileGuardian.Id):info(string.format("[%s] is starting air mission [%s] %s)",veaf.p(_pilot.name), veaf.p(_missionName), veaf.p(_parameters)))
            veafMissileGuardian.ActivateMission(_missionName, _silent)
            return true
        elseif _action and _action:lower() == "stop" then 
            local _silent = _parameters and _parameters:lower() == "silent"
            veaf.loggers.get(veafMissileGuardian.Id):info(string.format("[%s] is stopping air mission [%s] %s)",veaf.p(_pilot.name), veaf.p(_missionName), veaf.p(_parameters)))
            veafMissileGuardian.DesactivateMission(_missionName, _silent)
            return true
        end
    end               
    return false
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafMissileGuardian.initialize()
    veaf.loggers.get(veafMissileGuardian.Id):info("Initializing module")
    veafMissileGuardian.buildRadioMenu()
    veafMissileGuardian.dumpMissionsList(veaf.config.MISSION_EXPORT_PATH)
end

veaf.loggers.get(veafMissileGuardian.Id):info(string.format("Loading version %s", veafMissileGuardian.Version))


------------------ END script veafMissileGuardian.lua  ------------------


------------------ START script veafMove.lua  ------------------

------------------------------------------------------------------
-- VEAF move units for DCS World
-- By mitch (2018)
--
-- Features:
-- ---------
-- * Listen to marker change events and execute move commands, with optional parameters
-- * Possibilities : 
-- *    - move a specific group to a marker point, at a specific speed
-- *    - create a new tanker flightplan, moving a specific tanker group
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

--- veafMove Table.
veafMove = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafMove.Id = "MOVE"

--- Version.
veafMove.Version = "1.9.4"

-- trace level, specific to this module
--veafMove.LogLevel = "trace"

veaf.loggers.new(veafMove.Id, veafMove.LogLevel)

--- Key phrase to look for in the mark text which triggers the command.
veafMove.Keyphrase = "_move"

veafMove.RadioMenuName = "MOVE"

veafMove.tankerMissionParameters = {
    ["A-10C"] = {speed=250, alt=12000},
    ["A-10C_2"] = {speed=250, alt=12000},
    ["AV8BNA"] = {speed=350, alt=18000},
    ["F-14A"] = {speed=400, alt=22000},
    ["F-14A-135-GR"] = {speed=400, alt=22000},
    ["F-14B"] = {speed=400, alt=22000},
    ["F-15C"] = {speed=400, alt=22000},
    ["F-15E"] = {speed=400, alt=22000},
    ["F-16A"] = {speed=400, alt=22000},
    ["F-16A MLU"] = {speed=400, alt=22000},
    ["F-16C bl.50"] = {speed=400, alt=22000},
    ["F-16C bl.52d"] = {speed=400, alt=22000},
    ["F-16C_50"] = {speed=400, alt=22000},
    ["F/A-18A"] = {speed=400, alt=22000},
    ["F/A-18C"] = {speed=400, alt=22000},
    ["FA-18C_hornet"] = {speed=400, alt=22000},
    ["JF-17"] = {speed=400, alt=22000},
    ["M-2000C"] = {speed=400, alt=22000},
    ["MiG-29K"] = {speed=400, alt=22000},
    ["MiG-31"] = {speed=400, alt=22000},
    ["Mirage 2000-5"] = {speed=400, alt=22000},
    ["Su-24M"] = {speed=400, alt=22000},
    ["Su-24MR"] = {speed=400, alt=22000},
    ["Su-33"] = {speed=400, alt=22000},
    ["Su-34"] = {speed=400, alt=22000},
    ["Tornado GR4"] = {speed=400, alt=22000},
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafMove.rootPath = nil

--- Initial Marker id.
veafMove.markid = 20000

traceMarkerId = 6548
debugMarkers = {}

veafMove.Tankers = {}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafMove.onEventMarkChange(eventPos, event)
    if veafMove.executeCommand(eventPos, event.text) then

        -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
        trigger.action.removeMark(event.idx)

    end
end

function veafMove.executeCommand(eventPos, eventText, bypassSecurity)

    -- Check if marker has a text and the veafMove.keyphrase keyphrase.
    if eventText ~= nil and eventText:lower():find(veafMove.Keyphrase) then

        -- Analyse the mark point text and extract the keywords.
        local options = veafMove.markTextAnalysis(eventText)
        local result = false

        if options then
            -- Check options commands
            if options.moveGroup then
                result = veafMove.moveGroup(eventPos, options.groupName, options.speed, options.altitude)
            elseif options.moveTanker then
                result = veafMove.moveTanker(eventPos, options.groupName, options.speed, options.altitude, options.hdg, options.distance, options.teleport, options.silent)
            elseif options.changeTanker then
                result = veafMove.changeTanker(eventPos, options.speed, options.altitude)
            elseif options.moveAfac then
                result = veafMove.moveAfac(eventPos, options.groupName, options.speed, options.altitude, options.hdg, options.immortal)
            end
        else
            -- None of the keywords matched.
            return false
        end

        return result
    end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the mark text and extract keywords.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Extract keywords from mark text.
function veafMove.markTextAnalysis(text)

    -- Option parameters extracted from the mark text.
    local switch = {}
    switch.moveGroup = false
    switch.moveTanker = false
    switch.changeTanker = false
    switch.moveAfac = false

    -- the name of the group to move ; mandatory
    switch.groupName = ""

    -- speed in knots
    switch.speed = -1 -- defaults to original speed

    -- tanker refuel leg altitude in feet
    switch.altitude = -1 -- defaults to tanker original altitude

    -- tanker refuel leg heading in degrees
    switch.hdg = nil -- defaults to original heading

    -- option to set AFAC to immortal
    switch.immortal = false

    -- tanker refuel leg distance in degrees
    switch.distance = nil -- defaults to original distance

    -- if true, teleport the tanker instead of simply making it move
    switch.teleport = false

    -- if false, Named Points will be created when moving the tankers
    switch.silent = false

    -- Check for correct keywords.
    if text:lower():find(veafMove.Keyphrase .. " group") then
        switch.moveGroup = true
        switch.speed = 20
    elseif text:lower():find(veafMove.Keyphrase .. " tankermission") then
        switch.changeTanker = true
        switch.speed = -1
        switch.altitude = -1
    elseif text:lower():find(veafMove.Keyphrase .. " tanker") then
        switch.moveTanker = true
        switch.speed = -1
        switch.altitude = -1
    elseif text:lower():find(veafMove.Keyphrase .. " afac") then
        switch.moveAfac = true
        switch.speed = 150
        switch.altitude = 15000
    else
        return nil
    end

    -- keywords are split by ","
    local keywords = veaf.split(text, ",")

    for _, keyphrase in pairs(keywords) do
        -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
        local str = veaf.breakString(veaf.trim(keyphrase), " ")
        local key = str[1]
        local val = str[2]

        if key:lower() == "name" then
            -- Set group name
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword name = %s", val))
            switch.groupName = val
        end

        if key:lower() == "speed" or key:lower() == "spd" then
            -- Set speed.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword speed = %d", val))
            local nVal = tonumber(val)
            switch.speed = nVal
        end

        if key:lower() == "heading" or key:lower() == "hdg" then
            -- Set heading.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword hdg = %d", val))
            local nVal = tonumber(val)
            switch.hdg = nVal
        end

        if key:lower() == "distance" or key:lower() == "dist" then
            -- Set distance.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword distance = %d", val))
            local nVal = tonumber(val)
            switch.distance = nVal
        end

        if key:lower() == "alt" or key:lower() == "altitude" then
            -- Set altitude.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword alt = %d", val))
            local nVal = tonumber(val)
            switch.altitude = nVal
        end

        if key:lower() == "teleport" then
-- LOGGING DISABLED WHEN COMPILING("Keyword teleport found")
            switch.teleport = true
        end

        if key:lower() == "silent" then
-- LOGGING DISABLED WHEN COMPILING("Keyword silent found")
            switch.silent = true
        end

        if key:lower() == "immortal" then
-- LOGGING DISABLED WHEN COMPILING("Keyword immortal found")
            switch.immortal = true
        end

    end

    -- check mandatory parameter "group"
    if not(switch.groupName) then return nil end
    return switch
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Group move command
-------------------------------------------------------------------------------------------------------------------------------------------------------------

------------------------------------------------------------------------------
-- veafMove.moveGroup
-- @param point eventPos
-- @param string groupName the group name to move on
-- @param float speed in knots
------------------------------------------------------------------------------
function veafMove.moveGroup(eventPos, groupName, speed, altitude)
-- LOGGING DISABLED WHEN COMPILING("veafMove.moveGroup(groupName = " .. groupName .. ", speed = " .. speed .. ", altitude=".. altitude)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMove.moveGroup: eventPos  x=%.1f z=%.1f", eventPos.x, eventPos.z))

    local result = veaf.moveGroupTo(groupName, eventPos, speed, altitude)
    if not(result) then
        trigger.action.outText(groupName .. ' not found for move group command' , 10)
    end
    return result
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Change tanker mission parameters
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafMove.changeTanker(eventPos, speed, alt)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMove.changeTanker(speed=%s, alt=%s)", tostring(speed), tostring(alt)))
-- LOGGING DISABLED WHEN COMPILING(string.format("eventPos=%s",veaf.p(eventPos)))
-- LOGGING DISABLED WHEN COMPILING(debugMarkers)

    local tankerUnit = nil
    local units = veaf.findUnitsInCircle(eventPos, 2000, false)
-- LOGGING DISABLED WHEN COMPILING(string.format("units=%s", veaf.p(units)))
    if units then
        for name, _ in pairs(units) do
            -- try and find a tanker unit
            local unit = Unit.getByName(name)
            if unit and unit:getDesc()["attributes"]["Tankers"] then
                tankerUnit = unit
                break
            end
        end
    end

    if not tankerUnit then
        veaf.loggers.get(veafMove.Id):warn("Cannot find tanker unit around marker")
		trigger.action.outText("Cannot find tanker unit around marker" , 10)
        return false
    end

    local tankerGroup = tankerUnit:getGroup()
    local tankerGroupName = tankerGroup:getName()

    local tankerData = veaf.getGroupData(tankerGroupName)
    if not(tankerData) then
        local text = "Cannot move tanker " .. tankerGroupName .. " ; cannot find group data"
        veaf.loggers.get(veafMove.Id):info(text)
        trigger.action.outText(text)
        return
    end

    local route = veaf.findInTable(tankerData, "route")
    local points = veaf.findInTable(route, "points")
    if points then
-- LOGGING DISABLED WHEN COMPILING("found a " .. #points .. "-points route for tanker " .. tankerGroupName)
        -- modify the last 3 points
        local idxPoint1 = #points-2
        local idxPoint2 = #points-1
        local idxPoint3 = #points

        -- point1 is the point where the tanker mission starts ; we'll change the speed and altitude
        local point1 = points[idxPoint1]
-- LOGGING DISABLED WHEN COMPILING("found point1")
        -- set speed
        if speed > -1 then
            point1.speed = speed/1.94384  -- in m/s
        else
            speed = point1.speed*1.94384  -- in knots 
        end
        -- set altitude
        if alt > -1 then
            point1.alt = alt * 0.3048 -- in meters
        else
            alt = point1.alt / 0.3048 -- in feet
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("newPoint1=%s",veaf.p(point1)))

        -- point 2 is the start of the tanking Orbit ; we'll change the speed and altitude
        local point2 = points[idxPoint2]
-- LOGGING DISABLED WHEN COMPILING("found point2")
        local foundOrbit = false
        local task1 = veaf.findInTable(point2, "task")
        if task1 then
            local tasks = task1.params.tasks
            if (tasks) then
-- LOGGING DISABLED WHEN COMPILING("found %s tasks", veaf.p(#tasks))
                for j, task in pairs(tasks) do
-- LOGGING DISABLED WHEN COMPILING("found task #%s", veaf.p(j))
                    if task.params then
-- LOGGING DISABLED WHEN COMPILING("has .params")
                        if task.id and task.id == "Orbit" then
-- LOGGING DISABLED WHEN COMPILING("Found a ORBIT task for tanker " .. tankerGroupName)
                            foundOrbit = true
                            if speed > -1 then
                                task.params.speed = speed/1.94384  -- in m/s
                                point2.speed = speed/1.94384  -- in m/s
                            end
                            if alt > -1 then
                                task.params.altitude = alt * 0.3048 -- in meters
                                point2.alt = alt * 0.3048 -- in meters
                            end
                        end
                    end
                end
            end
        end
        if not foundOrbit then
            local text = "Cannot set tanker " .. tankerGroupName .. " parameters because it has no ORBIT task defined"
            veaf.loggers.get(veafMove.Id):info(text)
            trigger.action.outText(text)
            return
        end

        -- point 3 is the end of the tanking Orbit ; we'll change the speed and altitude
        local point3 = points[idxPoint3]
-- LOGGING DISABLED WHEN COMPILING("found point3")
        -- change speed
        if speed > -1 then
            point3.speed = speed/1.94384  -- in m/s
        end
        -- change altitude
        if alt > -1 then
            point3.alt = alt * 0.3048 -- in meters
        end
-- LOGGING DISABLED WHEN COMPILING("newpoint3="..veaf.p(point3))

        -- replace whole mission
-- LOGGING DISABLED WHEN COMPILING("Resetting changed tanker mission")
        -- replace the mission
        local mission = {
            id = 'Mission',
            params = tankerData
        }
        local controller = tankerGroup:getController()
        controller:setTask(mission)

        local msg = string.format("Set tanker %s to %d kn (ground) at %d ft", tankerGroupName, speed, alt)
        veaf.loggers.get(veafMove.Id):info(msg)
		trigger.action.outText(msg , 10)
        return true
    else
        return false
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Tanker move command
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafMove.moveTanker(eventPos, groupName, speed, alt, hdg, distance, teleport, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMove.moveTanker(groupName=%s, speed=%s, alt=%s, hdg=%s, distance=%s)",tostring(groupName), tostring(speed), tostring(alt), tostring(hdg), tostring(distance)))
-- LOGGING DISABLED WHEN COMPILING(debugMarkers)

-- LOGGING DISABLED WHEN COMPILING(string.format("eventPos=%s",veaf.p(eventPos)))

    local FIRSTPOINT_DISTANCE_SECONDS = 60 -- seconds to fly to WP1

    local unitGroup = Group.getByName(groupName)
	if unitGroup == nil then
        veaf.loggers.get(veafMove.Id):info(groupName .. ' not found for move tanker command')
		trigger.action.outText(groupName .. ' not found for move tanker command' , 10)
		return false
    end

    local tankerData = veaf.getGroupData(groupName)
    if not(tankerData) then
        local text = "Cannot move tanker " .. groupName .. " ; no group data"
        veaf.loggers.get(veafMove.Id):info(text)
        trigger.action.outText(text)
        return false
    end
-- LOGGING DISABLED WHEN COMPILING("tankerData : %s", veaf.p(tankerData))

    local route = veaf.findInTable(tankerData, "route")
    local points = veaf.findInTable(route, "points")
    if points then
-- LOGGING DISABLED WHEN COMPILING("found a " .. #points .. "-points route for tanker " .. groupName)
        -- modify the last 3 points
        local idxPoint1 = #points-2
        local idxPoint2 = #points-1
        local idxPoint3 = #points

        local point1 = points[idxPoint1]
-- LOGGING DISABLED WHEN COMPILING(string.format("point1=%s",veaf.p(point1)))

        local point2 = points[idxPoint2]
-- LOGGING DISABLED WHEN COMPILING(string.format("point2=%s",veaf.p(point2)))

        local point3 = points[idxPoint3]
-- LOGGING DISABLED WHEN COMPILING(string.format("point3=%s",veaf.p(point3)))

        -- if distance is not set, compute distance between point2 and point3
        local distance = distance
        if distance == nil then
            distance = math.sqrt((point3.x - point2.x)^2+(point3.y - point2.y)^2)
        else
            -- convert distance to meters
            distance = distance * 1852 -- meters
        end

        -- if hdg is not set, compute heading between point2 and point3
        local hdg = hdg
        if hdg == nil then
            hdg = veaf.headingBetweenPoints(point2, point3)
        else
            hdg = hdg * math.pi/180
        end

        -- if speed is not set, use point2 speed
        local speed = speed
        if speed == nil or speed < 0 then
            speed = point2.speed
        else
            -- convert speed to m/s
            speed = speed/1.94384
        end

        -- if alt is not set, use point2 altitude
        local alt = alt
        if alt == nil or alt < 0 then
            alt = point2.alt
        else
            -- convert altitude to meters
            alt = alt * 0.3048 -- meters
        end

-- LOGGING DISABLED WHEN COMPILING(string.format("distance=%s",veaf.p(distance)))
-- LOGGING DISABLED WHEN COMPILING(string.format("hdg=%s",veaf.p(hdg)))
-- LOGGING DISABLED WHEN COMPILING(string.format("speed=%s",veaf.p(speed)))
-- LOGGING DISABLED WHEN COMPILING(string.format("alt=%s",veaf.p(alt)))

        -- the first point in the refuel leg is based on the marker position
        local startLegPoint= { x=eventPos.x, y=eventPos.z, alt=alt, speed=speed }
-- LOGGING DISABLED WHEN COMPILING(string.format("startLegPoint=%s",veaf.p(startLegPoint)))
        if veafNamedPoints and not silent then
            veafNamedPoints.namePoint({x=startLegPoint.x, y=startLegPoint.alt, z=startLegPoint.y}, groupName .. " refuel start", unitGroup:getCoalition(), true)
        end

        -- compute the second point in the refuel leg based on desired heading and distance
        local endLegPoint= { x=startLegPoint.x, y=startLegPoint.y, alt=alt, speed=speed }
-- LOGGING DISABLED WHEN COMPILING(string.format("distance=%s",veaf.p(distance)))
-- LOGGING DISABLED WHEN COMPILING(string.format("hdg=%s",veaf.p(hdg)))
        endLegPoint.x = startLegPoint.x + distance * math.cos(hdg)
        endLegPoint.y = startLegPoint.y + distance * math.sin(hdg)
-- LOGGING DISABLED WHEN COMPILING(string.format("endLegPoint=%s",veaf.p(endLegPoint)))
        if veafNamedPoints and not silent then
            veafNamedPoints.namePoint({x=endLegPoint.x, y=endLegPoint.alt, z=endLegPoint.y}, groupName .. " refuel end", unitGroup:getCoalition(), true)
        end

        -- compute the point where the tanker should move in the opposite direction from the desired heading, at a standard distance
        local movePoint= { x=startLegPoint.x, y=startLegPoint.y, alt=alt, speed=speed }
        local teleportPoint= { x=startLegPoint.x, y=startLegPoint.y, alt=alt, speed=speed }
        local reverseHdg = hdg - math.pi
        if reverseHdg < 0 then
            reverseHdg = reverseHdg + math.pi*2
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("reverseHdg=%s",veaf.p(reverseHdg)))
        movePoint.x = startLegPoint.x + 1.5 * speed * FIRSTPOINT_DISTANCE_SECONDS * math.cos(reverseHdg)
        movePoint.y = startLegPoint.y + 1.5 * speed * FIRSTPOINT_DISTANCE_SECONDS * math.sin(reverseHdg)
        teleportPoint.x = startLegPoint.x + 3 * speed * FIRSTPOINT_DISTANCE_SECONDS * math.cos(reverseHdg)
        teleportPoint.y = startLegPoint.y + 3 * speed * FIRSTPOINT_DISTANCE_SECONDS * math.sin(reverseHdg)
-- LOGGING DISABLED WHEN COMPILING(string.format("movePoint=%s",veaf.p(movePoint)))

        -- set point1 to the computed movePoint
        point1.x = movePoint.x
        point1.y = movePoint.y
        point1.alt = movePoint.alt
        point1.speed = movePoint.speed
-- LOGGING DISABLED WHEN COMPILING(string.format("newPoint1=%s",veaf.p(point1)))

        -- set point2 to the start of the tanking Orbit (startLegPoint)
        local foundOrbit = false
        local task1 = veaf.findInTable(point2, "task")
        if task1 then
            local tasks = task1.params.tasks
            if (tasks) then
-- LOGGING DISABLED WHEN COMPILING("found " .. #tasks .. " tasks")
                for j, task in pairs(tasks) do
-- LOGGING DISABLED WHEN COMPILING(string.format("found task #%s", veaf.p(j)))
                    if task.params then
-- LOGGING DISABLED WHEN COMPILING("has .params")
                        if task.id and task.id == "Orbit" then
-- LOGGING DISABLED WHEN COMPILING("Found a ORBIT task for tanker " .. groupName)
                            foundOrbit = true
                            task.params.speed = speed
                            task.params.altitude = alt
                        end
                    end
                end
            end
        end
        if not foundOrbit then
            local text = "Cannot move tanker " .. groupName .. " because it has no ORBIT task defined"
            veaf.loggers.get(veafMove.Id):info(text)
            trigger.action.outText(text, 10)
            return false
        end
        point2.x = startLegPoint.x
        point2.y = startLegPoint.y
        point2.alt = startLegPoint.alt
        point2.speed = startLegPoint.speed
-- LOGGING DISABLED WHEN COMPILING(string.format("newPoint2=%s",veaf.p(point2)))

        -- set point2 to the end of the tanking Orbit (endLegPoint)
        point3.x = endLegPoint.x
        point3.y = endLegPoint.y
        point3.alt = endLegPoint.alt
        point3.speed = endLegPoint.speed
-- LOGGING DISABLED WHEN COMPILING("newpoint3="..veaf.p(point3))

        --actually move the group
        local delay = 0

        -- teleport if the option is set
        if teleport then
-- LOGGING DISABLED WHEN COMPILING("Teleport the tanker")
            local vars = { groupName = groupName, point = teleportPoint, action = "teleport"}
            local grp = mist.teleportToPoint(vars)
            unitGroup = Group.getByName(groupName)

            veafMove.teleportEscort(groupName, movePoint, teleportPoint)

            delay = 1
        end

-- LOGGING DISABLED WHEN COMPILING(string.format("Resetting moved tanker mission in %d seconds", delay))
        veafMove.replaceMission(unitGroup, tankerData, delay)

        return true
    else
        return false
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Escort move method, only called internally
-- @param escorted_groupName, string, corresponds to the groupname of the aicraft being escorted
-- @param movePoint, vec3 + speed, corresponds to the first waypoint that the escorted aircraft will take after it was moved
-- @param teleportPoint, vec3 + speed, corresponds to the waypoint on which the escorted aircraft is teleported to, this is required
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafMove.teleportEscort(escorted_groupName, movePoint, teleportPoint)

    --verify existence of the escorted aircraft
    local unitGroup = Group.getByName(escorted_groupName)

    if not unitGroup then
        veaf.loggers.get(veafMove.Id):info("Cannot move the escort of " .. escorted_groupName .. " ; this groupName does not correspond to any aircraft")
        return false
    end

    --verify the existence of the escort and proper configuration
    local escortedId = Group.getID(unitGroup) --this and only this serves as a groupID, what is given in EscortData does not correspond on the DCS side
    local groupName_escort = escorted_groupName .. " escort" --standardized escort groupName
    local unitGroup_escort = Group.getByName(groupName_escort)
    local escort_flag = false --indicates the go ahead for teleport/replaceMission calls
    local route_escort = {}
    local points_escort = {}
    local idxPoint1_escort = nil
    local idxPoint2_escort = nil
    local point1_escort = {}
    local point2_escort = {}
    local task2_escort = {}
    local tasks_escort = {}
    local task_escort = {}
    local EscortData = {}
    if unitGroup_escort ~= nil then
        EscortData = veaf.getGroupData(groupName_escort)
        if not(EscortData) then
            local text = "Cannot move Escort " .. groupName_escort .. " ; no group data"
            veaf.loggers.get(veafMove.Id):info(text)
        else
-- LOGGING DISABLED WHEN COMPILING("EscortData : %s", veaf.p(EscortData))
            route_escort = veaf.findInTable(EscortData, "route")
            points_escort = veaf.findInTable(route_escort, "points")

            if points_escort then
-- LOGGING DISABLED WHEN COMPILING("Escort has WP")
                idxPoint1_escort = #points_escort-1 --second to last waypoint
                idxPoint2_escort = #points_escort --last waypoint where the escort has to be set up in the editor
                point1_escort = points_escort[idxPoint1_escort]
                point2_escort = points_escort[idxPoint2_escort]
                task2_escort = veaf.findInTable(point2_escort, "task")
                if task2_escort.params.tasks then
-- LOGGING DISABLED WHEN COMPILING("Last escort WP has tasks")
                    tasks_escort = task2_escort.params.tasks
                    for k, task in pairs(tasks_escort) do
                        --if task.enabled and task.id and task.id == "Escort" and task.params and task.params.groupId == unitGroup_Id then --this line should be used to verify proper configuration of the escort but as it turns out the groupId stored in params has nothing to do with the groupId DCS needs for the escort task, use Group.getID(groupClass) instead to get the correct ID required for DCS, but no way to derive it from EscortData 
                        if task.enabled and task.id and task.id == "Escort" and task.params then
-- LOGGING DISABLED WHEN COMPILING("Found correct escort Tasking ! Extracted Escorted ID : %s", task.params.groupId)
-- LOGGING DISABLED WHEN COMPILING("Required escort ID : %s", escortedId)
                            escort_flag = true
                            task_escort=task --recover the escort task table to insert the "new" escorted ID, even though it's the same but it seems DCS destroys it after the escorted group respawns
                        end
                    end
                end
            end
        end
    else
        veaf.loggers.get(veafMove.Id):info(groupName_escort .. ' not found for move tanker escort command')
    end

    if not escort_flag then
        return false
    end

    --distances by which the escort is offseted from the escorted group in the map's referential, task_escort provides relative spacing
    local escort_offset = {}
    local hdg = veaf.headingBetweenPoints(teleportPoint, movePoint)
    escort_offset.x = (task_escort.params.pos.x*math.cos(hdg) - task_escort.params.pos.z*math.sin(hdg))
    escort_offset.z = (task_escort.params.pos.x*math.sin(hdg) + task_escort.params.pos.z*math.cos(hdg))

    local teleportPoint_escort = {}
    teleportPoint_escort.x = teleportPoint.x + escort_offset.x
    teleportPoint_escort.y = teleportPoint.y + escort_offset.z
    teleportPoint_escort.alt = teleportPoint.alt + task_escort.params.pos.y
    teleportPoint_escort.speed = teleportPoint.speed

    --Effectively waypoint 0, the AI will have to fly over it and in the editor it never poses a problem but in scripting the AI will do orbits to try and reach it
    --so it has to be offseted
    point1_escort.x = (teleportPoint.x + movePoint.x)/2 + escort_offset.x
    point1_escort.y = (teleportPoint.y + movePoint.y)/2 + escort_offset.z
    point1_escort.alt = movePoint.alt + task_escort.params.pos.y
    point1_escort.speed = movePoint.speed

    --Waypoint 1 where the escort tasking will come into play
    point2_escort.x = 2*point1_escort.x - teleportPoint.x - escort_offset.x
    point2_escort.y =  2*point1_escort.y - teleportPoint.y - escort_offset.z
    point2_escort.alt = movePoint.alt + task_escort.params.pos.y
    point2_escort.speed = movePoint.speed

    task_escort.params.groupId = escortedId --assign the new groupID within the old escort mission, only necessary after teleporting as the tanker's ID will have changed

-- LOGGING DISABLED WHEN COMPILING("Teleport the escort")
    local vars_escort = { groupName = groupName_escort, point = teleportPoint_escort, action = "teleport"}
    local grp_escort = mist.teleportToPoint(vars_escort)
    unitGroup_escort = Group.getByName(groupName_escort)

    veafMove.replaceMission(unitGroup_escort, EscortData)
    --this method appears to not work very well, the escort just doesn't defend the group

    --mist.goRoute(groupName_escort, route_escort)
    --works even worse, sends them to X=0, Z=0

    return true
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- support method to replace the mission of moved aircraft (default delay of 1s for teleported aircraft)
-- @param unitGroup, data returned by the Group.getByName(groupName) command
-- @param missionData, data returned by the veaf.getGroupData(groupName) command
-- @optional param delay, integer, delay to apply before replacing the mission, useful when teleporting, recommended 1s for such a scenario (which is the default value)
-- @optional param immortal, boolean, sets the group which is seeing it's mission replaced to immortal and invisible
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafMove.replaceMission(unitGroup, missionData, delay, immortal)

    local delay = delay or 1

    local actualReplaceMission = function(unitGroup, missionData, immortal)

        local freq = missionData.frequency or 243 --set frequency or guard channel
        local mod = missionData.modulation or 0 --set modulation or AM (=0)

-- LOGGING DISABLED WHEN COMPILING(string.format("Resetting %s mission", unitGroup:getName()))
-- LOGGING DISABLED WHEN COMPILING(string.format("replaceMissionData=%s", veaf.p(missionData)))
        --... for the escort, necessary to re assign the mission wether the tanker was teleported (== changed ID) or not because DCS

        local mission = {
            id = 'Mission',
            params = missionData
        }
        local controller = unitGroup:getController()
        controller:setTask(mission)

        if immortal then
            -- JTAC needs to be invisible and immortal
-- LOGGING DISABLED WHEN COMPILING("Group immortalized")
            local _setImmortal = {
                id = 'SetImmortal',
                params = {
                    value = true
                }
            }
            -- invisible to AI, Shagrat
            local _setInvisible = {
                id = 'SetInvisible',
                params = {
                    value = true
                }
            }

            Controller.setCommand(controller, _setImmortal)
            Controller.setCommand(controller, _setInvisible)
        end

        --have to set the frequency again as setTask seems to ignore missionData.frequency and switch the unit to 124AM
        local _setFrequency = {
            id = 'SetFrequency',
            params = {
                frequency = freq * 1000000,
                modulation = mod
            }
        }

        Controller.setCommand(controller, _setFrequency)
    end

    mist.scheduleFunction(actualReplaceMission, {unitGroup, missionData, immortal}, timer.getTime()+delay)
end

------------------------------------------------------------------------------
-- veafMove.moveAfac
-- @param point eventPos
-- @param string groupName 
-- @param float speed in knots
-- @param float alt in feet
-- @param float hdg in degrees
-- @param boolean immortal
------------------------------------------------------------------------------
function veafMove.moveAfac(eventPos, groupName, speed, alt, heading, immortal)
    if not speed then
        speed = 150
    end
    if not alt then
        alt = 20000
    end
-- LOGGING DISABLED WHEN COMPILING("veafMove.moveAfac(groupName = " .. groupName .. ", speed = " .. speed .. ", alt = " .. alt)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMove.moveAfac: eventPos  x=%.1f z=%.1f", eventPos.x, eventPos.z))

    local distanceFromTeleport = 3000 --distance between the orbit point and the teleport point in meters

	local unitGroup = Group.getByName(groupName)
	if unitGroup == nil then
        veaf.loggers.get(veafMove.Id):info(groupName .. ' not found for move afac command')
		trigger.action.outText(groupName .. ' not found for move afac command' , 10)
		return false
	end

    local coalition = unitGroup:getCoalition()

    local afacData = veaf.getGroupData(groupName)
    local isDynamicallySpawned = false
    if not afacData then
        for number, dynAFACcallsign in pairs(veafSpawn.AFAC.callsigns[coalition]) do
            if groupName:find(dynAFACcallsign.name) then
-- LOGGING DISABLED WHEN COMPILING("AFAC is dynamically spawned")
                afacData = veafSpawn.AFAC.missionData[coalition][number]
                isDynamicallySpawned = true
            end
        end
    end
-- LOGGING DISABLED WHEN COMPILING("Found AFAC named " .. groupName .. " for move command")
-- LOGGING DISABLED WHEN COMPILING(string.format("AFAC mission data is : %s", veaf.p(afacData)))

    local route_afac = veaf.findInTable(afacData, "route")
    local points_afac = veaf.findInTable(route_afac, "points")
    if points_afac then

-- LOGGING DISABLED WHEN COMPILING("Found AFAC waypoints")
        local idxPoint1_afac = #points_afac-1 --second to last waypoint
        local idxPoint2_afac = #points_afac --last waypoint
        local point1_afac = points_afac[idxPoint1_afac]
        local point2_afac = points_afac[idxPoint2_afac]
        local FACflag = false
        local OrbitFlag = false

        -- if hdg is not set, compute heading between point1 and point2
        local hdg = heading
        if hdg == nil then
            hdg = veaf.headingBetweenPoints(point1_afac, point2_afac)
        else
            hdg = heading * math.pi/180
        end

        -- teleport position
        local teleportPosition = {
            ["x"] = eventPos.x - distanceFromTeleport*math.cos(hdg), --teleport 3km south of orbit point
            ["y"] = eventPos.z - distanceFromTeleport*math.sin(hdg),
            ["alt"] = alt * 0.3048 -- in meters
        }

        -- orbit position
        local fromPosition = {
            ["x"] = eventPos.x,
            ["y"] = eventPos.z
        }

        --check valid configuration of the AFAC
        if point1_afac and point2_afac then
-- LOGGING DISABLED WHEN COMPILING("AFAC has at least the two waypoints required")
            local tasks1_afac=point1_afac.task.params.tasks
            local tasks2_afac=point2_afac.task.params.tasks
            if tasks1_afac and tasks2_afac then
                for _, task in pairs(tasks1_afac) do
                    if task.id == "FAC" then
-- LOGGING DISABLED WHEN COMPILING("FAC configuration valid on second to last WP")
                        FACflag = true
                    end
                end

                for _, task in pairs(tasks2_afac) do
                    if task.id == "Orbit" then
-- LOGGING DISABLED WHEN COMPILING("AFAC Orbit configuration valid on last WP")
                        OrbitFlag = true
                    end
                end
            end
        end

        if FACflag == false or OrbitFlag == false then
            veaf.loggers.get(veafMove.Id):info(groupName .. ' has an invalid FAC/Orbit configuration')
            trigger.action.outText(groupName .. ' has an invalid FAC/Orbit configuration' , 10)
            return false
        end

        --edit the last two waypoints of the AFAC's flight plan with the new requested position,speed and alt info
        point1_afac.speed=speed
        point1_afac.alt=teleportPosition.alt
        point1_afac.x=teleportPosition.x + distanceFromTeleport*math.cos(hdg)/2
        point1_afac.y=teleportPosition.y + distanceFromTeleport*math.sin(hdg)/2

        point2_afac.speed=speed
        point2_afac.alt=teleportPosition.alt
        point2_afac.x=eventPos.x
        point2_afac.y=eventPos.z

        --teleport the group south of the requested location
-- LOGGING DISABLED WHEN COMPILING("AFAC ".. groupName .. " teleported")
        local vars = { groupName = groupName, point = teleportPosition, action = "teleport" }
        if isDynamicallySpawned then
            vars = { groupName = groupName, groupData = afacData, anyTerrain = true, point = teleportPosition, action = "teleport" }
        end
        local grp = mist.teleportToPoint(vars)
        unitGroup = Group.getByName(groupName) --refresh group class after respawn, not necessary but safer considering at least the groupId changes

        --necessary delay for the following code to not be ignored
        local delay=1

        -- replace whole mission
        veafMove.replaceMission(unitGroup, afacData, delay, immortal)

        return true
    else
        return false
    end

end

-- prepare tanker units
function veafMove.findAllTankers()
    local TankerTypeNames = {"KC130", "KC-135", "KC135MPRS", "KJ-2000", "IL-78M"}
-- LOGGING DISABLED WHEN COMPILING(string.format("findAllTankers()"))
    local result = {}
    local units = mist.DBs.unitsByName -- local copy for faster execution
    for name, unit in pairs(units) do
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s, unit.type=%s", veaf.p(name), veaf.p(unit.type)))
-- LOGGING DISABLED WHEN COMPILING(string.format("unit=%s", veaf.p(unit)))
        --local unit = Unit.getByName(name)
        if unit then
            for _, tankerTypeName in pairs(TankerTypeNames) do
                if tankerTypeName:lower() == unit.type:lower() then
                    table.insert(result, unit.groupName)
                end
            end
        end
    end
    return result
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Build a radio menu to move or teleport a tanker
function veafMove.moveTankerToMe(parameters)
    local subParameters, unitName = veaf.safeUnpack(parameters)
    local tankerName, direction = veaf.safeUnpack(subParameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMove.moveTankerToMe(tankerName=%s, unitName=%s, direction=%d)", tankerName, unitName, direction))
    local unit = Unit.getByName(unitName)
    if unit then
        local unitType = unit:getDesc()["typeName"]
-- LOGGING DISABLED WHEN COMPILING(string.format("checking unit %s of type %s", tostring(unitName), tostring(unitType)))
        local tankerMissionParameters = veafMove.tankerMissionParameters[unitType]
        if not tankerMissionParameters then
            tankerMissionParameters = { speed = -1, alt = -1}  -- -1 means to use the currently defined speed and altitude
        end
        veafMove.moveTanker(unit:getPosition().p, tankerName, tankerMissionParameters.speed, tankerMissionParameters.alt, direction, nil, true, false)
        veaf.outTextForUnit(unitName, string.format("%s - Moving to your position right away !", tankerName), 15)
    end
end

--- Build the initial radio menu
function veafMove.buildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING(string.format("veafMove.buildRadioMenu()"))
    veafMove.rootPath = veafRadio.addSubMenu(veafMove.RadioMenuName)
    if not(veafRadio.skipHelpMenus) then
        veafRadio.addCommandToSubmenu("HELP", veafMove.rootPath, veafMove.help, nil, veafRadio.USAGE_ForGroup)
    end
    for _, tankerUnitName in pairs(veafMove.Tankers) do
        local tankerName = tankerUnitName
        if veafAssets then
-- LOGGING DISABLED WHEN COMPILING(string.format("searching for asset name %s", tankerUnitName))
            local asset = veafAssets.get(tankerUnitName)
            if asset then
                tankerName = asset.description
-- LOGGING DISABLED WHEN COMPILING(string.format("found asset name : %s", tankerName))
            end
        end
        -- Move tanker to me
        local menuName = string.format("%s - WEST", tankerName)
        local moveTankerPath = veafRadio.addSubMenu(menuName, veafMove.rootPath)
        veafRadio.addCommandToSubmenu(menuName, moveTankerPath, veafMove.moveTankerToMe, {tankerUnitName, 270}, veafRadio.USAGE_ForGroup)

        menuName = string.format("%s - EAST", tankerName)
        moveTankerPath = veafRadio.addSubMenu(menuName, veafMove.rootPath)
        veafRadio.addCommandToSubmenu(menuName, moveTankerPath, veafMove.moveTankerToMe, {tankerUnitName, 90}, veafRadio.USAGE_ForGroup)
    end
end

function veafMove.help(unitName)
    local text =
        'Create a marker and type "_move <group|tanker|afac>, name <groupname> " in the text\n' ..
        'This will issue a move command to the specified group in the DCS world\n' ..
        'Type "_move group, name [groupname]" to move the specified group to the marker point\n' ..
        '     add ", speed [speed]" to make the group move and at the specified speed (in knots)\n' ..
        'Type "_move tanker, name [groupname]" to create a new tanker flight plan and move the specified tanker.\n' ..
        '     add ", speed [speed]" to make the tanker move and execute its refuel mission at the specified speed (in knots)\n' ..
        '     add ", alt [altitude]" to specify the refuel leg altitude (in feet)\n' ..
        'Type "_move afac, name [groupname]" to create a new JTAC flight plan and move the specified afac drone.\n' ..
        '     add ", speed [speed]" to make the tanker move and execute its mission at the specified speed (in knots)\n' ..
        '     add ", alt [altitude]" to specify the altitude at which the drone will circle (in feet)'
    veaf.outTextForGroup(unitName, text, 30)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafMove.initialize()
    if #veafMove.Tankers == 0 then
        -- find all existing Tankers
        veafMove.Tankers = veafMove.findAllTankers()
    end
    veafMove.buildRadioMenu()
    veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafMove.onEventMarkChange)
end

veaf.loggers.get(veafMove.Id):info(string.format("Loading version %s", veafMove.Version))

------------------ END script veafMove.lua  ------------------


------------------ START script veafNamedPoints.lua  ------------------

------------------------------------------------------------------
-- VEAF name point command and functions for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Listen to marker change events and name the corresponding point, for future reference
-- * Works with all current and future maps (Caucasus, NTTR, Normandy, PG, ...)
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------
veafNamedPoints = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafNamedPoints.Id = "NAMED POINTS"

--- Version.
veafNamedPoints.Version = "1.16.1"

-- trace level, specific to this module
--veafNamedPoints.LogLevel = "trace"
veaf.loggers.new(veafNamedPoints.Id, veafNamedPoints.LogLevel)

--- Key phrase to look for in the mark text which triggers the command.
veafNamedPoints.Keyphrase = "_name point"
veafNamedPoints.RadioMenuName = "NAMED POINTS"
veafNamedPoints.RemoteCommandParser = "([[a-zA-Z0-9]+)%s?([^%s]*)%s?(.*)"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafNamedPoints.namedPoints = {}
veafNamedPoints.rootPath = nil
veafNamedPoints.markid = 1270000 --- Initial Marker id.

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafNamedPoints.onEventMarkChange(eventPos, event)
    if veafNamedPoints.executeCommand(eventPos, event) then

        -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
        trigger.action.removeMark(event.idx)
    end
end

function veafNamedPoints.executeCommand(eventPos, event, bypassSecurity)

    -- Check if marker has a text and the veafNamedPoints.keyphrase keyphrase.
    if event.text ~= nil and event.text:lower():find(veafNamedPoints.Keyphrase) then

        -- Analyse the mark point text and extract the keywords.
        local options = veafNamedPoints.markTextAnalysis(event.text)

        if options then
            -- Check options commands
            if options.namepoint then
                -- create the mission
                veafNamedPoints.namePoint(eventPos, options.name, event.coalition)
            end
            return true
        else
            -- None of the keywords matched.
            return false
        end
    end
    return false
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the mark text and extract keywords.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Extract keywords from mark text.
function veafNamedPoints.markTextAnalysis(text)

    -- Option parameters extracted from the mark text.
    local switch = {}
    switch.namepoint = false

    switch.name = "point"

    -- Check for correct keywords.
    local pos = text:lower():find(veafNamedPoints.Keyphrase)
    if pos then
        switch.namepoint = true
    else
        return nil
    end

    -- the point name should follow a space
    switch.name = text:sub(pos+string.len(veafNamedPoints.Keyphrase)+1)
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword name = %s", switch.name))

    return switch
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Named points management
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Create the point in the named points database
function veafNamedPoints.namePoint(targetSpot, name, coalition, silent)
-- LOGGING DISABLED WHEN COMPILING(string.format("namePoint(name = %s, coalition=%s)",name, coalition))
-- LOGGING DISABLED WHEN COMPILING("targetSpot=" .. veaf.vecToString(targetSpot))

    -- find an existing point with the same name
    local existingPoint = veafNamedPoints.getPoint(name)
-- LOGGING DISABLED WHEN COMPILING(string.format("existingPoint=%s", veaf.p(existingPoint)))
    if existingPoint and existingPoint.markerId then
        -- delete the existing point
        trigger.action.removeMark(existingPoint.markerId)
    end

    local point = { x = targetSpot.x, y = targetSpot.y, z = targetSpot.z}
    point.hidden = false
    veafNamedPoints.addPoint(name, point)

    local message = nil
    if not silent then
        message = "VEAF - Point named "..name.." added for own coalition."
    end

    veafNamedPoints.markid = veafNamedPoints.markid + 1
    point.markerId = veafNamedPoints.markid
    trigger.action.markToCoalition(veafNamedPoints.markid, "VEAF - Point named "..name, point, coalition, true, message)
-- LOGGING DISABLED WHEN COMPILING(string.format("created point %s", veaf.p(point)))
end

function veafNamedPoints.addPoint(name, point)
    if not point.y then point.y = 0 end
-- LOGGING DISABLED WHEN COMPILING(string.format("addPoint: {name=\"%s\",point={x=%d,y=0,z=%d}}", name, point.x, point.z))
    point.name = name:upper()
    veafNamedPoints.namedPoints[name:upper()] = point
    return point
end

function veafNamedPoints.delPoint(name)
-- LOGGING DISABLED WHEN COMPILING(string.format("delPoint(name = %s)",name))

    table.remove(veafNamedPoints.namedPoints, name:upper())
end

function veafNamedPoints.getPoint(name)
-- LOGGING DISABLED WHEN COMPILING(string.format("getPoint(name = %s)",name or ""))
    if name then
        return veafNamedPoints.namedPoints[name:upper()]
    else
        return nil
    end
end

function veafNamedPoints.getPointBearing(parameters)
    local name, unitName = veaf.safeUnpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("getPointBearing(%s)",name))
    local point = veafNamedPoints.getPoint(name)
    local unit = veafRadio.getHumanUnitOrWingman(unitName)
    if point and unit then
        local angle, distance, distanceInKm, distanceInNm = veaf.getBearingAndRangeFromTo(unit:getPosition().p, point)
        if distanceInNm > 2 then
            return "at " .. angle .. "° for " .. distanceInNm .. " nm"
        end
    end
    return nil
end

function veafNamedPoints.getNearestPoint(unitName)
-- LOGGING DISABLED WHEN COMPILING("veafNamedPoints.getNearestPoint(unitName = %s)",veaf.p(unitName))
    local closestPoint = nil
    local minDistance = 99999999
    local unit = veafRadio.getHumanUnitOrWingman(unitName)
    if unit then
        for name, point in pairs(veafNamedPoints.namedPoints) do
            local distanceFromPlayer = ((point.x - unit:getPosition().p.x)^2 + (point.z - unit:getPosition().p.z)^2)^0.5
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s, distanceFromPlayer=%d",name, distanceFromPlayer))
            if distanceFromPlayer < minDistance then
                minDistance = distanceFromPlayer
                closestPoint = point
            end
        end
    end
-- LOGGING DISABLED WHEN COMPILING("closestPoint=%s",veaf.p(closestPoint))
    return closestPoint
end

function veafNamedPoints.pointFromString(coordinatesString)
-- LOGGING DISABLED WHEN COMPILING(string.format("pointFromString(coordinatesString = %s)",veaf.p(coordinatesString)))
    local _result = nil
    local _lat, _lon = veaf.computeLLFromString(coordinatesString)
-- LOGGING DISABLED WHEN COMPILING(string.format("_lat=%s",veaf.p(_lat)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_lon=%s",veaf.p(_lon)))
    if _lat and _lon then
        _result = veafNamedPoints.pointFromLL(_lat, _lon)
    end
    return _result
end

function veafNamedPoints.pointFromLL(lat, long)
-- LOGGING DISABLED WHEN COMPILING(string.format("pointFromLL(lat = %s, long = %s)",veaf.p(lat), veaf.p(long)))
    return coord.LLtoLO(lat, long)
end

function veafNamedPoints.addDataToPoint(point, data)
    if point then
        if data then
            for key, value in pairs(data) do
                point[key] = value
            end
        end
        return point
    end
end

function veafNamedPoints.listAllPoints(unitName)
-- LOGGING DISABLED WHEN COMPILING(string.format("listAllPoints(unitName = %s)",tostring(unitName)))
    local message = ""
    local names = {}
    for name, point in pairs(veafNamedPoints.namedPoints) do
        if not point.hidden then table.insert(names, name) end
    end
    table.sort(names)
    for _, name in pairs(names) do
        local point = veafNamedPoints.namedPoints[name]
        local lat, lon = coord.LOtoLL(point)
        local llString = mist.tostringLL(lat, lon, 3)
        llString = llString:sub(0,2) .. '°' .. llString:sub(4)
        local mgrs = coord.LLtoMGRS(lat, lon)
        local mgrsString = mist.tostringMGRS(mgrs, 5)
        message = message .. name .. " => " .. llString .. " / " .. mgrsString .. "\n"
    end

    -- send message only for the unit
    veaf.outTextForUnit(unitName, message, 30)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Build the initial radio menu
function veafNamedPoints.buildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("buildRadioMenu()")
    veafNamedPoints.rootPath = veafRadio.addSubMenu(veafNamedPoints.RadioMenuName)
    veafRadio.addCommandToSubmenu("List all points", veafNamedPoints.rootPath, veafNamedPoints.listAllPoints, nil, veafRadio.USAGE_ForGroup)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- remote interface
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- execute command from the remote interface
function veafNamedPoints.executeCommandFromRemote(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafNamedPoints.executeCommandFromRemote()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("parameters= %s", veaf.p(parameters)))
    local _pilot, _pilotName, _unitName, _command = unpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilot= %s", veaf.p(_pilot)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilotName= %s", veaf.p(_pilotName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_unitName= %s", veaf.p(_unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_command= %s", veaf.p(_command)))
    if not _pilot or not _command then
        return false
    end

    veaf.outTextForUnit(_unitName, "no remote command for veafNamedPoints; for atc and weather try -weather", 30)

    --[[ keep this for later if we need a remote access for the veafNamedPoints script
    if _command then
        -- parse the command
        local _action, _pointName, _parameters = _command:match(veafNamedPoints.RemoteCommandParser)
-- LOGGING DISABLED WHEN COMPILING(string.format("_action=%s",veaf.p(_action)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_pointName=%s",veaf.p(_pointName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_parameters=%s",veaf.p(_parameters)))
        if _action and _action:lower() == "weather" then
            veaf.loggers.get(veafNamedPoints.Id):info(string.format("[%s] is requesting weather at his position",veaf.p(_pilotName)))
            veafNamedPoints.getWeatherAtClosestPoint(_unitName, true)
            return true
        elseif _action and _action:lower() == "atc" then
            veaf.loggers.get(veafNamedPoints.Id):info(string.format("[%s] is requesting atc at his position",veaf.p(_pilotName)))
            veafNamedPoints.getAtcAndWeatherAtClosestPoint(_unitName, true)
            return true
        end
    end
    return false
    ]]
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafNamedPoints.initialize(customPoints)
    veaf.loggers.get(veafNamedPoints.Id):info("Initialize veafNamedPoints")
    veafNamedPoints.namedPoints = {}

    veafNamedPoints.addCities()
    veafNamedPoints.addAirbases()
    veafNamedPoints.addCustomPoints(customPoints)

    veafNamedPoints.buildRadioMenu()
    veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafNamedPoints.onEventMarkChange)
end

function veafNamedPoints.addCities()
    local theatre = env.mission.theatre
    veaf.loggers.get(veafNamedPoints.Id):info("Initialize veafNamedPoints cities for theatre %s", theatre)

    local theatreCities
    if theatre == veaf.theatreName.Syria then
        theatreCities = veafNamedPoints._citiesSyria
    elseif theatre == veaf.theatreName.Caucasus then
        theatreCities = veafNamedPoints._citiesCaucasus
    elseif theatre == veaf.theatreName.PersianGulf then
        theatreCities = veafNamedPoints._citiesPersianGulf
    elseif theatre == veaf.theatreName.TheChannel then
        theatreCities = veafNamedPoints._citiesTheChannel
    elseif theatre == veaf.theatreName.MarianaIslands then
        theatreCities = veafNamedPoints._citiesMarianasIslands
    elseif theatre == veaf.theatreName.Falklands then
        theatreCities = veafNamedPoints._citiesFalklands
    else
        veaf.loggers.get(veaf.Id):warn(string.format("no cities in veafNamedPoints for theatre %s ", theatre))
        theatreCities = {}
    end

    veafNamedPoints.addCitiesFromList(theatreCities)
end

function veafNamedPoints.addCitiesFromList(cities)
    for name, data in pairs(cities) do
-- LOGGING DISABLED WHEN COMPILING(string.format("processing city name=[%s]",name or ""))
        local point = coord.LLtoLO(data.latitude, data.longitude)
-- LOGGING DISABLED WHEN COMPILING(string.format("point=[%s]",veaf.p(point)))
        point.hidden = true
        local name = data.display_name
-- LOGGING DISABLED WHEN COMPILING(string.format("name=[%s]",name or ""))

        veafNamedPoints.addPoint(name, point)

        -- add a clean version of the city name
        local cleanedUpCityName = name:gsub("[^a-zA-Z]","")
        if not veafNamedPoints.namedPoints[cleanedUpCityName:upper()] then
            veafNamedPoints.addPoint(cleanedUpCityName, point)
        end

        --veafNamedPoints.markid = veafNamedPoints.markid + 1
        --trigger.action.markToAll(veafNamedPoints.markid, "VEAF - Point named "..name, point, true) 
    end
end

function veafNamedPoints.addAirbases()
    veafAirbases.initialize()
    
    for _, veafAirbase in pairs(veafAirbases.Airbases) do
        if (veafAirbase.Category == Airbase.Category.AIRDROME) then
-- LOGGING DISABLED WHEN COMPILING("processing airbase name=[%s]", veafAirbase.DisplayName)
            local vec3 = veafAirbase.DcsAirbase:getPoint()
            local runways = {}
            for i, veafRunway in ipairs(veafAirbase.Runways) do
                for __, veafRunwayEnd in ipairs(veafRunway) do
-- LOGGING DISABLED WHEN COMPILING(veaf.p(veafRunwayEnd))
                    table.insert(runways, {name = string.format("%02d", veafRunwayEnd.Number), hdg = veafRunwayEnd.Heading })
                end
            end            

            local namedPoint = { x = vec3.x, y = 0, z = vec3.z, runways = runways} --{name="AIRBASE Batumi",  point={x=-356437,y=0,z=618211, atc=true, tower="V131, U260", tacan="16X BTM", runways={{name="13", hdg=125, ils="110.30"}, {name="31", hdg=305}}}},
            namedPoint.hidden = true
            veafNamedPoints.addPoint(string.format("AIRBASE %s", veafAirbase.DisplayName), namedPoint)
        end
    end

    return nil
end

function veafNamedPoints.addCustomPoints(customPoints)
-- LOGGING DISABLED WHEN COMPILING("addCustomPoints()")

    if customPoints then 
        for _, defaultPoint in pairs(customPoints) do
            veafNamedPoints.addPoint(defaultPoint.name, defaultPoint.point)
        end
    end
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  Cities tables
--- these tables below comes from the game, and contain duplicate indexes
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
-- backward compatibility for old mission created with pre-2023 VMC
function veafNamedPoints.addAllSyriaCities() end
function veafNamedPoints.addAllCaucasusCities() end
function veafNamedPoints.addAllTheChannelCities() end
function veafNamedPoints.addAllMarianasIslandsCities() end
-- 
---@diagnostic disable: duplicate-index  
veafNamedPoints._citiesCaucasus =
{
    ["AMBROLAURI"] = { latitude = 42.530195, longitude = 43.150121, display_name = "AMBROLAURI"},
    ["KUTAISI"] = { latitude = 42.267086, longitude = 42.696849, display_name = "KUTAISI"},
    ["BATUMI"] = { latitude = 41.654059, longitude = 41.655372, display_name = "BATUMI"},
    ["POTI"] = { latitude = 42.157804, longitude = 41.677693, display_name = "POTI"},
    ["ZUGDIDI"] = { latitude = 42.516379, longitude = 41.879016, display_name = "ZUGDIDI"},
    ["MAYKOP"] = { latitude = 44.607053, longitude = 40.096197, display_name = "MAYKOP"},
    ["KRASNODAR"] = { latitude = 45.053964, longitude = 39.000128, display_name = "KRASNODAR"},
    ["NOVOROSSIYSK"] = { latitude = 44.719335, longitude = 37.751757, display_name = "NOVOROSSIYSK"},
    ["KISLOVODSK"] = { latitude = 43.913866, longitude = 42.723910, display_name = "KISLOVODSK"},
    ["SUKHUMI"] = { latitude = 43.010963, longitude = 40.999400, display_name = "SUKHUMI"},
    ["SOCHI"] = { latitude = 43.604619, longitude = 39.721483, display_name = "SOCHI"},
    ["NAL'CHIK"] = { latitude = 43.485073, longitude = 43.623441, display_name = "NAL'CHIK"},
    ["PYATIGORSK"] = { latitude = 44.052891, longitude = 43.048264, display_name = "PYATIGORSK"},
    ["MINERAL'NYE VODY"] = { latitude = 44.201454, longitude = 43.137392, display_name = "MINERAL'NYE VODY"},
    ["GEORGIEVSK"] = { latitude = 44.148896, longitude = 43.458776, display_name = "GEORGIEVSK"},
    ["CHERKESSK"] = { latitude = 44.226950, longitude = 42.060347, display_name = "CHERKESSK"},
    ["LANCHHUTI"] = { latitude = 42.089007, longitude = 42.027661, display_name = "LANCHHUTI"},
    ["SAMTREDIA"] = { latitude = 42.174739, longitude = 42.336138, display_name = "SAMTREDIA"},
    ["ABASHA"] = { latitude = 42.221963, longitude = 42.211652, display_name = "ABASHA"},
    ["SENAKI"] = { latitude = 42.285411, longitude = 42.045822, display_name = "SENAKI"},
    ["HONI"] = { latitude = 42.326943, longitude = 42.412159, display_name = "HONI"},
    ["MARTVILI"] = { latitude = 42.400702, longitude = 42.368318, display_name = "MARTVILI"},
    ["TSHALTUBO"] = { latitude = 42.323088, longitude = 42.614660, display_name = "TSHALTUBO"},
    ["TKIBULI"] = { latitude = 42.352385, longitude = 42.997191, display_name = "TKIBULI"},
    ["ZESTAFONI"] = { latitude = 42.111455, longitude = 43.037536, display_name = "ZESTAFONI"},
    ["CHIATURA"] = { latitude = 42.293616, longitude = 43.270537, display_name = "CHIATURA"},
    ["SACHHERE"] = { latitude = 42.342501, longitude = 43.404049, display_name = "SACHHERE"},
    ["OZURGETI"] = { latitude = 41.936007, longitude = 42.018441, display_name = "OZURGETI"},
    ["KOBULETI"] = { latitude = 41.808106, longitude = 41.780942, display_name = "KOBULETI"},
    ["ONI"] = { latitude = 42.591213, longitude = 43.449897, display_name = "ONI"},
    ["DZHVARI"] = { latitude = 42.703382, longitude = 42.042828, display_name = "DZHVARI"},
    ["GALI"] = { latitude = 42.624895, longitude = 41.727731, display_name = "GALI"},
    ["OCHAMCHIRA"] = { latitude = 42.722153, longitude = 41.474646, display_name = "OCHAMCHIRA"},
    ["UST'-DZHEGUTA"] = { latitude = 44.080182, longitude = 41.969081, display_name = "UST'-DZHEGUTA"},
    ["APSHERONSK"] = { latitude = 44.459654, longitude = 39.729136, display_name = "APSHERONSK"},
    ["TUAPSE"] = { latitude = 44.114132, longitude = 39.068294, display_name = "TUAPSE"},
    ["GELENDZHIK"] = { latitude = 44.578432, longitude = 38.018660, display_name = "GELENDZHIK"},
    ["PASHKOVSKIY"] = { latitude = 45.029117, longitude = 39.094463, display_name = "PASHKOVSKIY"},
    ["UST'-LABINSK"] = { latitude = 45.221626, longitude = 39.683478, display_name = "UST'-LABINSK"},
    ["BELORECHENSK"] = { latitude = 44.771687, longitude = 39.875183, display_name = "BELORECHENSK"},
    ["ABINSK"] = { latitude = 44.870561, longitude = 38.156475, display_name = "ABINSK"},
    ["KRYMSK"] = { latitude = 44.927837, longitude = 38.011490, display_name = "KRYMSK"},
    ["SLAVYANSK-NA-KUBANI"] = { latitude = 45.253951, longitude = 38.123431, display_name = "SLAVYANSK-NA-KUBANI"},
    ["ANAPA"] = { latitude = 44.932503, longitude = 37.298006, display_name = "ANAPA"},
    ["TEMRYUK"] = { latitude = 45.261076, longitude = 37.432072, display_name = "TEMRYUK"},
    ["TKVARCHELI"] = { latitude = 42.853781, longitude = 41.674307, display_name = "TKVARCHELI"},
    ["ESSENTUKI"] = { latitude = 44.043909, longitude = 42.860779, display_name = "ESSENTUKI"},
    ["ADLER"] = { latitude = 43.453776, longitude = 39.915595, display_name = "ADLER"},
    ["HOSTA"] = { latitude = 43.515628, longitude = 39.864624, display_name = "HOSTA"},
    ["LAZAREVSKOE"] = { latitude = 43.917274, longitude = 39.326891, display_name = "LAZAREVSKOE"},
    ["LERMONTOV"] = { latitude = 44.108783, longitude = 42.974445, display_name = "LERMONTOV"},
    ["TYRNYAUZ"] = { latitude = 43.391341, longitude = 42.921969, display_name = "TYRNYAUZ"},
    ["NOVOPAVLOVSK"] = { latitude = 43.962533, longitude = 43.641826, display_name = "NOVOPAVLOVSK"},
    ["BAKSAN"] = { latitude = 43.686840, longitude = 43.544814, display_name = "BAKSAN"},
    ["NARTKALA"] = { latitude = 43.554316, longitude = 43.853943, display_name = "NARTKALA"},
    ["ZHELEZNOVODSK"] = { latitude = 44.141803, longitude = 43.023059, display_name = "ZHELEZNOVODSK"},
    ["Helvachauri"] = { latitude = 41.596451, longitude = 41.668302, display_name = "Helvachauri"},
    ["Mahindzhauri"] = { latitude = 41.673079, longitude = 41.699531, display_name = "Mahindzhauri"},
    ["Kur.Bahmaro"] = { latitude = 41.848456, longitude = 42.327860, display_name = "Kur.Bahmaro"},
    ["Kur.Sairme"] = { latitude = 41.907942, longitude = 42.743349, display_name = "Kur.Sairme"},
    ["Chakva"] = { latitude = 41.727030, longitude = 41.734648, display_name = "Chakva"},
    ["Laituri"] = { latitude = 41.918270, longitude = 41.900378, display_name = "Laituri"},
    ["Kvedo-Nasakirali"] = { latitude = 41.977199, longitude = 42.061216, display_name = "Kvedo-Nasakirali"},
    ["Kulashi"] = { latitude = 42.210505, longitude = 42.349080, display_name = "Kulashi"},
    ["Gagma-Pirveli-Horga"] = { latitude = 42.267520, longitude = 41.859143, display_name = "Gagma-Pirveli-Horga"},
    ["Gamogma-Pirveli-Horga"] = { latitude = 42.288279, longitude = 41.845307, display_name = "Gamogma-Pirveli-Horga"},
    ["Ordzhonikidze"] = { latitude = 42.008849, longitude = 43.173219, display_name = "Ordzhonikidze"},
    ["Terzhola"] = { latitude = 42.195788, longitude = 42.983178, display_name = "Terzhola"},
    ["Haristvala"] = { latitude = 42.415940, longitude = 43.041115, display_name = "Haristvala"},
    ["Zeda-Sairme"] = { latitude = 42.564224, longitude = 42.882795, display_name = "Zeda-Sairme"},
    ["kur.Skuri"] = { latitude = 42.697552, longitude = 42.161497, display_name = "kur.Skuri"},
    ["Tsalendzhiha"] = { latitude = 42.619409, longitude = 42.070900, display_name = "Tsalendzhiha"},
    ["Muzhava"] = { latitude = 42.713559, longitude = 41.992057, display_name = "Muzhava"},
    ["Ingurges"] = { latitude = 42.672937, longitude = 41.853299, display_name = "Ingurges"},
    ["Severo-Vostochnye Sady"] = { latitude = 44.635280, longitude = 40.131508, display_name = "Severo-Vostochnye Sady"},
    ["Psebay"] = { latitude = 44.138091, longitude = 40.803699, display_name = "Psebay"},
    ["Kamennomostskiy"] = { latitude = 44.302330, longitude = 40.184071, display_name = "Kamennomostskiy"},
    ["Tul'skiy"] = { latitude = 44.513927, longitude = 40.170579, display_name = "Tul'skiy"},
    ["Magri"] = { latitude = 44.022796, longitude = 39.163094, display_name = "Magri"},
    ["Vishnevka"] = { latitude = 44.010679, longitude = 39.186179, display_name = "Vishnevka"},
    ["Goryachiy Klyuch"] = { latitude = 44.631585, longitude = 39.125779, display_name = "Goryachiy Klyuch"},
    ["Hadyzhensk"] = { latitude = 44.427144, longitude = 39.530325, display_name = "Hadyzhensk"},
    ["Grozneft'"] = { latitude = 44.096137, longitude = 39.100269, display_name = "Grozneft'"},
    ["Novomihaylovskiy"] = { latitude = 44.258430, longitude = 38.854150, display_name = "Novomihaylovskiy"},
    ["Dzhubga"] = { latitude = 44.325968, longitude = 38.703843, display_name = "Dzhubga"},
    ["Arhipo-Osipovka"] = { latitude = 44.376981, longitude = 38.530670, display_name = "Arhipo-Osipovka"},
    ["Pshada"] = { latitude = 44.474606, longitude = 38.401898, display_name = "Pshada"},
    ["Divnomorskoe"] = { latitude = 44.503462, longitude = 38.132761, display_name = "Divnomorskoe"},
    ["Kabardinka"] = { latitude = 44.657320, longitude = 37.934694, display_name = "Kabardinka"},
    ["Giaginskaya"] = { latitude = 44.872978, longitude = 40.056178, display_name = "Giaginskaya"},
    ["Vasyurinskaya"] = { latitude = 45.117847, longitude = 39.420083, display_name = "Vasyurinskaya"},
    ["Adygeysk"] = { latitude = 44.887801, longitude = 39.187143, display_name = "Adygeysk"},
    ["Tlyustenhabl'"] = { latitude = 44.980442, longitude = 39.092968, display_name = "Tlyustenhabl'"},
    ["Kalinino"] = { latitude = 45.097761, longitude = 39.016695, display_name = "Kalinino"},
    ["Afipskiy"] = { latitude = 44.904156, longitude = 38.844197, display_name = "Afipskiy"},
    ["Il'skiy"] = { latitude = 44.843810, longitude = 38.563777, display_name = "Il'skiy"},
    ["Chernomorskiy"] = { latitude = 44.851048, longitude = 38.491750, display_name = "Chernomorskiy"},
    ["Holmskiy"] = { latitude = 44.844059, longitude = 38.390536, display_name = "Holmskiy"},
    ["Ahtyrskiy"] = { latitude = 44.848464, longitude = 38.305640, display_name = "Ahtyrskiy"},
    ["Enem"] = { latitude = 44.926601, longitude = 38.903816, display_name = "Enem"},
    ["Yablonovskiy"] = { latitude = 44.986937, longitude = 38.944217, display_name = "Yablonovskiy"},
    ["Troitskiy"] = { latitude = 45.146496, longitude = 38.133592, display_name = "Troitskiy"},
    ["Gayduk"] = { latitude = 44.787974, longitude = 37.699085, display_name = "Gayduk"},
    ["Nizhnebakanskiy "] = { latitude = 44.868592, longitude = 37.861945, display_name = "Nizhnebakanskiy "},
    ["Verhnebakanskiy "] = { latitude = 44.843629, longitude = 37.660287, display_name = "Verhnebakanskiy "},
    ["Abrau-Dyurso"] = { latitude = 44.701124, longitude = 37.599438, display_name = "Abrau-Dyurso"},
    ["Achigvara"] = { latitude = 42.680590, longitude = 41.628738, display_name = "Achigvara"},
    ["Dzhukmur"] = { latitude = 42.746085, longitude = 41.450590, display_name = "Dzhukmur"},
    ["Okumi"] = { latitude = 42.724566, longitude = 41.754616, display_name = "Okumi"},
    ["Chkhortoli"] = { latitude = 42.761492, longitude = 41.733044, display_name = "Chkhortoli"},
    ["Beshaluba"] = { latitude = 42.757691, longitude = 41.515278, display_name = "Beshaluba"},
    ["Merkula"] = { latitude = 42.765603, longitude = 41.478017, display_name = "Merkula"},
    ["Aradu"] = { latitude = 42.779854, longitude = 41.463354, display_name = "Aradu"},
    ["Tsagera"] = { latitude = 42.779645, longitude = 41.422951, display_name = "Tsagera"},
    ["Labra"] = { latitude = 42.812025, longitude = 41.394992, display_name = "Labra"},
    ["Varcha"] = { latitude = 42.841492, longitude = 41.138267, display_name = "Varcha"},
    ["Babushara"] = { latitude = 42.853206, longitude = 41.116261, display_name = "Babushara"},
    ["Adzyubzha"] = { latitude = 42.838793, longitude = 41.191593, display_name = "Adzyubzha"},
    ["Arakich"] = { latitude = 42.845023, longitude = 41.248912, display_name = "Arakich"},
    ["Estonka"] = { latitude = 42.887601, longitude = 41.199704, display_name = "Estonka"},
    ["Nizh.- Pshap"] = { latitude = 42.888750, longitude = 41.125540, display_name = "Nizh.- Pshap"},
    ["Verh.- Pshap"] = { latitude = 42.891730, longitude = 41.155013, display_name = "Verh.- Pshap"},
    ["Shaumyanovka"] = { latitude = 42.909981, longitude = 41.190234, display_name = "Shaumyanovka"},
    ["Bagazhiashta"] = { latitude = 42.918151, longitude = 41.132147, display_name = "Bagazhiashta"},
    ["Gul'ripsh"] = { latitude = 42.926240, longitude = 41.101427, display_name = "Gul'ripsh"},
    ["Lentehi"] = { latitude = 42.790946, longitude = 42.724648, display_name = "Lentehi"},
    ["Mestia"] = { latitude = 43.049600, longitude = 42.729598, display_name = "Mestia"},
    ["Pervomayskoe"] = { latitude = 43.939875, longitude = 42.487590, display_name = "Pervomayskoe"},
    ["Uchkeken"] = { latitude = 43.943106, longitude = 42.514172, display_name = "Uchkeken"},
    ["Tereze"] = { latitude = 43.934122, longitude = 42.446490, display_name = "Tereze"},
    ["Bambora"] = { latitude = 43.101065, longitude = 40.593551, display_name = "Bambora"},
    ["Gudauta"] = { latitude = 43.106705, longitude = 40.635279, display_name = "Gudauta"},
    ["Novyy Afon"] = { latitude = 43.090391, longitude = 40.809345, display_name = "Novyy Afon"},
    ["Gagra"] = { latitude = 43.296491, longitude = 40.249993, display_name = "Gagra"},
    ["Bzyb'"] = { latitude = 43.229864, longitude = 40.360523, display_name = "Bzyb'"},
    ["Myussera"] = { latitude = 43.155608, longitude = 40.454065, display_name = "Myussera"},
    ["Pitsunda"] = { latitude = 43.163846, longitude = 40.338462, display_name = "Pitsunda"},
    ["Teberda"] = { latitude = 43.452155, longitude = 41.739692, display_name = "Teberda"},
    ["Karachaevsk"] = { latitude = 43.770235, longitude = 41.898619, display_name = "Karachaevsk"},
    ["Ordzhonikidzevskiy"] = { latitude = 43.844148, longitude = 41.892512, display_name = "Ordzhonikidzevskiy"},
    ["Gantiadi"] = { latitude = 43.393301, longitude = 40.088996, display_name = "Gantiadi"},
    ["Krasnaya Polyana"] = { latitude = 43.682548, longitude = 40.204334, display_name = "Krasnaya Polyana"},
    ["Kurdzhinovo"] = { latitude = 44.003499, longitude = 40.946645, display_name = "Kurdzhinovo"},
    ["Nov.Matsesta"] = { latitude = 43.560079, longitude = 39.801623, display_name = "Nov.Matsesta"},
    ["Star.Matsesta"] = { latitude = 43.582022, longitude = 39.802334, display_name = "Star.Matsesta"},
    ["Dagomys"] = { latitude = 43.665053, longitude = 39.658538, display_name = "Dagomys"},
    ["UDARNYY"] = { latitude = 44.351550, longitude = 42.502748, display_name = "UDARNYY"},
    ["SVOBODY"] = { latitude = 44.025747, longitude = 43.044756, display_name = "SVOBODY"},
    ["GORYACHEVODSKIY"] = { latitude = 44.023610, longitude = 43.098525, display_name = "GORYACHEVODSKIY"},
    ["INOZEMTSEVO"] = { latitude = 44.099998, longitude = 43.088585, display_name = "INOZEMTSEVO"},
    ["ANDZHIEVSKIY"] = { latitude = 44.239664, longitude = 43.084935, display_name = "ANDZHIEVSKIY"},
    ["Soldato-Aleksandrovskoe"] = { latitude = 44.266128, longitude = 43.758362, display_name = "Soldato-Aleksandrovskoe"},
    ["Aleksandriyskaya"] = { latitude = 44.226368, longitude = 43.341033, display_name = "Aleksandriyskaya"},
    ["Podgornaya"] = { latitude = 44.201130, longitude = 43.427100, display_name = "Podgornaya"},
    ["ZALUKOKOAZHE"] = { latitude = 43.902365, longitude = 43.218663, display_name = "ZALUKOKOAZHE"},
    ["CHEGEM PERVYY"] = { latitude = 43.569862, longitude = 43.581955, display_name = "CHEGEM PERVYY"},
    ["KENZHE"] = { latitude = 43.499694, longitude = 43.554187, display_name = "KENZHE"},
    ["HASAN'YA"] = { latitude = 43.438242, longitude = 43.578239, display_name = "HASAN'YA"},
    ["BELAYA RECHKA"] = { latitude = 43.434090, longitude = 43.509509, display_name = "BELAYA RECHKA"},
    ["KASHHATAU"] = { latitude = 43.318498, longitude = 43.607567, display_name = "KASHHATAU"},
    ["ZHemtala"] = { latitude = 43.285507, longitude = 43.651954, display_name = "ZHemtala"},
    ["Zaragizh"] = { latitude = 43.331056, longitude = 43.707394, display_name = "Zaragizh"},
    ["Yanikoy"] = { latitude = 43.544208, longitude = 43.507782, display_name = "Yanikoy"},
    ["ZHanhoteko"] = { latitude = 43.560102, longitude = 43.207769, display_name = "ZHanhoteko"},
    ["Zayukovo"] = { latitude = 43.620891, longitude = 43.316939, display_name = "Zayukovo"},
    ["Yantarnoe"] = { latitude = 43.762016, longitude = 43.881024, display_name = "Yantarnoe"},
    ["Zalukodes"] = { latitude = 43.836692, longitude = 43.154045, display_name = "Zalukodes"},
    ["Zol'skoe"] = { latitude = 43.842530, longitude = 43.202659, display_name = "Zol'skoe"},
    ["Zol'skaya"] = { latitude = 43.905414, longitude = 43.299699, display_name = "Zol'skaya"},
    ["Zarechnoe"] = { latitude = 43.937403, longitude = 43.856933, display_name = "Zarechnoe"},
    ["Yutsa"] = { latitude = 43.967055, longitude = 43.011684, display_name = "Yutsa"},
    ["Zakavkazskiy Partizan"] = { latitude = 44.116442, longitude = 43.838405, display_name = "Zakavkazskiy Partizan"},
    ["Zmeyka"] = { latitude = 44.141831, longitude = 43.116065, display_name = "Zmeyka"},
    ["Zagorskiy"] = { latitude = 44.258175, longitude = 43.124187, display_name = "Zagorskiy"},
    ["ZHeleznodorozhnyy"] = { latitude = 44.283840, longitude = 43.730236, display_name = "ZHeleznodorozhnyy"},
    ["Yasnaya Polyana"] = { latitude = 44.026131, longitude = 42.751359, display_name = "Yasnaya Polyana"},
    ["Zolotushka"] = { latitude = 44.048106, longitude = 42.968682, display_name = "Zolotushka"},
    ["Zheleznovodskiy"] = { latitude = 44.155908, longitude = 42.989749, display_name = "Zheleznovodskiy"},
    ["Vodorazdel'nyy"] = { latitude = 44.253379, longitude = 42.343575, display_name = "Vodorazdel'nyy"},
    ["Vorovskolesskaya"] = { latitude = 44.379548, longitude = 42.404731, display_name = "Vorovskolesskaya"},
    ["Volkonka"] = { latitude = 43.869753, longitude = 39.394933, display_name = "Volkonka"},
    ["Volkovka"] = { latitude = 43.697214, longitude = 39.665990, display_name = "Volkovka"},
    ["Vorontsovka"] = { latitude = 43.620869, longitude = 39.918245, display_name = "Vorontsovka"},
    ["Verhne-Veseloe"] = { latitude = 43.426208, longitude = 39.979875, display_name = "Verhne-Veseloe"},
    ["Veseloe"] = { latitude = 43.412545, longitude = 40.002760, display_name = "Veseloe"},
    ["Zelenchukskaya"] = { latitude = 43.858968, longitude = 41.582415, display_name = "Zelenchukskaya"},
    ["Verhnyaya-Mtsara"] = { latitude = 43.169050, longitude = 40.766431, display_name = "Verhnyaya-Mtsara"},
    ["Verhnyaya Eshera"] = { latitude = 43.075860, longitude = 40.892093, display_name = "Verhnyaya Eshera"},
    ["Znamenka"] = { latitude = 44.146904, longitude = 42.058306, display_name = "Znamenka"},
    ["Vinsady"] = { latitude = 44.080854, longitude = 42.959745, display_name = "Vinsady"},
    ["Zemo-Machara"] = { latitude = 43.007251, longitude = 41.185583, display_name = "Zemo-Machara"},
    ["Zemo-Azhara"] = { latitude = 43.111753, longitude = 41.749381, display_name = "Zemo-Azhara"},
    ["Vladimirovka"] = { latitude = 42.893409, longitude = 41.233317, display_name = "Vladimirovka"},
    ["Zaporozhskaya"] = { latitude = 45.380489, longitude = 36.863126, display_name = "Zaporozhskaya"},
    ["Volna Revolyutsii"] = { latitude = 45.335780, longitude = 36.945070, display_name = "Volna Revolyutsii"},
    ["Vinogradnyy"] = { latitude = 45.194969, longitude = 36.898478, display_name = "Vinogradnyy"},
    ["Veselovka"] = { latitude = 45.130495, longitude = 36.900949, display_name = "Veselovka"},
    ["Vyshesteblievskaya"] = { latitude = 45.197182, longitude = 36.997904, display_name = "Vyshesteblievskaya"},
    ["Vestnik"] = { latitude = 45.102251, longitude = 37.452285, display_name = "Vestnik"},
    ["Yurovka"] = { latitude = 45.116079, longitude = 37.412790, display_name = "Yurovka"},
    ["Vinogradnyy"] = { latitude = 45.058888, longitude = 37.327405, display_name = "Vinogradnyy"},
    ["Vityazevo"] = { latitude = 44.998498, longitude = 37.272707, display_name = "Vityazevo"},
    ["Voskresenskiy"] = { latitude = 44.967396, longitude = 37.326592, display_name = "Voskresenskiy"},
    ["Vinogradnyy"] = { latitude = 44.980235, longitude = 37.912105, display_name = "Vinogradnyy"},
    ["Vladimirovka"] = { latitude = 44.792068, longitude = 37.675603, display_name = "Vladimirovka"},
    ["Vorontsovskaya"] = { latitude = 45.221638, longitude = 38.736370, display_name = "Vorontsovskaya"},
    ["Yuzhnyy"] = { latitude = 45.033052, longitude = 38.079312, display_name = "Yuzhnyy"},
    ["ZHeleznyy"] = { latitude = 45.299309, longitude = 39.553831, display_name = "ZHeleznyy"},
    ["Voronezhskaya"] = { latitude = 45.212918, longitude = 39.561800, display_name = "Voronezhskaya"},
    ["Zarozhdenie"] = { latitude = 45.159335, longitude = 39.231552, display_name = "Zarozhdenie"},
    ["Znamenskiy"] = { latitude = 45.060774, longitude = 39.142515, display_name = "Znamenskiy"},
    ["Vysotnyy"] = { latitude = 44.956537, longitude = 39.680462, display_name = "Vysotnyy"},
    ["Vochepshiy"] = { latitude = 44.876265, longitude = 39.284439, display_name = "Vochepshiy"},
    ["Zarechnyy"] = { latitude = 44.757202, longitude = 39.826973, display_name = "Zarechnyy"},
    ["Yuzhnyy"] = { latitude = 44.730125, longitude = 39.864296, display_name = "Yuzhnyy"},
    ["Veselyy"] = { latitude = 44.676962, longitude = 39.936165, display_name = "Veselyy"},
    ["Yuzhnyy"] = { latitude = 45.148146, longitude = 39.027375, display_name = "Yuzhnyy"},
    ["Vozdvizhenskaya"] = { latitude = 45.129818, longitude = 40.142530, display_name = "Vozdvizhenskaya"},
    ["Zarevo"] = { latitude = 45.002707, longitude = 40.081195, display_name = "Zarevo"},
    ["Vozrozhdenie"] = { latitude = 44.549250, longitude = 38.217419, display_name = "Vozrozhdenie"},
    ["Vpered"] = { latitude = 44.550175, longitude = 39.705213, display_name = "Vpered"},
    ["Zeyuko"] = { latitude = 44.118806, longitude = 41.826530, display_name = "Zeyuko"},
    ["Zubi"] = { latitude = 42.571365, longitude = 42.669290, display_name = "Zubi"},
    ["Zeda-Gordi"] = { latitude = 42.456377, longitude = 42.522623, display_name = "Zeda-Gordi"},
    ["Zeda-Mesheti"] = { latitude = 42.222265, longitude = 42.656102, display_name = "Zeda-Mesheti"},
    ["Zeda-Dimi"] = { latitude = 42.073116, longitude = 42.839455, display_name = "Zeda-Dimi"},
    ["Zemo-Shuhuti"] = { latitude = 42.081785, longitude = 42.093085, display_name = "Zemo-Shuhuti"},
    ["Zeda-Etseri"] = { latitude = 42.081659, longitude = 42.417598, display_name = "Zeda-Etseri"},
    ["Zeda-Tsihesulori"] = { latitude = 42.087890, longitude = 42.505130, display_name = "Zeda-Tsihesulori"},
    ["Zeindari"] = { latitude = 42.093768, longitude = 42.674916, display_name = "Zeindari"},
    ["Zeda-Mukedi"] = { latitude = 42.067746, longitude = 42.468248, display_name = "Zeda-Mukedi"},
    ["Zeda-Vani"] = { latitude = 42.066098, longitude = 42.519928, display_name = "Zeda-Vani"},
    ["Zeda-Gora"] = { latitude = 42.059853, longitude = 42.690921, display_name = "Zeda-Gora"},
    ["Zeda-Zegani"] = { latitude = 42.038936, longitude = 42.921891, display_name = "Zeda-Zegani"},
    ["Zemo-Partshma"] = { latitude = 42.036261, longitude = 42.247299, display_name = "Zemo-Partshma"},
    ["Zeda-Dzimiti"] = { latitude = 42.013334, longitude = 42.062550, display_name = "Zeda-Dzimiti"},
    ["Zovreti"] = { latitude = 42.180543, longitude = 43.040004, display_name = "Zovreti"},
    ["Zeda-Bahvi"] = { latitude = 41.947222, longitude = 42.111167, display_name = "Zeda-Bahvi"},
    ["Zoti"] = { latitude = 41.893183, longitude = 42.444286, display_name = "Zoti"},
    ["Zvare"] = { latitude = 41.982828, longitude = 43.415672, display_name = "Zvare"},
    ["Zeni"] = { latitude = 42.520439, longitude = 41.713372, display_name = "Zeni"},
    ["Zeni"] = { latitude = 42.384638, longitude = 41.968533, display_name = "Zeni"},
    ["Zeda-Etseri"] = { latitude = 42.585548, longitude = 41.920678, display_name = "Zeda-Etseri"},
    ["Zeda-Lia"] = { latitude = 42.676335, longitude = 42.023392, display_name = "Zeda-Lia"},
    ["Zaragula"] = { latitude = 42.627553, longitude = 42.712001, display_name = "Zaragula"},
    ["Zemo-ZHoshkha"] = { latitude = 42.586561, longitude = 42.952919, display_name = "Zemo-ZHoshkha"},
    ["Zogishi"] = { latitude = 42.554181, longitude = 42.841330, display_name = "Zogishi"},
    ["Zeda-Shavra"] = { latitude = 42.503286, longitude = 42.990015, display_name = "Zeda-Shavra"},
    ["Zubi"] = { latitude = 42.397134, longitude = 42.013864, display_name = "Zubi"},
    ["Zemo-Huntsi"] = { latitude = 42.407792, longitude = 42.426617, display_name = "Zemo-Huntsi"},
    ["Zarati"] = { latitude = 42.355384, longitude = 42.723736, display_name = "Zarati"},
    ["Zomleti"] = { latitude = 42.035595, longitude = 42.138993, display_name = "Zomleti"},
    ["Zemo-Aketi"] = { latitude = 42.046732, longitude = 42.099893, display_name = "Zemo-Aketi"},
    ["Zemo-"] = { latitude = 42.522736, longitude = 43.301035, display_name = "Zemo-"},
    ["Zeda-Kveda"] = { latitude = 42.341887, longitude = 43.488780, display_name = "Zeda-Kveda"},
    ["Zaarnadzeebi"] = { latitude = 42.256196, longitude = 43.012686, display_name = "Zaarnadzeebi"},
    ["Zeda-Beretisa"] = { latitude = 42.195126, longitude = 43.402533, display_name = "Zeda-Beretisa"},
    ["Vertkvichala"] = { latitude = 42.105803, longitude = 43.304873, display_name = "Vertkvichala"},
    ["Zedubani"] = { latitude = 41.962796, longitude = 43.310404, display_name = "Zedubani"},
    ["Zemo-Gumurishi"] = { latitude = 42.710482, longitude = 41.788429, display_name = "Zemo-Gumurishi"},
    ["Zeni"] = { latitude = 42.617798, longitude = 41.882572, display_name = "Zeni"},
    ["Zeni"] = { latitude = 42.565132, longitude = 41.819593, display_name = "Zeni"},
    ["Zeni"] = { latitude = 42.312676, longitude = 41.922268, display_name = "Zeni"},
    ["Zemo-Natanebi"] = { latitude = 41.963000, longitude = 41.870221, display_name = "Zemo-Natanebi"},
    ["Zeda-Dagva"] = { latitude = 41.754863, longitude = 41.807808, display_name = "Zeda-Dagva"},
    ["Zartsupa"] = { latitude = 42.493699, longitude = 41.644697, display_name = "Zartsupa"},
    ["ZHoneti"] = { latitude = 42.370197, longitude = 42.703253, display_name = "ZHoneti"},
    ["Zeda-Sameba"] = { latitude = 41.791274, longitude = 41.859312, display_name = "Zeda-Sameba"},
    ["Verhne-Nikolaevskoe"] = { latitude = 43.529614, longitude = 39.929520, display_name = "Verhne-Nikolaevskoe"},
    ["Verhne-Imeretinskaya Buhta"] = { latitude = 43.417052, longitude = 39.975434, display_name = "Verhne-Imeretinskaya Buhta"},
    ["Verhne-Armyanskoe Loo"] = { latitude = 43.739337, longitude = 39.617997, display_name = "Verhne-Armyanskoe Loo"},
    ["Verh.ZHemtala"] = { latitude = 43.236728, longitude = 43.669495, display_name = "Verh.ZHemtala"},
    ["Verh.Teberda"] = { latitude = 43.538315, longitude = 41.782876, display_name = "Verh.Teberda"},
    ["Verh.Mara"] = { latitude = 43.771537, longitude = 42.136894, display_name = "Verh.Mara"},
    ["Verh.Kurkuzhin"] = { latitude = 43.705952, longitude = 43.296825, display_name = "Verh.Kurkuzhin"},
    ["Verh.Chegem"] = { latitude = 43.240698, longitude = 43.134321, display_name = "Verh.Chegem"},
    ["Verh.Balkariya"] = { latitude = 43.131049, longitude = 43.456872, display_name = "Verh.Balkariya"},
    ["Verh.Baksan"] = { latitude = 43.309417, longitude = 42.750280, display_name = "Verh.Baksan"},
    ["Verevkin"] = { latitude = 45.220310, longitude = 40.354313, display_name = "Verevkin"},
    ["Velikovechnoe"] = { latitude = 44.936567, longitude = 39.749784, display_name = "Velikovechnoe"},
    ["Vazhnoe"] = { latitude = 43.992853, longitude = 41.940704, display_name = "Vazhnoe"},
    ["Vasil'evskiy"] = { latitude = 45.091406, longitude = 38.531103, display_name = "Vasil'evskiy"},
    ["Varvarovka"] = { latitude = 44.834052, longitude = 37.372957, display_name = "Varvarovka"},
    ["Vartsihe"] = { latitude = 42.144134, longitude = 42.718334, display_name = "Vartsihe"},
    ["Varnavinskoe"] = { latitude = 44.995514, longitude = 38.192444, display_name = "Varnavinskoe"},
    ["Varenikovskaya"] = { latitude = 45.120464, longitude = 37.634587, display_name = "Varenikovskaya"},
    ["Vardane"] = { latitude = 43.734603, longitude = 39.553328, display_name = "Vardane"},
    ["Vani"] = { latitude = 41.986843, longitude = 43.203182, display_name = "Vani"},
    ["Vani"] = { latitude = 42.048170, longitude = 42.149633, display_name = "Vani"},
    ["Vakidzhvari"] = { latitude = 41.915906, longitude = 42.141490, display_name = "Vakidzhvari"},
    ["Vachevi"] = { latitude = 42.319197, longitude = 43.198124, display_name = "Vachevi"},
    ["Utsera"] = { latitude = 42.633276, longitude = 43.538861, display_name = "Utsera"},
    ["Utash"] = { latitude = 45.097694, longitude = 37.319662, display_name = "Utash"},
    ["Usahelo"] = { latitude = 42.597293, longitude = 42.828361, display_name = "Usahelo"},
    ["Usahelo"] = { latitude = 42.232442, longitude = 43.371765, display_name = "Usahelo"},
    ["Urvani"] = { latitude = 42.642964, longitude = 43.282974, display_name = "Urvani"},
    ["Urvan'"] = { latitude = 43.490777, longitude = 43.764370, display_name = "Urvan'"},
    ["Urup"] = { latitude = 43.846970, longitude = 41.152495, display_name = "Urup"},
    ["Uruhskaya"] = { latitude = 44.153716, longitude = 43.669649, display_name = "Uruhskaya"},
    ["Urta"] = { latitude = 42.430754, longitude = 41.841157, display_name = "Urta"},
    ["Urozhaynyy"] = { latitude = 44.116948, longitude = 42.762639, display_name = "Urozhaynyy"},
    ["Ureki"] = { latitude = 41.994268, longitude = 41.778543, display_name = "Ureki"},
    ["Ulyap"] = { latitude = 45.055071, longitude = 39.950905, display_name = "Ulyap"},
    ["Uluria"] = { latitude = 42.585800, longitude = 42.113808, display_name = "Uluria"},
    ["Ul'yanovka"] = { latitude = 44.303471, longitude = 42.927167, display_name = "Ul'yanovka"},
    ["Ukrainskiy"] = { latitude = 45.254040, longitude = 39.354076, display_name = "Ukrainskiy"},
    ["Ukanava"] = { latitude = 41.934585, longitude = 42.186210, display_name = "Ukanava"},
    ["Uhuti"] = { latitude = 42.040771, longitude = 42.683152, display_name = "Uhuti"},
    ["Udobnaya"] = { latitude = 44.214925, longitude = 41.562212, display_name = "Udobnaya"},
    ["Uchkulan"] = { latitude = 43.455180, longitude = 42.087454, display_name = "Uchkulan"},
    ["Uchashona"] = { latitude = 42.493247, longitude = 41.924453, display_name = "Uchashona"},
    ["Ubisi"] = { latitude = 42.098775, longitude = 43.226444, display_name = "Ubisi"},
    ["Ubinskaya"] = { latitude = 44.737000, longitude = 38.541069, display_name = "Ubinskaya"},
    ["Uazabaa"] = { latitude = 43.054593, longitude = 40.976749, display_name = "Uazabaa"},
    ["Tyumenskiy"] = { latitude = 44.182244, longitude = 38.972674, display_name = "Tyumenskiy"},
    ["Tvrini"] = { latitude = 42.073237, longitude = 43.072609, display_name = "Tvrini"},
    ["Tvishi"] = { latitude = 42.515837, longitude = 42.788506, display_name = "Tvishi"},
    ["Tverskaya"] = { latitude = 44.604805, longitude = 39.610687, display_name = "Tverskaya"},
    ["Tvalueti"] = { latitude = 42.227705, longitude = 43.268895, display_name = "Tvalueti"},
    ["Tuzi"] = { latitude = 42.272471, longitude = 43.110811, display_name = "Tuzi"},
    ["Tsvirmi"] = { latitude = 43.017141, longitude = 42.801118, display_name = "Tsvirmi"},
    ["Tsvane"] = { latitude = 42.370055, longitude = 41.654112, display_name = "Tsvane"},
    ["Tsutshvati"] = { latitude = 42.289077, longitude = 42.860776, display_name = "Tsutshvati"},
    ["Tskrysh"] = { latitude = 42.782967, longitude = 41.373482, display_name = "Tskrysh"},
    ["Tsknori"] = { latitude = 42.402082, longitude = 42.890375, display_name = "Tsknori"},
    ["Tskemi"] = { latitude = 42.269262, longitude = 42.174364, display_name = "Tskemi"},
    ["Tskavroka"] = { latitude = 41.859784, longitude = 41.888202, display_name = "Tskavroka"},
    ["Tskaltsminda"] = { latitude = 42.011212, longitude = 41.778545, display_name = "Tskaltsminda"},
    ["Tskaltashua"] = { latitude = 42.022671, longitude = 42.828904, display_name = "Tskaltashua"},
    ["Tskalshavi"] = { latitude = 42.219307, longitude = 43.341725, display_name = "Tskalshavi"},
    ["Tskalaporeti"] = { latitude = 42.031419, longitude = 43.078673, display_name = "Tskalaporeti"},
    ["Tsipnara"] = { latitude = 42.024010, longitude = 42.293819, display_name = "Tsipnara"},
    ["Tsipnagvara"] = { latitude = 41.955727, longitude = 42.224549, display_name = "Tsipnagvara"},
    ["Tsiperchi"] = { latitude = 42.603636, longitude = 42.695129, display_name = "Tsiperchi"},
    ["Tsipa"] = { latitude = 42.046461, longitude = 42.949247, display_name = "Tsipa"},
    ["Tsipa"] = { latitude = 42.009347, longitude = 43.451125, display_name = "Tsipa"},
    ["Tsihisdziri"] = { latitude = 41.772133, longitude = 41.762802, display_name = "Tsihisdziri"},
    ["Tsibanobalka"] = { latitude = 44.982008, longitude = 37.342091, display_name = "Tsibanobalka"},
    ["Tshunkuri"] = { latitude = 42.396804, longitude = 42.570436, display_name = "Tshunkuri"},
    ["Tshmori"] = { latitude = 42.536955, longitude = 43.478057, display_name = "Tshmori"},
    ["Tshentaro"] = { latitude = 42.147873, longitude = 42.837507, display_name = "Tshentaro"},
    ["Tshenis-Tskali"] = { latitude = 42.796462, longitude = 41.419871, display_name = "Tshenis-Tskali"},
    ["Tshemlishidi"] = { latitude = 41.921541, longitude = 42.071692, display_name = "Tshemlishidi"},
    ["Tshami"] = { latitude = 42.291453, longitude = 43.510570, display_name = "Tshami"},
    ["Tsesi"] = { latitude = 42.542767, longitude = 43.196616, display_name = "Tsesi"},
    ["Tsedisi"] = { latitude = 42.532887, longitude = 43.548207, display_name = "Tsedisi"},
    ["Tsebel'da"] = { latitude = 43.022159, longitude = 41.269029, display_name = "Tsebel'da"},
    ["Tsatshvi"] = { latitude = 42.414906, longitude = 41.789573, display_name = "Tsatshvi"},
    ["Tsalkoti"] = { latitude = 43.407001, longitude = 40.052620, display_name = "Tsalkoti"},
    ["Tsaishi"] = { latitude = 42.431382, longitude = 41.803488, display_name = "Tsaishi"},
    ["Tsahi"] = { latitude = 42.526030, longitude = 42.922673, display_name = "Tsahi"},
    ["Trudobelikovskiy"] = { latitude = 45.285178, longitude = 38.142705, display_name = "Trudobelikovskiy"},
    ["Travlev"] = { latitude = 44.370687, longitude = 39.537629, display_name = "Travlev"},
    ["Tolebi"] = { latitude = 42.074187, longitude = 42.240400, display_name = "Tolebi"},
    ["Tkviri"] = { latitude = 42.169336, longitude = 42.246736, display_name = "Tkviri"},
    ["Tklapivake"] = { latitude = 42.172575, longitude = 43.068294, display_name = "Tklapivake"},
    ["Tkelvani"] = { latitude = 42.047842, longitude = 42.551287, display_name = "Tkelvani"},
    ["Tkaya"] = { latitude = 42.622523, longitude = 41.928621, display_name = "Tkaya"},
    ["Tkachiri"] = { latitude = 42.142229, longitude = 42.632747, display_name = "Tkachiri"},
    ["Tihovskiy"] = { latitude = 45.194706, longitude = 38.218835, display_name = "Tihovskiy"},
    ["Thmelari"] = { latitude = 42.145418, longitude = 42.263166, display_name = "Thmelari"},
    ["Thina"] = { latitude = 42.881268, longitude = 41.544684, display_name = "Thina"},
    ["Thilnari"] = { latitude = 41.569953, longitude = 41.647305, display_name = "Thilnari"},
    ["Teuchezhkhabl'"] = { latitude = 44.931656, longitude = 39.562499, display_name = "Teuchezhkhabl'"},
    ["Tenginskaya"] = { latitude = 45.100953, longitude = 40.008266, display_name = "Tenginskaya"},
    ["Tenginka"] = { latitude = 44.328982, longitude = 38.783412, display_name = "Tenginka"},
    ["Telmani"] = { latitude = 42.041496, longitude = 42.055072, display_name = "Telmani"},
    ["Telepa"] = { latitude = 42.199325, longitude = 43.013716, display_name = "Telepa"},
    ["Teklati"] = { latitude = 42.253536, longitude = 42.009190, display_name = "Teklati"},
    ["Taya"] = { latitude = 42.610109, longitude = 42.213082, display_name = "Taya"},
    ["Tavisupleba"] = { latitude = 43.041709, longitude = 41.016536, display_name = "Tavisupleba"},
    ["Tavasa"] = { latitude = 42.275571, longitude = 43.082456, display_name = "Tavasa"},
    ["Tashly-Tala"] = { latitude = 43.146908, longitude = 43.697247, display_name = "Tashly-Tala"},
    ["Tambukan"] = { latitude = 43.932197, longitude = 43.142965, display_name = "Tambukan"},
    ["Tamanskiy"] = { latitude = 45.150180, longitude = 36.782611, display_name = "Tamanskiy"},
    ["Taman'"] = { latitude = 45.211080, longitude = 36.712001, display_name = "Taman'"},
    ["Tamakoni"] = { latitude = 42.458194, longitude = 42.305573, display_name = "Tamakoni"},
    ["Tallyk"] = { latitude = 44.153193, longitude = 42.343567, display_name = "Tallyk"},
    ["Tahtamukay"] = { latitude = 44.922632, longitude = 38.994206, display_name = "Tahtamukay"},
    ["Tagiloni"] = { latitude = 42.547563, longitude = 41.772461, display_name = "Tagiloni"},
    ["Tabori"] = { latitude = 42.605990, longitude = 42.892410, display_name = "Tabori"},
    ["Tabakini"] = { latitude = 42.065905, longitude = 43.036069, display_name = "Tabakini"},
    ["Tabagrebi"] = { latitude = 42.328721, longitude = 43.275940, display_name = "Tabagrebi"},
    ["Tabachnyy"] = { latitude = 44.556422, longitude = 40.091093, display_name = "Tabachnyy"},
    ["Svoboda"] = { latitude = 44.155825, longitude = 42.768210, display_name = "Svoboda"},
    ["svh.Tagrskiy"] = { latitude = 43.199276, longitude = 40.284213, display_name = "svh.Tagrskiy"},
    ["svh.Nasakirali"] = { latitude = 41.970377, longitude = 42.020224, display_name = "svh.Nasakirali"},
    ["svh.Kohora"] = { latitude = 42.659065, longitude = 41.708095, display_name = "svh.Kohora"},
    ["svh.Horshi"] = { latitude = 42.341095, longitude = 42.035080, display_name = "svh.Horshi"},
    ["svh.Didi-Chkonskiy"] = { latitude = 42.539708, longitude = 42.290066, display_name = "svh.Didi-Chkonskiy"},
    ["svh.Ahalsopeli"] = { latitude = 42.249177, longitude = 41.857919, display_name = "svh.Ahalsopeli"},
    ["Svetlyy Put' Lenina"] = { latitude = 45.208583, longitude = 37.656995, display_name = "Svetlyy Put' Lenina"},
    ["Svetlovodskoe"] = { latitude = 43.896544, longitude = 43.180565, display_name = "Svetlovodskoe"},
    ["Sveri"] = { latitude = 42.226188, longitude = 43.303560, display_name = "Sveri"},
    ["Suzdal'skaya"] = { latitude = 44.766994, longitude = 39.368694, display_name = "Suzdal'skaya"},
    ["Suvorovskoe"] = { latitude = 45.286566, longitude = 39.460538, display_name = "Suvorovskoe"},
    ["Suvorovskaya"] = { latitude = 44.187846, longitude = 42.658805, display_name = "Suvorovskaya"},
    ["Suvorov-Cherkesskiy"] = { latitude = 45.069861, longitude = 37.271582, display_name = "Suvorov-Cherkesskiy"},
    ["Surmushi"] = { latitude = 42.592088, longitude = 42.878475, display_name = "Surmushi"},
    ["Supseh"] = { latitude = 44.861146, longitude = 37.364340, display_name = "Supseh"},
    ["Supovskiy"] = { latitude = 44.902356, longitude = 38.942116, display_name = "Supovskiy"},
    ["Sulori"] = { latitude = 42.025049, longitude = 42.577617, display_name = "Sulori"},
    ["Sukko"] = { latitude = 44.798695, longitude = 37.420789, display_name = "Sukko"},
    ["Suhoy Kut"] = { latitude = 45.127745, longitude = 40.207372, display_name = "Suhoy Kut"},
    ["Suhcha"] = { latitude = 42.417076, longitude = 42.468544, display_name = "Suhcha"},
    ["Sudzhuna"] = { latitude = 42.200960, longitude = 42.146446, display_name = "Sudzhuna"},
    ["Strelka"] = { latitude = 45.204296, longitude = 37.286910, display_name = "Strelka"},
    ["Storozhevaya"] = { latitude = 43.883112, longitude = 41.453295, display_name = "Storozhevaya"},
    ["Stavropol'skaya"] = { latitude = 44.718327, longitude = 38.825185, display_name = "Stavropol'skaya"},
    ["Starotitarovskaya"] = { latitude = 45.219384, longitude = 37.161638, display_name = "Starotitarovskaya"},
    ["Staropavlovskaya"] = { latitude = 43.846862, longitude = 43.635628, display_name = "Staropavlovskaya"},
    ["Starokorsunskaya"] = { latitude = 45.056256, longitude = 39.314748, display_name = "Starokorsunskaya"},
    ["Starobzhegokay"] = { latitude = 45.037835, longitude = 38.890909, display_name = "Starobzhegokay"},
    ["Star.Cherek"] = { latitude = 43.474391, longitude = 43.858173, display_name = "Star.Cherek"},
    ["Stantsionnyy"] = { latitude = 44.438859, longitude = 39.474111, display_name = "Stantsionnyy"},
    ["Spokoynaya"] = { latitude = 44.259312, longitude = 41.390173, display_name = "Spokoynaya"},
    ["Speti"] = { latitude = 42.322001, longitude = 43.523397, display_name = "Speti"},
    ["Spatagori"] = { latitude = 42.612449, longitude = 42.820431, display_name = "Spatagori"},
    ["Sovhoznyy"] = { latitude = 44.543971, longitude = 40.152743, display_name = "Sovhoznyy"},
    ["Sovhoznyy"] = { latitude = 45.297970, longitude = 38.106763, display_name = "Sovhoznyy"},
    ["Sovhoznoe"] = { latitude = 43.803855, longitude = 43.147584, display_name = "Sovhoznoe"},
    ["Sormoni"] = { latitude = 42.320448, longitude = 42.736131, display_name = "Sormoni"},
    ["Soloniki"] = { latitude = 43.884949, longitude = 39.378103, display_name = "Soloniki"},
    ["Solenoe"] = { latitude = 44.044307, longitude = 40.872960, display_name = "Solenoe"},
    ["Soldatskaya"] = { latitude = 43.812310, longitude = 43.823078, display_name = "Soldatskaya"},
    ["Soglasnyy"] = { latitude = 45.237391, longitude = 39.975613, display_name = "Soglasnyy"},
    ["Sochkheti"] = { latitude = 42.395196, longitude = 42.910288, display_name = "Sochkheti"},
    ["Smolenskaya"] = { latitude = 44.786688, longitude = 38.800768, display_name = "Smolenskaya"},
    ["Skurdi"] = { latitude = 42.476765, longitude = 42.368497, display_name = "Skurdi"},
    ["Skura"] = { latitude = 41.866696, longitude = 41.925186, display_name = "Skura"},
    ["Skindori"] = { latitude = 42.242029, longitude = 43.249227, display_name = "Skindori"},
    ["Sinegorsk"] = { latitude = 44.775835, longitude = 38.338404, display_name = "Sinegorsk"},
    ["Simoneti"] = { latitude = 42.238107, longitude = 42.912135, display_name = "Simoneti"},
    ["Siktarva"] = { latitude = 42.192302, longitude = 42.940934, display_name = "Siktarva"},
    ["Sida"] = { latitude = 42.570767, longitude = 41.712233, display_name = "Sida"},
    ["Shuntuk"] = { latitude = 44.455608, longitude = 40.164758, display_name = "Shuntuk"},
    ["Shuamta"] = { latitude = 42.097609, longitude = 42.451121, display_name = "Shuamta"},
    ["Shua-shvava"] = { latitude = 42.501499, longitude = 43.221855, display_name = "Shua-shvava"},
    ["Shua-Nosiri"] = { latitude = 42.280291, longitude = 42.094655, display_name = "Shua-Nosiri"},
    ["Shua-Gubi"] = { latitude = 42.261628, longitude = 42.466473, display_name = "Shua-Gubi"},
    ["Shua-Gezruli"] = { latitude = 42.153417, longitude = 43.219613, display_name = "Shua-Gezruli"},
    ["Shua-Bashi"] = { latitude = 42.143434, longitude = 42.493907, display_name = "Shua-Bashi"},
    ["Shturbino"] = { latitude = 45.054728, longitude = 39.912252, display_name = "Shturbino"},
    ["Shrosha"] = { latitude = 42.115040, longitude = 43.179695, display_name = "Shrosha"},
    ["Shromiskari"] = { latitude = 42.358092, longitude = 42.116093, display_name = "Shromiskari"},
    ["Shroma"] = { latitude = 42.522152, longitude = 43.061884, display_name = "Shroma"},
    ["Shroma"] = { latitude = 43.082720, longitude = 41.035309, display_name = "Shroma"},
    ["Shovgenovskiy"] = { latitude = 45.021933, longitude = 40.235745, display_name = "Shovgenovskiy"},
    ["Shomaheti"] = { latitude = 42.214857, longitude = 43.428888, display_name = "Shomaheti"},
    ["Shkol'nyy"] = { latitude = 45.034098, longitude = 37.615478, display_name = "Shkol'nyy"},
    ["Shkol'noe"] = { latitude = 44.916928, longitude = 39.856176, display_name = "Shkol'noe"},
    ["Shitskvara"] = { latitude = 43.070784, longitude = 40.927797, display_name = "Shitskvara"},
    ["Shithala"] = { latitude = 43.555555, longitude = 43.798022, display_name = "Shithala"},
    ["Shirvanskaya"] = { latitude = 44.380256, longitude = 39.807018, display_name = "Shirvanskaya"},
    ["Shirokaya Balka"] = { latitude = 44.487080, longitude = 39.412907, display_name = "Shirokaya Balka"},
    ["Shevchenko"] = { latitude = 44.899509, longitude = 39.516976, display_name = "Shevchenko"},
    ["Shepsi"] = { latitude = 44.038265, longitude = 39.141680, display_name = "Shepsi"},
    ["Shendzhiy"] = { latitude = 44.887778, longitude = 39.060568, display_name = "Shendzhiy"},
    ["Shedok"] = { latitude = 44.221288, longitude = 40.837051, display_name = "Shedok"},
    ["Shaumyanskiy"] = { latitude = 44.163229, longitude = 43.539348, display_name = "Shaumyanskiy"},
    ["Shaumyan"] = { latitude = 44.323123, longitude = 39.290108, display_name = "Shaumyan"},
    ["Shardakovo"] = { latitude = 43.879279, longitude = 43.102586, display_name = "Shardakovo"},
    ["Shamgona"] = { latitude = 42.521753, longitude = 41.769134, display_name = "Shamgona"},
    ["Shalushka"] = { latitude = 43.530601, longitude = 43.568470, display_name = "Shalushka"},
    ["Shahe"] = { latitude = 43.789565, longitude = 39.474887, display_name = "Shahe"},
    ["Severskaya"] = { latitude = 44.854150, longitude = 38.678560, display_name = "Severskaya"},
    ["Severnyy"] = { latitude = 44.658857, longitude = 40.117073, display_name = "Severnyy"},
    ["Sevastopol'skaya"] = { latitude = 44.354847, longitude = 40.303966, display_name = "Sevastopol'skaya"},
    ["Seva"] = { latitude = 42.555444, longitude = 43.351597, display_name = "Seva"},
    ["Sergieti"] = { latitude = 42.391498, longitude = 42.323468, display_name = "Sergieti"},
    ["Sepieti"] = { latitude = 42.281965, longitude = 42.243453, display_name = "Sepieti"},
    ["Sennoy"] = { latitude = 45.294518, longitude = 36.989450, display_name = "Sennoy"},
    ["Semisvodnyy"] = { latitude = 45.300754, longitude = 37.969587, display_name = "Semisvodnyy"},
    ["Schastlivoe"] = { latitude = 44.164789, longitude = 42.271165, display_name = "Schastlivoe"},
    ["Sazano"] = { latitude = 42.218143, longitude = 43.062941, display_name = "Sazano"},
    ["Savane"] = { latitude = 42.315460, longitude = 43.467280, display_name = "Savane"},
    ["Saukdere"] = { latitude = 44.903911, longitude = 37.885750, display_name = "Saukdere"},
    ["Satkebuchao"] = { latitude = 42.402993, longitude = 42.058518, display_name = "Satkebuchao"},
    ["Sashamugio"] = { latitude = 42.580189, longitude = 41.658063, display_name = "Sashamugio"},
    ["Sasashi"] = { latitude = 42.800320, longitude = 42.978569, display_name = "Sasashi"},
    ["Sary-Tyuz"] = { latitude = 43.902060, longitude = 41.893315, display_name = "Sary-Tyuz"},
    ["Sarmakovo"] = { latitude = 43.741446, longitude = 43.196640, display_name = "Sarmakovo"},
    ["Sareki"] = { latitude = 42.330518, longitude = 43.359819, display_name = "Sareki"},
    ["Saratovskiy"] = { latitude = 45.212657, longitude = 39.980321, display_name = "Saratovskiy"},
    ["Saratovskiy"] = { latitude = 45.101815, longitude = 39.762800, display_name = "Saratovskiy"},
    ["Saratovskaya"] = { latitude = 44.711278, longitude = 39.218458, display_name = "Saratovskaya"},
    ["Sarakoni"] = { latitude = 42.503838, longitude = 42.058830, display_name = "Sarakoni"},
    ["Saprasia"] = { latitude = 42.037612, longitude = 42.645146, display_name = "Saprasia"},
    ["Saodishario"] = { latitude = 42.246902, longitude = 42.114695, display_name = "Saodishario"},
    ["San.Im.Lenina"] = { latitude = 42.926139, longitude = 41.115398, display_name = "San.Im.Lenina"},
    ["Samikao"] = { latitude = 42.263550, longitude = 42.296038, display_name = "Samikao"},
    ["Samelalio"] = { latitude = 42.707453, longitude = 41.749466, display_name = "Samelalio"},
    ["Salominao"] = { latitude = 42.088135, longitude = 42.719745, display_name = "Salominao"},
    ["Salieti"] = { latitude = 42.271758, longitude = 43.209060, display_name = "Salieti"},
    ["Salhino"] = { latitude = 43.486466, longitude = 40.054114, display_name = "Salhino"},
    ["Salhino"] = { latitude = 42.515468, longitude = 42.341970, display_name = "Salhino"},
    ["Sal'me"] = { latitude = 43.427666, longitude = 40.024911, display_name = "Sal'me"},
    ["Sakulia"] = { latitude = 42.134432, longitude = 42.561555, display_name = "Sakulia"},
    ["Sakraula"] = { latitude = 42.031286, longitude = 42.984628, display_name = "Sakraula"},
    ["Saketsia"] = { latitude = 42.529619, longitude = 43.087889, display_name = "Saketsia"},
    ["Sairhe"] = { latitude = 42.313435, longitude = 43.408683, display_name = "Sairhe"},
    ["Sagvichio"] = { latitude = 42.213265, longitude = 41.874584, display_name = "Sagvichio"},
    ["Sagvamichavo"] = { latitude = 42.228483, longitude = 41.827094, display_name = "Sagvamichavo"},
    ["Saeliavo"] = { latitude = 42.437401, longitude = 42.387708, display_name = "Saeliavo"},
    ["Sadovyy"] = { latitude = 45.319973, longitude = 38.053711, display_name = "Sadovyy"},
    ["Sadovyy"] = { latitude = 44.227462, longitude = 43.195699, display_name = "Sadovyy"},
    ["Sadovyy"] = { latitude = 45.018190, longitude = 37.747990, display_name = "Sadovyy"},
    ["Sadovoe"] = { latitude = 45.003356, longitude = 39.693078, display_name = "Sadovoe"},
    ["Sadovoe"] = { latitude = 44.332447, longitude = 42.038087, display_name = "Sadovoe"},
    ["Sadovoe"] = { latitude = 44.018527, longitude = 42.972176, display_name = "Sadovoe"},
    ["Sadmeli"] = { latitude = 42.540139, longitude = 43.114993, display_name = "Sadmeli"},
    ["Sachochuo"] = { latitude = 42.209857, longitude = 41.766596, display_name = "Sachochuo"},
    ["Sachino"] = { latitude = 42.647290, longitude = 42.083729, display_name = "Sachino"},
    ["Sabuliskerio"] = { latitude = 42.675257, longitude = 41.675785, display_name = "Sabuliskerio"},
    ["Saberio"] = { latitude = 42.645920, longitude = 41.907494, display_name = "Saberio"},
    ["Sabe"] = { latitude = 42.046961, longitude = 43.251094, display_name = "Sabe"},
    ["Sabazho"] = { latitude = 42.222720, longitude = 41.798622, display_name = "Sabazho"},
    ["Ryazanskaya"] = { latitude = 44.961990, longitude = 39.578809, display_name = "Ryazanskaya"},
    ["Russkoe"] = { latitude = 44.961940, longitude = 37.836869, display_name = "Russkoe"},
    ["Russkaya Mamayka"] = { latitude = 43.650247, longitude = 39.714431, display_name = "Russkaya Mamayka"},
    ["Ruhi"] = { latitude = 42.544316, longitude = 41.847535, display_name = "Ruhi"},
    ["Rtshilati"] = { latitude = 42.325175, longitude = 43.134800, display_name = "Rtshilati"},
    ["Roschinskiy"] = { latitude = 44.383885, longitude = 42.148785, display_name = "Roschinskiy"},
    ["Rohi"] = { latitude = 42.107479, longitude = 42.706407, display_name = "Rohi"},
    ["Rodnikovyy"] = { latitude = 44.683532, longitude = 39.999878, display_name = "Rodnikovyy"},
    ["Rodniki"] = { latitude = 44.740312, longitude = 39.912532, display_name = "Rodniki"},
    ["Rodinauli"] = { latitude = 42.150130, longitude = 42.871330, display_name = "Rodinauli"},
    ["Rioni"] = { latitude = 42.332956, longitude = 42.714852, display_name = "Rioni"},
    ["Rim-Gorskiy"] = { latitude = 43.958096, longitude = 42.524108, display_name = "Rim-Gorskiy"},
    ["Rike"] = { latitude = 42.588840, longitude = 41.890551, display_name = "Rike"},
    ["Repo-Etseri"] = { latitude = 42.631793, longitude = 41.658992, display_name = "Repo-Etseri"},
    ["Rechkhi"] = { latitude = 42.659807, longitude = 41.747882, display_name = "Rechkhi"},
    ["Razdol'noe"] = { latitude = 43.592801, longitude = 39.798031, display_name = "Razdol'noe"},
    ["Rassvet"] = { latitude = 44.898001, longitude = 37.454519, display_name = "Rassvet"},
    ["Raevskaya"] = { latitude = 44.835329, longitude = 37.559541, display_name = "Raevskaya"},
    ["Pyatihatki"] = { latitude = 44.976064, longitude = 37.304959, display_name = "Pyatihatki"},
    ["Pyatigorskiy"] = { latitude = 43.974984, longitude = 43.261010, display_name = "Pyatigorskiy"},
    ["Psyzh"] = { latitude = 44.241128, longitude = 42.022016, display_name = "Psyzh"},
    ["Psyrtsha"] = { latitude = 43.087430, longitude = 40.879180, display_name = "Psyrtsha"},
    ["Psynshoko"] = { latitude = 43.758797, longitude = 43.716771, display_name = "Psynshoko"},
    ["Psynodaha"] = { latitude = 43.861800, longitude = 43.244666, display_name = "Psynodaha"},
    ["Psyhurey"] = { latitude = 43.838650, longitude = 43.577387, display_name = "Psyhurey"},
    ["Psygansu"] = { latitude = 43.415515, longitude = 43.789113, display_name = "Psygansu"},
    ["Psychoh"] = { latitude = 43.718672, longitude = 43.525482, display_name = "Psychoh"},
    ["Pshizov"] = { latitude = 45.095471, longitude = 40.104028, display_name = "Pshizov"},
    ["Pshicho"] = { latitude = 45.066198, longitude = 40.173833, display_name = "Pshicho"},
    ["Pshehskaya"] = { latitude = 44.698485, longitude = 39.790475, display_name = "Pshehskaya"},
    ["Pshap"] = { latitude = 42.900098, longitude = 41.103230, display_name = "Pshap"},
    ["Pseytuh"] = { latitude = 45.053705, longitude = 38.704020, display_name = "Pseytuh"},
    ["Psemen"] = { latitude = 43.983633, longitude = 40.975741, display_name = "Psemen"},
    ["Psekups"] = { latitude = 44.835444, longitude = 39.207383, display_name = "Psekups"},
    ["Psauch'e-Dahe"] = { latitude = 44.215163, longitude = 41.879390, display_name = "Psauch'e-Dahe"},
    ["Protichka"] = { latitude = 45.378976, longitude = 38.070425, display_name = "Protichka"},
    ["Proletarskiy"] = { latitude = 44.619952, longitude = 40.181767, display_name = "Proletarskiy"},
    ["Progress"] = { latitude = 43.818331, longitude = 43.335369, display_name = "Progress"},
    ["Progress"] = { latitude = 44.871644, longitude = 40.189500, display_name = "Progress"},
    ["Privol'noe"] = { latitude = 44.006071, longitude = 42.941312, display_name = "Privol'noe"},
    ["Prirechnoe"] = { latitude = 43.803274, longitude = 43.305037, display_name = "Prirechnoe"},
    ["Prirechenskiy"] = { latitude = 44.755341, longitude = 39.241202, display_name = "Prirechenskiy"},
    ["Primorskoe"] = { latitude = 43.094709, longitude = 40.704462, display_name = "Primorskoe"},
    ["Primorskiy"] = { latitude = 45.264384, longitude = 36.909279, display_name = "Primorskiy"},
    ["Prikubanskiy"] = { latitude = 44.958409, longitude = 39.027413, display_name = "Prikubanskiy"},
    ["Prikubanskie hutora"] = { latitude = 45.161087, longitude = 38.072122, display_name = "Prikubanskie hutora"},
    ["Prigorodnyy"] = { latitude = 44.124347, longitude = 39.118629, display_name = "Prigorodnyy"},
    ["Prigorodnoe"] = { latitude = 44.238742, longitude = 42.120034, display_name = "Prigorodnoe"},
    ["Preobrazhenskoe"] = { latitude = 45.087444, longitude = 39.623111, display_name = "Preobrazhenskoe"},
    ["Pregradnaya"] = { latitude = 43.951487, longitude = 41.184826, display_name = "Pregradnaya"},
    ["Pravokubanskiy"] = { latitude = 43.919953, longitude = 41.883161, display_name = "Pravokubanskiy"},
    ["Potsho"] = { latitude = 42.432617, longitude = 42.167673, display_name = "Potsho"},
    ["Ponezhukay"] = { latitude = 44.891919, longitude = 39.381016, display_name = "Ponezhukay"},
    ["Pokvesh"] = { latitude = 42.797212, longitude = 41.568194, display_name = "Pokvesh"},
    ["Pokrovskiy"] = { latitude = 45.111818, longitude = 38.415735, display_name = "Pokrovskiy"},
    ["Podkumok"] = { latitude = 43.970835, longitude = 42.775149, display_name = "Podkumok"},
    ["Podgornyy"] = { latitude = 44.692614, longitude = 40.083808, display_name = "Podgornyy"},
    ["Podgornaya"] = { latitude = 44.214940, longitude = 41.283165, display_name = "Podgornaya"},
    ["Pobegaylovka"] = { latitude = 44.241492, longitude = 43.014814, display_name = "Pobegaylovka"},
    ["Plavnenskiy"] = { latitude = 45.036542, longitude = 37.930622, display_name = "Plavnenskiy"},
    ["Plastunovskaya"] = { latitude = 45.296667, longitude = 39.265353, display_name = "Plastunovskaya"},
    ["Plastunka"] = { latitude = 43.671607, longitude = 39.761316, display_name = "Plastunka"},
    ["Pitsargali"] = { latitude = 42.533189, longitude = 41.596891, display_name = "Pitsargali"},
    ["Pirveli-Tola"] = { latitude = 42.573555, longitude = 42.984611, display_name = "Pirveli-Tola"},
    ["Pirveli-Sviri"] = { latitude = 42.106504, longitude = 42.960607, display_name = "Pirveli-Sviri"},
    ["Pirveli-Ontopo"] = { latitude = 42.250454, longitude = 42.246871, display_name = "Pirveli-Ontopo"},
    ["Pirveli-Obcha"] = { latitude = 42.097058, longitude = 42.857916, display_name = "Pirveli-Obcha"},
    ["Pirveli-Maisi"] = { latitude = 42.101253, longitude = 43.085303, display_name = "Pirveli-Maisi"},
    ["Pirveli-Gurdzemi"] = { latitude = 42.469479, longitude = 42.265483, display_name = "Pirveli-Gurdzemi"},
    ["Pirveli-Gudava"] = { latitude = 42.662997, longitude = 41.558282, display_name = "Pirveli-Gudava"},
    ["Pirveli-Etseri"] = { latitude = 42.183070, longitude = 42.158404, display_name = "Pirveli-Etseri"},
    ["Pirveli-Choga"] = { latitude = 42.574126, longitude = 42.198176, display_name = "Pirveli-Choga"},
    ["Pirveli-Akvaga"] = { latitude = 42.602194, longitude = 41.772756, display_name = "Pirveli-Akvaga"},
    ["Pervorechenskoe"] = { latitude = 45.164385, longitude = 39.308994, display_name = "Pervorechenskoe"},
    ["Pervomayskiy"] = { latitude = 45.180051, longitude = 38.305376, display_name = "Pervomayskiy"},
    ["Pervomayskiy"] = { latitude = 44.897833, longitude = 39.739162, display_name = "Pervomayskiy"},
    ["Pervomayskiy"] = { latitude = 44.694004, longitude = 39.306035, display_name = "Pervomayskiy"},
    ["Pervomayskiy"] = { latitude = 44.408531, longitude = 40.186120, display_name = "Pervomayskiy"},
    ["Perveli-Ohurey"] = { latitude = 42.736365, longitude = 41.575883, display_name = "Perveli-Ohurey"},
    ["Persati"] = { latitude = 42.068078, longitude = 42.788478, display_name = "Persati"},
    ["Perevisa"] = { latitude = 42.260370, longitude = 43.294406, display_name = "Perevisa"},
    ["Perevalka"] = { latitude = 44.053160, longitude = 40.756944, display_name = "Perevalka"},
    ["Pereta"] = { latitude = 42.062818, longitude = 42.715368, display_name = "Pereta"},
    ["Peredovaya"] = { latitude = 44.119929, longitude = 41.476287, display_name = "Peredovaya"},
    ["Pchegatlukay"] = { latitude = 44.888976, longitude = 39.262451, display_name = "Pchegatlukay"},
    ["Pavlovskoe"] = { latitude = 43.067519, longitude = 41.114584, display_name = "Pavlovskoe"},
    ["Pavlovskiy"] = { latitude = 45.078137, longitude = 37.772047, display_name = "Pavlovskiy"},
    ["Patriketi"] = { latitude = 42.156221, longitude = 42.656848, display_name = "Patriketi"},
    ["Patara-Poti"] = { latitude = 42.190478, longitude = 41.731759, display_name = "Patara-Poti"},
    ["Patara-Oni"] = { latitude = 42.535801, longitude = 42.981056, display_name = "Patara-Oni"},
    ["Patara-Dzhihaishi"] = { latitude = 42.284101, longitude = 42.404035, display_name = "Patara-Dzhihaishi"},
    ["Partshnali"] = { latitude = 42.007781, longitude = 43.124335, display_name = "Partshnali"},
    ["Partshanakanevi"] = { latitude = 42.207220, longitude = 42.557555, display_name = "Partshanakanevi"},
    ["Partonohori"] = { latitude = 42.651198, longitude = 41.861593, display_name = "Partonohori"},
    ["Paraheti"] = { latitude = 42.553093, longitude = 43.320122, display_name = "Paraheti"},
    ["Panahes"] = { latitude = 44.987973, longitude = 38.713089, display_name = "Panahes"},
    ["Pahulani"] = { latitude = 42.654425, longitude = 41.989393, display_name = "Pahulani"},
    ["Otradnyy"] = { latitude = 44.874473, longitude = 38.952471, display_name = "Otradnyy"},
    ["Otradnoe"] = { latitude = 43.265661, longitude = 40.288506, display_name = "Otradnoe"},
    ["otd.N3 SKZNIISiV"] = { latitude = 45.136100, longitude = 38.965099, display_name = "otd.N3 SKZNIISiV"},
    ["Ostrovskaya Shel'"] = { latitude = 44.278869, longitude = 39.291550, display_name = "Ostrovskaya Shel'"},
    ["Ostrogorka"] = { latitude = 44.123258, longitude = 42.979051, display_name = "Ostrogorka"},
    ["Orsantia"] = { latitude = 42.470203, longitude = 41.669408, display_name = "Orsantia"},
    ["Orpiri"] = { latitude = 42.332106, longitude = 42.817570, display_name = "Orpiri"},
    ["Orlovka"] = { latitude = 43.990864, longitude = 43.777097, display_name = "Orlovka"},
    ["Orka"] = { latitude = 42.308786, longitude = 42.246602, display_name = "Orka"},
    ["Orhvi"] = { latitude = 42.506220, longitude = 42.801449, display_name = "Orhvi"},
    ["Orel "] = { latitude = 43.461316, longitude = 39.920078, display_name = "Orel "},
    ["Ordzhonikidze"] = { latitude = 42.640714, longitude = 42.025758, display_name = "Ordzhonikidze"},
    ["Orbeli"] = { latitude = 42.636184, longitude = 42.822917, display_name = "Orbeli"},
    ["Orbel'yanovka"] = { latitude = 44.232740, longitude = 42.878746, display_name = "Orbel'yanovka"},
    ["Opshkviti"] = { latitude = 42.147389, longitude = 42.604859, display_name = "Opshkviti"},
    ["Opachkhapu"] = { latitude = 42.448393, longitude = 41.903878, display_name = "Opachkhapu"},
    ["Ondzhoheti"] = { latitude = 42.032045, longitude = 42.507565, display_name = "Ondzhoheti"},
    ["Ol'ginskiy"] = { latitude = 45.126611, longitude = 38.359099, display_name = "Ol'ginskiy"},
    ["Okureshi"] = { latitude = 42.543283, longitude = 42.672827, display_name = "Okureshi"},
    ["Oktyabr'skoe"] = { latitude = 43.889818, longitude = 43.176085, display_name = "Oktyabr'skoe"},
    ["Oktyabr'skiy"] = { latitude = 44.849738, longitude = 38.471831, display_name = "Oktyabr'skiy"},
    ["Oktyabr'skiy"] = { latitude = 45.236696, longitude = 38.290841, display_name = "Oktyabr'skiy"},
    ["Oktyabr'skiy"] = { latitude = 44.253774, longitude = 42.486886, display_name = "Oktyabr'skiy"},
    ["Oktyabr'skiy"] = { latitude = 44.322918, longitude = 39.339558, display_name = "Oktyabr'skiy"},
    ["Oktyabr'skaya"] = { latitude = 44.854025, longitude = 39.617850, display_name = "Oktyabr'skaya"},
    ["Oktomberi"] = { latitude = 42.208187, longitude = 42.430870, display_name = "Oktomberi"},
    ["Oktomberi"] = { latitude = 42.443933, longitude = 41.743717, display_name = "Oktomberi"},
    ["Oktomberi"] = { latitude = 43.030969, longitude = 41.229984, display_name = "Oktomberi"},
    ["Oireme"] = { latitude = 42.467632, longitude = 41.808728, display_name = "Oireme"},
    ["Ohvamekari"] = { latitude = 42.369398, longitude = 41.833464, display_name = "Ohvamekari"},
    ["Odzhola"] = { latitude = 42.388595, longitude = 42.785896, display_name = "Odzhola"},
    ["Odishi"] = { latitude = 43.075147, longitude = 41.091600, display_name = "Odishi"},
    ["Odishi"] = { latitude = 42.526251, longitude = 41.923614, display_name = "Odishi"},
    ["Ochkhomuri"] = { latitude = 42.471584, longitude = 42.079849, display_name = "Ochkhomuri"},
    ["Ochkhamuri"] = { latitude = 41.856720, longitude = 41.833105, display_name = "Ochkhamuri"},
    ["Oche"] = { latitude = 42.524586, longitude = 42.313209, display_name = "Oche"},
    ["Obudzhi"] = { latitude = 42.556017, longitude = 42.015172, display_name = "Obudzhi"},
    ["Obil'noe"] = { latitude = 44.245620, longitude = 43.557820, display_name = "Obil'noe"},
    ["Novyy Sad"] = { latitude = 44.907033, longitude = 38.908686, display_name = "Novyy Sad"},
    ["Novyy Karachay "] = { latitude = 43.819752, longitude = 41.904403, display_name = "Novyy Karachay "},
    ["Novyy"] = { latitude = 45.007971, longitude = 38.979031, display_name = "Novyy"},
    ["Novyy"] = { latitude = 44.151786, longitude = 43.439254, display_name = "Novyy"},
    ["Novyy"] = { latitude = 44.935888, longitude = 40.166760, display_name = "Novyy"},
    ["Novyy"] = { latitude = 44.726360, longitude = 39.824853, display_name = "Novyy"},
    ["Novye Polyany"] = { latitude = 44.302384, longitude = 39.825372, display_name = "Novye Polyany"},
    ["Novozavedennoe"] = { latitude = 44.265326, longitude = 43.639520, display_name = "Novozavedennoe"},
    ["Novovelichkovskaya"] = { latitude = 45.282561, longitude = 38.846433, display_name = "Novovelichkovskaya"},
    ["Novoukrainskiy"] = { latitude = 44.897714, longitude = 38.047940, display_name = "Novoukrainskiy"},
    ["Novotroitskiy"] = { latitude = 45.031839, longitude = 38.007409, display_name = "Novotroitskiy"},
    ["Novotitarovskaya"] = { latitude = 45.243806, longitude = 38.981038, display_name = "Novotitarovskaya"},
    ["Novoterskiy"] = { latitude = 44.150120, longitude = 43.092246, display_name = "Novoterskiy"},
    ["Novosrednenskoe"] = { latitude = 44.094686, longitude = 43.823821, display_name = "Novosrednenskoe"},
    ["Novosevastopol'skoe"] = { latitude = 45.063035, longitude = 39.704568, display_name = "Novosevastopol'skoe"},
    ["Novomyshastovskaya"] = { latitude = 45.201414, longitude = 38.574925, display_name = "Novomyshastovskaya"},
    ["Novolabinskaya"] = { latitude = 45.114755, longitude = 39.900432, display_name = "Novolabinskaya"},
    ["Novoispravnenskoe"] = { latitude = 43.979804, longitude = 41.542656, display_name = "Novoispravnenskoe"},
    ["Novodmitrievskaya"] = { latitude = 44.834141, longitude = 38.877951, display_name = "Novodmitrievskaya"},
    ["Novobzhegokay"] = { latitude = 44.934656, longitude = 38.837614, display_name = "Novobzhegokay"},
    ["Novoblagodarnoe"] = { latitude = 44.144876, longitude = 42.878900, display_name = "Novoblagodarnoe"},
    ["Novoalekseevskoe"] = { latitude = 44.957243, longitude = 39.860670, display_name = "Novoalekseevskoe"},
    ["Novaya Akvaskia"] = { latitude = 42.794629, longitude = 41.540276, display_name = "Novaya Akvaskia"},
    ["Novaya Adygeya"] = { latitude = 45.028796, longitude = 38.933988, display_name = "Novaya Adygeya"},
    ["Nov.Teberda "] = { latitude = 43.673906, longitude = 41.894978, display_name = "Nov.Teberda "},
    ["Nov.Dzheguta"] = { latitude = 43.996395, longitude = 42.048760, display_name = "Nov.Dzheguta"},
    ["Nosiri"] = { latitude = 42.275005, longitude = 42.139716, display_name = "Nosiri"},
    ["Noga"] = { latitude = 42.474952, longitude = 42.208261, display_name = "Noga"},
    ["Nizhnyaya Gostagayka"] = { latitude = 45.040575, longitude = 37.349826, display_name = "Nizhnyaya Gostagayka"},
    ["Nizhnezol'skiy"] = { latitude = 44.120358, longitude = 43.639111, display_name = "Nizhnezol'skiy"},
    ["Nizhnepodkumskiy"] = { latitude = 44.078935, longitude = 43.212710, display_name = "Nizhnepodkumskiy"},
    ["Nizhne-Vysokoe"] = { latitude = 43.474346, longitude = 39.972717, display_name = "Nizhne-Vysokoe"},
    ["Nizh.Teberda"] = { latitude = 43.638667, longitude = 41.872969, display_name = "Nizh.Teberda"},
    ["Nizh.Shilovka"] = { latitude = 43.463994, longitude = 40.023461, display_name = "Nizh.Shilovka"},
    ["Nizh.Kurkuzhin"] = { latitude = 43.752549, longitude = 43.362675, display_name = "Nizh.Kurkuzhin"},
    ["Nizh.Ermolovka"] = { latitude = 43.750379, longitude = 41.510216, display_name = "Nizh.Ermolovka"},
    ["Nizh.Chegem"] = { latitude = 43.498349, longitude = 43.296418, display_name = "Nizh.Chegem"},
    ["Nizh.Arhyz"] = { latitude = 43.680448, longitude = 41.458998, display_name = "Nizh.Arhyz"},
    ["Nizh. Mtsara "] = { latitude = 43.141038, longitude = 40.761450, display_name = "Nizh. Mtsara "},
    ["Nizh. Armyanskoe Uschel'e"] = { latitude = 43.084525, longitude = 40.794689, display_name = "Nizh. Armyanskoe Uschel'e"},
    ["Ninoshvili"] = { latitude = 42.042257, longitude = 41.948968, display_name = "Ninoshvili"},
    ["Nikortsminda"] = { latitude = 42.464696, longitude = 43.097630, display_name = "Nikortsminda"},
    ["Nikolaevskoe"] = { latitude = 44.122613, longitude = 42.129337, display_name = "Nikolaevskoe"},
    ["Nikolaenko"] = { latitude = 44.409057, longitude = 39.673508, display_name = "Nikolaenko"},
    ["Nigvziani"] = { latitude = 42.067004, longitude = 41.879237, display_name = "Nigvziani"},
    ["Nigvzara"] = { latitude = 42.213600, longitude = 43.465632, display_name = "Nigvzara"},
    ["Niabauri"] = { latitude = 41.868343, longitude = 42.013388, display_name = "Niabauri"},
    ["Nezlobnaya"] = { latitude = 44.118949, longitude = 43.410519, display_name = "Nezlobnaya"},
    ["Nezhinskiy"] = { latitude = 43.931930, longitude = 42.685571, display_name = "Nezhinskiy"},
    ["Neshukay"] = { latitude = 44.910399, longitude = 39.416240, display_name = "Neshukay"},
    ["Nergeeti"] = { latitude = 42.054518, longitude = 42.823643, display_name = "Nergeeti"},
    ["Nekrasovskaya"] = { latitude = 45.148939, longitude = 39.755071, display_name = "Nekrasovskaya"},
    ["Neftyanaya"] = { latitude = 44.377554, longitude = 39.643419, display_name = "Neftyanaya"},
    ["Neftegorsk"] = { latitude = 44.366015, longitude = 39.713219, display_name = "Neftegorsk"},
    ["Nebug"] = { latitude = 44.171632, longitude = 38.998327, display_name = "Nebug"},
    ["Nebodziri"] = { latitude = 41.988354, longitude = 43.370345, display_name = "Nebodziri"},
    ["Neberdzhaevskaya"] = { latitude = 44.828654, longitude = 37.893678, display_name = "Neberdzhaevskaya"},
    ["Navenahevi"] = { latitude = 42.250740, longitude = 42.859437, display_name = "Navenahevi"},
    ["Navaginka"] = { latitude = 43.618130, longitude = 39.745794, display_name = "Navaginka"},
    ["Natuhaevskaya"] = { latitude = 44.911294, longitude = 37.567604, display_name = "Natuhaevskaya"},
    ["Natsatu"] = { latitude = 42.567541, longitude = 41.979564, display_name = "Natsatu"},
    ["Nasperi"] = { latitude = 42.590925, longitude = 42.770482, display_name = "Nasperi"},
    ["Nartan"] = { latitude = 43.511529, longitude = 43.704153, display_name = "Nartan"},
    ["Narazeni"] = { latitude = 42.393980, longitude = 41.913391, display_name = "Narazeni"},
    ["Naposhtu"] = { latitude = 42.326551, longitude = 41.933522, display_name = "Naposhtu"},
    ["Naochi"] = { latitude = 42.776851, longitude = 41.352702, display_name = "Naochi"},
    ["Namohvani"] = { latitude = 42.421347, longitude = 42.697503, display_name = "Namohvani"},
    ["Namikolavo"] = { latitude = 42.474821, longitude = 42.329612, display_name = "Namikolavo"},
    ["Nalepsao"] = { latitude = 42.434450, longitude = 42.365644, display_name = "Nalepsao"},
    ["Nakuraleshi"] = { latitude = 42.552601, longitude = 42.746391, display_name = "Nakuraleshi"},
    ["Nakipu"] = { latitude = 42.562322, longitude = 42.073330, display_name = "Nakipu"},
    ["Nahurtsilavo"] = { latitude = 42.446389, longitude = 42.263325, display_name = "Nahurtsilavo"},
    ["Nahunao"] = { latitude = 42.430549, longitude = 42.323061, display_name = "Nahunao"},
    ["Nahshirgele"] = { latitude = 42.224265, longitude = 42.824321, display_name = "Nahshirgele"},
    ["Nahahulevi"] = { latitude = 42.359411, longitude = 42.438140, display_name = "Nahahulevi"},
    ["Naguru"] = { latitude = 42.694093, longitude = 42.097908, display_name = "Naguru"},
    ["Nagomari"] = { latitude = 41.990544, longitude = 42.112949, display_name = "Nagomari"},
    ["Nageberavo"] = { latitude = 42.332468, longitude = 42.314506, display_name = "Nageberavo"},
    ["Naesakao"] = { latitude = 42.178447, longitude = 42.209197, display_name = "Naesakao"},
    ["Nadezhnaya"] = { latitude = 44.207001, longitude = 41.404721, display_name = "Nadezhnaya"},
    ["Nadaburi"] = { latitude = 42.126553, longitude = 43.427983, display_name = "Nadaburi"},
    ["Nabakevi"] = { latitude = 42.503987, longitude = 41.662352, display_name = "Nabakevi"},
    ["Mziani"] = { latitude = 41.976759, longitude = 42.151579, display_name = "Mziani"},
    ["Myshako"] = { latitude = 44.667140, longitude = 37.759021, display_name = "Myshako"},
    ["Muhuri"] = { latitude = 42.634638, longitude = 42.176056, display_name = "Muhuri"},
    ["Muhuri"] = { latitude = 42.689556, longitude = 41.697291, display_name = "Muhuri"},
    ["Muhura"] = { latitude = 42.326650, longitude = 43.087842, display_name = "Muhura"},
    ["Mtsvane-Kontshi"] = { latitude = 41.689072, longitude = 41.709726, display_name = "Mtsvane-Kontshi"},
    ["Mtispiri"] = { latitude = 41.941767, longitude = 42.157494, display_name = "Mtispiri"},
    ["Morzoh"] = { latitude = 43.567536, longitude = 43.840579, display_name = "Morzoh"},
    ["Mongiri"] = { latitude = 42.490006, longitude = 42.152069, display_name = "Mongiri"},
    ["Molodezhnyy"] = { latitude = 44.680002, longitude = 39.675713, display_name = "Molodezhnyy"},
    ["Moldovka"] = { latitude = 43.454585, longitude = 39.940029, display_name = "Moldovka"},
    ["Moldavanskoe"] = { latitude = 44.946939, longitude = 37.868979, display_name = "Moldavanskoe"},
    ["Moidanahe"] = { latitude = 42.545104, longitude = 42.138447, display_name = "Moidanahe"},
    ["Mohva"] = { latitude = 42.406014, longitude = 43.331100, display_name = "Mohva"},
    ["Mohashi"] = { latitude = 42.415311, longitude = 42.190616, display_name = "Mohashi"},
    ["Mogukorovskiy"] = { latitude = 45.156215, longitude = 38.198283, display_name = "Mogukorovskiy"},
    ["Mogiri"] = { latitude = 42.374700, longitude = 41.729160, display_name = "Mogiri"},
    ["Modzvi"] = { latitude = 42.262543, longitude = 43.423399, display_name = "Modzvi"},
    ["Mitsatsiteli"] = { latitude = 42.237341, longitude = 42.538850, display_name = "Mitsatsiteli"},
    ["Mirnyy"] = { latitude = 44.608512, longitude = 39.008222, display_name = "Mirnyy"},
    ["Mingrel'skaya"] = { latitude = 45.014629, longitude = 38.338356, display_name = "Mingrel'skaya"},
    ["Mikava"] = { latitude = 42.622078, longitude = 42.105739, display_name = "Mikava"},
    ["Mihaylovskoe"] = { latitude = 45.010440, longitude = 38.505502, display_name = "Mihaylovskoe"},
    ["Mihaylovskiy Pereval"] = { latitude = 44.515937, longitude = 38.308302, display_name = "Mihaylovskiy Pereval"},
    ["Mihaylovka"] = { latitude = 44.220290, longitude = 43.715318, display_name = "Mihaylovka"},
    ["Mhkhiani"] = { latitude = 42.198488, longitude = 42.599261, display_name = "Mhkhiani"},
    ["Mgvimevi"] = { latitude = 42.324557, longitude = 43.325751, display_name = "Mgvimevi"},
    ["Mezmay"] = { latitude = 44.201951, longitude = 39.954704, display_name = "Mezmay"},
    ["Messazhay"] = { latitude = 44.141813, longitude = 39.118127, display_name = "Messazhay"},
    ["Meria"] = { latitude = 41.944845, longitude = 41.895503, display_name = "Meria"},
    ["Merheuli"] = { latitude = 42.987272, longitude = 41.159733, display_name = "Merheuli"},
    ["Merdzhevi"] = { latitude = 42.306727, longitude = 43.431058, display_name = "Merdzhevi"},
    ["Merchanskoe"] = { latitude = 44.953938, longitude = 38.129010, display_name = "Merchanskoe"},
    ["Meore-Tola"] = { latitude = 42.590570, longitude = 43.004131, display_name = "Meore-Tola"},
    ["Meore-Sviri"] = { latitude = 42.109908, longitude = 42.927318, display_name = "Meore-Sviri"},
    ["Meore-Otobaya"] = { latitude = 42.454627, longitude = 41.646351, display_name = "Meore-Otobaya"},
    ["Meore-Obcha"] = { latitude = 42.107171, longitude = 42.887676, display_name = "Meore-Obcha"},
    ["Meore-Mohashi"] = { latitude = 42.391827, longitude = 42.155776, display_name = "Meore-Mohashi"},
    ["Meore-Gudava"] = { latitude = 42.643386, longitude = 41.544281, display_name = "Meore-Gudava"},
    ["Meore-Choga"] = { latitude = 42.553676, longitude = 42.195803, display_name = "Meore-Choga"},
    ["Meore-Balda"] = { latitude = 42.499520, longitude = 42.391396, display_name = "Meore-Balda"},
    ["Meore Guripuli"] = { latitude = 42.293696, longitude = 41.890921, display_name = "Meore Guripuli"},
    ["Melauri"] = { latitude = 42.190643, longitude = 42.384720, display_name = "Melauri"},
    ["Medzhinistskali"] = { latitude = 41.591304, longitude = 41.634544, display_name = "Medzhinistskali"},
    ["Mednogorskiy"] = { latitude = 43.915956, longitude = 41.186234, display_name = "Mednogorskiy"},
    ["Medani"] = { latitude = 42.672074, longitude = 42.137770, display_name = "Medani"},
    ["Mechkheturi"] = { latitude = 42.156947, longitude = 43.357366, display_name = "Mechkheturi"},
    ["Mazandara"] = { latitude = 42.619401, longitude = 42.045884, display_name = "Mazandara"},
    ["Mayskiy"] = { latitude = 44.304185, longitude = 42.414766, display_name = "Mayskiy"},
    ["Mathodzhi"] = { latitude = 42.387197, longitude = 42.442078, display_name = "Mathodzhi"},
    ["Maruha"] = { latitude = 43.765166, longitude = 41.633133, display_name = "Maruha"},
    ["Martotubani"] = { latitude = 42.129811, longitude = 43.099587, display_name = "Martotubani"},
    ["Martanskaya"] = { latitude = 44.762337, longitude = 39.437253, display_name = "Martanskaya"},
    ["Marelisi"] = { latitude = 41.957941, longitude = 43.275197, display_name = "Marelisi"},
    ["Marani"] = { latitude = 42.167287, longitude = 42.279974, display_name = "Marani"},
    ["Mar'yanskaya"] = { latitude = 45.105603, longitude = 38.639941, display_name = "Mar'yanskaya"},
    ["Mar'inskaya"] = { latitude = 43.883397, longitude = 43.485856, display_name = "Mar'inskaya"},
    ["Mar'ina Roscha"] = { latitude = 44.622675, longitude = 38.028581, display_name = "Mar'ina Roscha"},
    ["Mandaeti"] = { latitude = 42.183777, longitude = 43.335150, display_name = "Mandaeti"},
    ["Mamheg"] = { latitude = 45.014362, longitude = 40.218094, display_name = "Mamheg"},
    ["Mamayka"] = { latitude = 43.635005, longitude = 39.702864, display_name = "Mamayka"},
    ["Malotenginskaya"] = { latitude = 44.281730, longitude = 41.526014, display_name = "Malotenginskaya"},
    ["Malokurgannyy"] = { latitude = 43.844057, longitude = 41.906286, display_name = "Malokurgannyy"},
    ["Malka"] = { latitude = 43.800714, longitude = 43.326704, display_name = "Malka"},
    ["Mal.Zelenchuk"] = { latitude = 44.160376, longitude = 41.864663, display_name = "Mal.Zelenchuk"},
    ["Makopse"] = { latitude = 43.994893, longitude = 39.213327, display_name = "Makopse"},
    ["Makatubani"] = { latitude = 42.124781, longitude = 43.243436, display_name = "Makatubani"},
    ["Maidani"] = { latitude = 42.284090, longitude = 42.312674, display_name = "Maidani"},
    ["Mahatauri"] = { latitude = 42.288011, longitude = 43.463631, display_name = "Mahatauri"},
    ["Mahashi"] = { latitude = 42.606742, longitude = 42.749978, display_name = "Mahashi"},
    ["Maharadze"] = { latitude = 41.935696, longitude = 41.974045, display_name = "Maharadze"},
    ["Maglaki"] = { latitude = 42.261039, longitude = 42.565289, display_name = "Maglaki"},
    ["Maevskiy"] = { latitude = 45.172039, longitude = 38.161598, display_name = "Maevskiy"},
    ["Machkhvareti"] = { latitude = 42.068987, longitude = 42.008697, display_name = "Machkhvareti"},
    ["Lysogorskaya"] = { latitude = 44.106014, longitude = 43.281068, display_name = "Lysogorskaya"},
    ["Lunacharskiy"] = { latitude = 43.905269, longitude = 42.690928, display_name = "Lunacharskiy"},
    ["Loo"] = { latitude = 43.705439, longitude = 39.587153, display_name = "Loo"},
    ["Lineynaya"] = { latitude = 44.594594, longitude = 39.487620, display_name = "Lineynaya"},
    ["Liheti"] = { latitude = 42.608896, longitude = 43.238178, display_name = "Liheti"},
    ["Lidzava"] = { latitude = 43.178993, longitude = 40.363041, display_name = "Lidzava"},
    ["Lia"] = { latitude = 42.636237, longitude = 41.988812, display_name = "Lia"},
    ["Levokumka"] = { latitude = 44.234481, longitude = 43.139161, display_name = "Levokumka"},
    ["Letsurtsume"] = { latitude = 42.533360, longitude = 42.119200, display_name = "Letsurtsume"},
    ["Lesogorskaya"] = { latitude = 44.547683, longitude = 39.536450, display_name = "Lesogorskaya"},
    ["Leso-Kefar' "] = { latitude = 43.788871, longitude = 41.449272, display_name = "Leso-Kefar' "},
    ["Lesnoe"] = { latitude = 43.776488, longitude = 43.893822, display_name = "Lesnoe"},
    ["Leselidze"] = { latitude = 43.394992, longitude = 40.031382, display_name = "Leselidze"},
    ["Lesa"] = { latitude = 42.077277, longitude = 41.963379, display_name = "Lesa"},
    ["Lepochkhue"] = { latitude = 42.330917, longitude = 42.247525, display_name = "Lepochkhue"},
    ["Leninskiy"] = { latitude = 45.172744, longitude = 38.234987, display_name = "Leninskiy"},
    ["Leninskiy"] = { latitude = 44.192230, longitude = 43.153106, display_name = "Leninskiy"},
    ["Lemikave"] = { latitude = 42.427247, longitude = 42.241238, display_name = "Lemikave"},
    ["Lekadzhaie"] = { latitude = 42.405106, longitude = 42.292257, display_name = "Lekadzhaie"},
    ["Leharchile"] = { latitude = 42.644704, longitude = 42.136081, display_name = "Leharchile"},
    ["Lehaindravo"] = { latitude = 42.354513, longitude = 42.340358, display_name = "Lehaindravo"},
    ["Legvani"] = { latitude = 41.967060, longitude = 43.250167, display_name = "Legvani"},
    ["Legogie-Nasadzhu"] = { latitude = 42.445929, longitude = 42.182047, display_name = "Legogie-Nasadzhu"},
    ["Ledgebe"] = { latitude = 42.486240, longitude = 42.276690, display_name = "Ledgebe"},
    ["Ledarsale"] = { latitude = 42.590914, longitude = 42.179511, display_name = "Ledarsale"},
    ["Lechkop"] = { latitude = 43.006797, longitude = 40.947857, display_name = "Lechkop"},
    ["Lechinkay"] = { latitude = 43.564141, longitude = 43.434332, display_name = "Lechinkay"},
    ["Lashkuta"] = { latitude = 43.561224, longitude = 43.220160, display_name = "Lashkuta"},
    ["Larchva"] = { latitude = 42.351435, longitude = 41.835304, display_name = "Larchva"},
    ["Lailashi"] = { latitude = 42.611170, longitude = 42.859131, display_name = "Lailashi"},
    ["L'vovskoe"] = { latitude = 44.996451, longitude = 38.629714, display_name = "L'vovskoe"},
    ["Kyzyl-Urup"] = { latitude = 44.007355, longitude = 41.222594, display_name = "Kyzyl-Urup"},
    ["Kyzyl-Pokun"] = { latitude = 43.931009, longitude = 42.322452, display_name = "Kyzyl-Pokun"},
    ["Kyzyl-Oktyabr'skiy"] = { latitude = 43.821981, longitude = 41.784413, display_name = "Kyzyl-Oktyabr'skiy"},
    ["Kyzyl-Kala"] = { latitude = 43.916362, longitude = 42.022165, display_name = "Kyzyl-Kala"},
    ["Kyzburun 3-y"] = { latitude = 43.661503, longitude = 43.539209, display_name = "Kyzburun 3-y"},
    ["Kyzburun 1-y"] = { latitude = 43.649090, longitude = 43.398417, display_name = "Kyzburun 1-y"},
    ["Kvitiri"] = { latitude = 42.240290, longitude = 42.627891, display_name = "Kvitiri"},
    ["Kvishona"] = { latitude = 42.483267, longitude = 41.605369, display_name = "Kvishona"},
    ["Kvishari"] = { latitude = 42.556177, longitude = 42.944007, display_name = "Kvishari"},
    ["Kvilishori"] = { latitude = 42.367595, longitude = 42.633262, display_name = "Kvilishori"},
    ["Kvemo-Natanebi"] = { latitude = 41.940818, longitude = 41.821619, display_name = "Kvemo-Natanebi"},
    ["Kvemo-Nagvazavo"] = { latitude = 42.369523, longitude = 42.351584, display_name = "Kvemo-Nagvazavo"},
    ["Kvemo-Merheuli"] = { latitude = 42.953562, longitude = 41.083145, display_name = "Kvemo-Merheuli"},
    ["Kvemo-Makvaneti"] = { latitude = 41.899174, longitude = 42.004806, display_name = "Kvemo-Makvaneti"},
    ["Kvemo-Linda"] = { latitude = 43.052983, longitude = 41.084562, display_name = "Kvemo-Linda"},
    ["Kvemo-Huntsi"] = { latitude = 42.386495, longitude = 42.408388, display_name = "Kvemo-Huntsi"},
    ["Kvemo-Heti"] = { latitude = 42.036326, longitude = 42.345654, display_name = "Kvemo-Heti"},
    ["Kvemo-Gumurishi"] = { latitude = 42.695293, longitude = 41.779772, display_name = "Kvemo-Gumurishi"},
    ["Kvemo-Chibati"] = { latitude = 42.084840, longitude = 41.984109, display_name = "Kvemo-Chibati"},
    ["Kvemo-Bargebi"] = { latitude = 42.550507, longitude = 41.597211, display_name = "Kvemo-Bargebi"},
    ["Kvemo-Aketi"] = { latitude = 42.018515, longitude = 42.086106, display_name = "Kvemo-Aketi"},
    ["Kvemo-Abasha"] = { latitude = 42.067810, longitude = 42.332917, display_name = "Kvemo-Abasha"},
    ["Kvemo-"] = { latitude = 42.534733, longitude = 43.277237, display_name = "Kvemo-"},
    ["Kveda-Zegani"] = { latitude = 42.053654, longitude = 42.882304, display_name = "Kveda-Zegani"},
    ["Kveda-Tsageri"] = { latitude = 42.636375, longitude = 42.752565, display_name = "Kveda-Tsageri"},
    ["Kveda-Tlugi"] = { latitude = 42.450272, longitude = 43.131812, display_name = "Kveda-Tlugi"},
    ["Kveda-Simoneti"] = { latitude = 42.224997, longitude = 42.857157, display_name = "Kveda-Simoneti"},
    ["Kveda-Mesheti"] = { latitude = 42.212940, longitude = 42.627000, display_name = "Kveda-Mesheti"},
    ["Kveda-Kvaliti"] = { latitude = 42.090228, longitude = 42.983547, display_name = "Kveda-Kvaliti"},
    ["Kveda-Kinchkha"] = { latitude = 42.487021, longitude = 42.561627, display_name = "Kveda-Kinchkha"},
    ["Kveda-Ilemi"] = { latitude = 42.079259, longitude = 43.128506, display_name = "Kveda-Ilemi"},
    ["Kveda-Gvirishi"] = { latitude = 42.579735, longitude = 42.800770, display_name = "Kveda-Gvirishi"},
    ["Kveda-Gordi"] = { latitude = 42.441570, longitude = 42.521019, display_name = "Kveda-Gordi"},
    ["Kveda-Gora"] = { latitude = 42.072625, longitude = 42.664264, display_name = "Kveda-Gora"},
    ["Kveda-Chkhorotsku"] = { latitude = 42.494847, longitude = 42.101203, display_name = "Kveda-Chkhorotsku"},
    ["Kveda-Chelovani"] = { latitude = 42.361088, longitude = 43.275404, display_name = "Kveda-Chelovani"},
    ["Kveda-Bzvani"] = { latitude = 42.076868, longitude = 42.589309, display_name = "Kveda-Bzvani"},
    ["Kveda-Bahvi"] = { latitude = 41.961494, longitude = 42.091267, display_name = "Kveda-Bahvi"},
    ["Kveda-Alisubani"] = { latitude = 42.241205, longitude = 43.093737, display_name = "Kveda-Alisubani"},
    ["Kvatsihe"] = { latitude = 42.291298, longitude = 43.146492, display_name = "Kvatsihe"},
    ["Kvashkhieti"] = { latitude = 42.548421, longitude = 43.384416, display_name = "Kvashkhieti"},
    ["Kvakude"] = { latitude = 42.054600, longitude = 42.299292, display_name = "Kvakude"},
    ["Kvaiti"] = { latitude = 42.425612, longitude = 42.418926, display_name = "Kvaiti"},
    ["Kvahchiri"] = { latitude = 42.205749, longitude = 42.738452, display_name = "Kvahchiri"},
    ["Kvabga"] = { latitude = 41.923119, longitude = 42.401623, display_name = "Kvabga"},
    ["Kuzhorskaya"] = { latitude = 44.670893, longitude = 40.302495, display_name = "Kuzhorskaya"},
    ["Kutol"] = { latitude = 42.847172, longitude = 41.384156, display_name = "Kutol"},
    ["Kutiri"] = { latitude = 42.266691, longitude = 42.357876, display_name = "Kutiri"},
    ["Kutaisskaya"] = { latitude = 44.648472, longitude = 39.310268, display_name = "Kutaisskaya"},
    ["Kutais"] = { latitude = 44.526035, longitude = 39.297980, display_name = "Kutais"},
    ["Kushubauri"] = { latitude = 42.050474, longitude = 42.439897, display_name = "Kushubauri"},
    ["Kurzu"] = { latitude = 42.586949, longitude = 42.289336, display_name = "Kurzu"},
    ["Kursebi"] = { latitude = 42.324736, longitude = 42.782359, display_name = "Kursebi"},
    ["Kurinskaya"] = { latitude = 44.412581, longitude = 39.419430, display_name = "Kurinskaya"},
    ["Kurdzhipskaya"] = { latitude = 44.466941, longitude = 40.052631, display_name = "Kurdzhipskaya"},
    ["Kurdzhinovo"] = { latitude = 43.955447, longitude = 40.955449, display_name = "Kurdzhinovo"},
    ["Kurchanskaya"] = { latitude = 45.226181, longitude = 37.567587, display_name = "Kurchanskaya"},
    ["Kunchukohabl'"] = { latitude = 44.986007, longitude = 39.473929, display_name = "Kunchukohabl'"},
    ["Kumysh"] = { latitude = 43.883288, longitude = 41.895281, display_name = "Kumysh"},
    ["Kumuri"] = { latitude = 42.035760, longitude = 42.465455, display_name = "Kumuri"},
    ["Kumistavi"] = { latitude = 42.385156, longitude = 42.587147, display_name = "Kumistavi"},
    ["Kulishkari"] = { latitude = 42.513092, longitude = 41.958707, display_name = "Kulishkari"},
    ["Kulevi"] = { latitude = 42.272143, longitude = 41.658712, display_name = "Kulevi"},
    ["Kul'tubani"] = { latitude = 43.411775, longitude = 40.018098, display_name = "Kul'tubani"},
    ["Kuheshi"] = { latitude = 42.680133, longitude = 42.113696, display_name = "Kuheshi"},
    ["Kudepsta"] = { latitude = 43.500558, longitude = 39.893815, display_name = "Kudepsta"},
    ["Kuchugury"] = { latitude = 45.408297, longitude = 36.957851, display_name = "Kuchugury"},
    ["Kubina"] = { latitude = 44.064788, longitude = 41.946525, display_name = "Kubina"},
    ["Kubanskiy"] = { latitude = 44.617741, longitude = 39.773434, display_name = "Kubanskiy"},
    ["Kubanskaya"] = { latitude = 44.607766, longitude = 39.710965, display_name = "Kubanskaya"},
    ["Kuba-Taba"] = { latitude = 43.781922, longitude = 43.445360, display_name = "Kuba-Taba"},
    ["Kuba"] = { latitude = 43.861071, longitude = 43.454167, display_name = "Kuba"},
    ["Kroyanskoe"] = { latitude = 44.095332, longitude = 39.120379, display_name = "Kroyanskoe"},
    ["Krizhanovskiy"] = { latitude = 45.250930, longitude = 38.164738, display_name = "Krizhanovskiy"},
    ["Krivenkovskoe"] = { latitude = 44.192196, longitude = 39.231930, display_name = "Krivenkovskoe"},
    ["Krikuna"] = { latitude = 45.267052, longitude = 38.212063, display_name = "Krikuna"},
    ["Krepostnaya"] = { latitude = 44.712451, longitude = 38.678714, display_name = "Krepostnaya"},
    ["Kremenchug-Konstantinovskiy"] = { latitude = 43.795032, longitude = 43.648364, display_name = "Kremenchug-Konstantinovskiy"},
    ["Krasnyy Pahar'"] = { latitude = 44.202575, longitude = 43.104153, display_name = "Krasnyy Pahar'"},
    ["Krasnyy Oktyabr'"] = { latitude = 45.195279, longitude = 37.655732, display_name = "Krasnyy Oktyabr'"},
    ["Krasnyy Kurgan"] = { latitude = 45.016457, longitude = 37.339404, display_name = "Krasnyy Kurgan"},
    ["Krasnyy"] = { latitude = 44.981641, longitude = 38.043088, display_name = "Krasnyy"},
    ["Krasnovostochnyy"] = { latitude = 43.967766, longitude = 42.295034, display_name = "Krasnovostochnyy"},
    ["Krasnosel'skoe"] = { latitude = 45.292325, longitude = 39.165057, display_name = "Krasnosel'skoe"},
    ["Krasnooktyabr'skiy"] = { latitude = 44.932096, longitude = 38.368635, display_name = "Krasnooktyabr'skiy"},
    ["Krasnokumskoe"] = { latitude = 44.176715, longitude = 43.484738, display_name = "Krasnokumskoe"},
    ["Krasnogvardeyskoe"] = { latitude = 45.124528, longitude = 39.575543, display_name = "Krasnogvardeyskoe"},
    ["Krasnogorskaya"] = { latitude = 43.942361, longitude = 41.886798, display_name = "Krasnogorskaya"},
    ["Krasnodarskiy"] = { latitude = 45.157592, longitude = 39.135852, display_name = "Krasnodarskiy"},
    ["Krasnaya Volya"] = { latitude = 43.562303, longitude = 39.917862, display_name = "Krasnaya Volya"},
    ["Krasnaya Batareya"] = { latitude = 45.089560, longitude = 37.786608, display_name = "Krasnaya Batareya"},
    ["Krasn.Kurgan"] = { latitude = 43.946104, longitude = 42.607477, display_name = "Krasn.Kurgan"},
    ["Kraevsko-Armyanskoe"] = { latitude = 43.607677, longitude = 39.824782, display_name = "Kraevsko-Armyanskoe"},
    ["Kozet"] = { latitude = 44.995830, longitude = 38.997301, display_name = "Kozet"},
    ["Koydan"] = { latitude = 44.107503, longitude = 42.158336, display_name = "Koydan"},
    ["Kosovichi"] = { latitude = 45.090116, longitude = 38.562291, display_name = "Kosovichi"},
    ["Kosh-Habl'"] = { latitude = 44.140522, longitude = 41.848038, display_name = "Kosh-Habl'"},
    ["Korzhevskiy"] = { latitude = 45.223751, longitude = 38.190642, display_name = "Korzhevskiy"},
    ["Korzhevskiy"] = { latitude = 45.196991, longitude = 37.716494, display_name = "Korzhevskiy"},
    ["Kortsheli"] = { latitude = 42.561060, longitude = 41.946838, display_name = "Kortsheli"},
    ["Koreti"] = { latitude = 42.301668, longitude = 43.380740, display_name = "Koreti"},
    ["Korbouli"] = { latitude = 42.239897, longitude = 43.467509, display_name = "Korbouli"},
    ["Kopanskiy"] = { latitude = 45.174892, longitude = 38.803876, display_name = "Kopanskiy"},
    ["Kontuati"] = { latitude = 42.349493, longitude = 42.418258, display_name = "Kontuati"},
    ["Kontianeti"] = { latitude = 42.326672, longitude = 42.166574, display_name = "Kontianeti"},
    ["Konstantinovskaya"] = { latitude = 44.049269, longitude = 43.158315, display_name = "Konstantinovskaya"},
    ["Konchkati"] = { latitude = 41.983654, longitude = 41.897403, display_name = "Konchkati"},
    ["Konchkati"] = { latitude = 42.010778, longitude = 42.018563, display_name = "Konchkati"},
    ["Komsomolets"] = { latitude = 44.022420, longitude = 43.562616, display_name = "Komsomolets"},
    ["Komsomol'skiy"] = { latitude = 44.936557, longitude = 39.703550, display_name = "Komsomol'skiy"},
    ["Kommayak"] = { latitude = 44.110647, longitude = 43.858365, display_name = "Kommayak"},
    ["Kolosistyy"] = { latitude = 45.135919, longitude = 38.896495, display_name = "Kolosistyy"},
    ["Kolos"] = { latitude = 45.151407, longitude = 38.345172, display_name = "Kolos"},
    ["Kolobani"] = { latitude = 42.203259, longitude = 42.267792, display_name = "Kolobani"},
    ["Kolhida"] = { latitude = 43.248837, longitude = 40.294920, display_name = "Kolhida"},
    ["Koki"] = { latitude = 42.478988, longitude = 41.705012, display_name = "Koki"},
    ["Koka"] = { latitude = 42.317730, longitude = 42.829199, display_name = "Koka"},
    ["Kochetinskiy"] = { latitude = 45.178268, longitude = 39.238568, display_name = "Kochetinskiy"},
    ["Kochara"] = { latitude = 42.853821, longitude = 41.442319, display_name = "Kochara"},
    ["Kobuleti"] = { latitude = 41.805916, longitude = 41.884158, display_name = "Kobuleti"},
    ["Kobu-Bashi"] = { latitude = 43.932621, longitude = 41.313135, display_name = "Kobu-Bashi"},
    ["Kishpek"] = { latitude = 43.649391, longitude = 43.645297, display_name = "Kishpek"},
    ["Kirtshi"] = { latitude = 42.483754, longitude = 42.054947, display_name = "Kirtshi"},
    ["Kirpichnyy"] = { latitude = 44.041877, longitude = 42.829060, display_name = "Kirpichnyy"},
    ["Kirpichnoe"] = { latitude = 44.168178, longitude = 39.201378, display_name = "Kirpichnoe"},
    ["Kirov"] = { latitude = 42.415340, longitude = 41.661494, display_name = "Kirov"},
    ["Kirilovka"] = { latitude = 44.771935, longitude = 37.724488, display_name = "Kirilovka"},
    ["Kievskoe"] = { latitude = 45.039183, longitude = 37.886756, display_name = "Kievskoe"},
    ["Kichmalka"] = { latitude = 43.793482, longitude = 42.941509, display_name = "Kichmalka"},
    ["Kichi-Balyk"] = { latitude = 43.792728, longitude = 42.650712, display_name = "Kichi-Balyk"},
    ["Ketilari"] = { latitude = 42.146402, longitude = 42.169710, display_name = "Ketilari"},
    ["Keslerovo"] = { latitude = 45.065249, longitude = 37.820617, display_name = "Keslerovo"},
    ["Kerken"] = { latitude = 42.876960, longitude = 41.454944, display_name = "Kerken"},
    ["Kemal'pasha"] = { latitude = 41.468809, longitude = 41.519062, display_name = "Kemal'pasha"},
    ["Kelermesskaya"] = { latitude = 44.793224, longitude = 40.128338, display_name = "Kelermesskaya"},
    ["Kelasuri"] = { latitude = 43.024991, longitude = 41.107288, display_name = "Kelasuri"},
    ["Kazazov"] = { latitude = 44.905153, longitude = 39.174224, display_name = "Kazazov"},
    ["Kavkazskiy"] = { latitude = 44.269490, longitude = 42.230278, display_name = "Kavkazskiy"},
    ["Katshi"] = { latitude = 42.292364, longitude = 43.206406, display_name = "Katshi"},
    ["Karskiy"] = { latitude = 44.828921, longitude = 38.509046, display_name = "Karskiy"},
    ["Karla Marksa"] = { latitude = 45.239177, longitude = 39.058650, display_name = "Karla Marksa"},
    ["Kardonikskaya"] = { latitude = 43.864866, longitude = 41.716166, display_name = "Kardonikskaya"},
    ["Karagach"] = { latitude = 43.804645, longitude = 43.776369, display_name = "Karagach"},
    ["Kangly"] = { latitude = 44.259543, longitude = 43.029475, display_name = "Kangly"},
    ["Kamlyuko"] = { latitude = 43.778866, longitude = 43.260087, display_name = "Kamlyuko"},
    ["Kamennomostskoe"] = { latitude = 43.735328, longitude = 43.047845, display_name = "Kamennomostskoe"},
    ["Kamennomostskiy"] = { latitude = 43.750261, longitude = 41.907820, display_name = "Kamennomostskiy"},
    ["Kaluzhskaya"] = { latitude = 44.763438, longitude = 38.974839, display_name = "Kaluzhskaya"},
    ["Kalinovoe Ozero"] = { latitude = 43.615339, longitude = 39.884288, display_name = "Kalinovoe Ozero"},
    ["Kalininskiy"] = { latitude = 45.207233, longitude = 40.043834, display_name = "Kalininskiy"},
    ["Kalinina"] = { latitude = 44.503982, longitude = 39.720655, display_name = "Kalinina"},
    ["Kalezh"] = { latitude = 44.009759, longitude = 39.353566, display_name = "Kalezh"},
    ["Kaldahvara"] = { latitude = 43.223294, longitude = 40.418047, display_name = "Kaldahvara"},
    ["Kaladzhinskaya"] = { latitude = 44.307522, longitude = 40.905076, display_name = "Kaladzhinskaya"},
    ["Kakuti"] = { latitude = 41.859621, longitude = 41.975559, display_name = "Kakuti"},
    ["Kahun"] = { latitude = 43.539108, longitude = 43.881699, display_name = "Kahun"},
    ["Kahati"] = { latitude = 42.491554, longitude = 41.767683, display_name = "Kahati"},
    ["Kachaeti"] = { latitude = 42.475414, longitude = 43.108621, display_name = "Kachaeti"},
    ["Kabehabl'"] = { latitude = 45.046603, longitude = 40.180256, display_name = "Kabehabl'"},
    ["Kabardinskaya"] = { latitude = 44.501303, longitude = 39.490572, display_name = "Kabardinskaya"},
    ["Izumrud"] = { latitude = 43.467734, longitude = 39.956954, display_name = "Izumrud"},
    ["Izmaylovka"] = { latitude = 43.630299, longitude = 39.824872, display_name = "Izmaylovka"},
    ["Ivanovskaya"] = { latitude = 45.276018, longitude = 38.459475, display_name = "Ivanovskaya"},
    ["Ivanov"] = { latitude = 45.114713, longitude = 37.449495, display_name = "Ivanov"},
    ["Ithvisi"] = { latitude = 42.299871, longitude = 43.345725, display_name = "Ithvisi"},
    ["Isunderi"] = { latitude = 42.556323, longitude = 42.653318, display_name = "Isunderi"},
    ["Isula"] = { latitude = 42.234284, longitude = 42.066794, display_name = "Isula"},
    ["Ispravnaya"] = { latitude = 44.073144, longitude = 41.608052, display_name = "Ispravnaya"},
    ["Islamey"] = { latitude = 43.674212, longitude = 43.451668, display_name = "Islamey"},
    ["Inzhich-Chukun"] = { latitude = 44.047223, longitude = 41.782859, display_name = "Inzhich-Chukun"},
    ["Inzhi-Chishko"] = { latitude = 44.199712, longitude = 41.716677, display_name = "Inzhi-Chishko"},
    ["Intabueti"] = { latitude = 41.974858, longitude = 42.230582, display_name = "Intabueti"},
    ["Inkit"] = { latitude = 43.178272, longitude = 40.295540, display_name = "Inkit"},
    ["Ingiri"] = { latitude = 42.495609, longitude = 41.813885, display_name = "Ingiri"},
    ["Indyuk"] = { latitude = 44.224273, longitude = 39.236887, display_name = "Indyuk"},
    ["Industrial'nyy"] = { latitude = 45.097241, longitude = 39.101407, display_name = "Industrial'nyy"},
    ["Inchkhuri"] = { latitude = 42.449647, longitude = 42.398849, display_name = "Inchkhuri"},
    ["Imeretinskaya"] = { latitude = 44.687871, longitude = 39.425248, display_name = "Imeretinskaya"},
    ["im.Tel'mana"] = { latitude = 44.075250, longitude = 42.855761, display_name = "im.Tel'mana"},
    ["im.Kosta Hetagurova "] = { latitude = 43.806385, longitude = 41.902590, display_name = "im.Kosta Hetagurova "},
    ["Il'ichevskoe"] = { latitude = 44.189752, longitude = 42.146887, display_name = "Il'ichevskoe"},
    ["Il'ich"] = { latitude = 45.425374, longitude = 36.769869, display_name = "Il'ich"},
    ["Il'ich"] = { latitude = 43.953727, longitude = 41.527379, display_name = "Il'ich"},
    ["Ianeuli"] = { latitude = 41.996073, longitude = 42.284336, display_name = "Ianeuli"},
    ["Ianeti"] = { latitude = 42.185880, longitude = 42.420261, display_name = "Ianeti"},
    ["Hvanchkara"] = { latitude = 42.564505, longitude = 43.017793, display_name = "Hvanchkara"},
    ["Hutsubani"] = { latitude = 41.814393, longitude = 41.820341, display_name = "Hutsubani"},
    ["Husy-Kardonik"] = { latitude = 43.788087, longitude = 41.572828, display_name = "Husy-Kardonik"},
    ["Hushtosyrt"] = { latitude = 43.436177, longitude = 43.236374, display_name = "Hushtosyrt"},
    ["Hurzuk"] = { latitude = 43.432373, longitude = 42.149492, display_name = "Hurzuk"},
    ["Hunevi"] = { latitude = 42.106239, longitude = 43.362778, display_name = "Hunevi"},
    ["Hundzhulouri"] = { latitude = 42.190578, longitude = 42.306336, display_name = "Hundzhulouri"},
    ["Humeni-Natopuri"] = { latitude = 42.624068, longitude = 41.611103, display_name = "Humeni-Natopuri"},
    ["Humara"] = { latitude = 43.861423, longitude = 41.912994, display_name = "Humara"},
    ["Hrialeti"] = { latitude = 42.008259, longitude = 41.820575, display_name = "Hrialeti"},
    ["Hresili"] = { latitude = 42.347693, longitude = 42.890246, display_name = "Hresili"},
    ["Hreiti"] = { latitude = 42.348511, longitude = 43.178584, display_name = "Hreiti"},
    ["Hotevi"] = { latitude = 42.467876, longitude = 43.135786, display_name = "Hotevi"},
    ["Horiti"] = { latitude = 42.074583, longitude = 43.212767, display_name = "Horiti"},
    ["Honchiori"] = { latitude = 42.498535, longitude = 43.036221, display_name = "Honchiori"},
    ["Holodnaya Rechka"] = { latitude = 43.362803, longitude = 40.132876, display_name = "Holodnaya Rechka"},
    ["Hole"] = { latitude = 42.629201, longitude = 41.834447, display_name = "Hole"},
    ["Hidmagala"] = { latitude = 42.040528, longitude = 41.789749, display_name = "Hidmagala"},
    ["Hidistavi"] = { latitude = 41.962817, longitude = 42.192399, display_name = "Hidistavi"},
    ["Hidari"] = { latitude = 42.018357, longitude = 43.100231, display_name = "Hidari"},
    ["Hevi"] = { latitude = 41.964098, longitude = 42.278006, display_name = "Hevi"},
    ["Hetsera"] = { latitude = 42.431467, longitude = 41.879320, display_name = "Hetsera"},
    ["Heledi"] = { latitude = 42.794395, longitude = 42.640247, display_name = "Heledi"},
    ["Hatukay"] = { latitude = 45.193082, longitude = 39.661811, display_name = "Hatukay"},
    ["Hatazhukay"] = { latitude = 45.072561, longitude = 40.182799, display_name = "Hatazhukay"},
    ["Hasaut-Grecheskoe "] = { latitude = 43.714868, longitude = 41.668144, display_name = "Hasaut-Grecheskoe "},
    ["Hantski"] = { latitude = 42.578465, longitude = 42.222137, display_name = "Hantski"},
    ["Hanskaya"] = { latitude = 44.676366, longitude = 39.960442, display_name = "Hanskaya"},
    ["Hani"] = { latitude = 41.956643, longitude = 42.957650, display_name = "Hani"},
    ["Han'kov"] = { latitude = 45.163180, longitude = 37.866697, display_name = "Han'kov"},
    ["Hamyshki"] = { latitude = 44.100538, longitude = 40.129130, display_name = "Hamyshki"},
    ["Hamiskuri"] = { latitude = 42.387923, longitude = 41.809094, display_name = "Hamiskuri"},
    ["Halipauri"] = { latitude = 42.345266, longitude = 43.305217, display_name = "Halipauri"},
    ["Hadzhiko"] = { latitude = 44.008582, longitude = 39.333321, display_name = "Hadzhiko"},
    ["Hachemziy"] = { latitude = 44.955574, longitude = 40.318034, display_name = "Hachemziy"},
    ["Habez"] = { latitude = 44.042203, longitude = 41.765924, display_name = "Habez"},
    ["Habaz"] = { latitude = 43.730189, longitude = 42.931830, display_name = "Habaz"},
    ["h.im.Lenina"] = { latitude = 45.024109, longitude = 39.217630, display_name = "h.im.Lenina"},
    ["Gyuryul'deuk"] = { latitude = 43.981930, longitude = 42.060924, display_name = "Gyuryul'deuk"},
    ["Gvishtibi"] = { latitude = 42.313702, longitude = 42.570687, display_name = "Gvishtibi"},
    ["Gvimaroni"] = { latitude = 42.276655, longitude = 41.967510, display_name = "Gvimaroni"},
    ["Gverki"] = { latitude = 42.058676, longitude = 43.195791, display_name = "Gverki"},
    ["Gvankiti"] = { latitude = 42.168099, longitude = 42.981755, display_name = "Gvankiti"},
    ["Gvandra"] = { latitude = 43.046780, longitude = 40.908764, display_name = "Gvandra"},
    ["Gurna"] = { latitude = 42.403730, longitude = 42.854106, display_name = "Gurna"},
    ["Guriyskaya"] = { latitude = 44.663547, longitude = 39.614414, display_name = "Guriyskaya"},
    ["Gurianta"] = { latitude = 41.951175, longitude = 41.931424, display_name = "Gurianta"},
    ["Gupagu"] = { latitude = 42.890107, longitude = 41.593118, display_name = "Gupagu"},
    ["Gundelen"] = { latitude = 43.597443, longitude = 43.180901, display_name = "Gundelen"},
    ["Gundaeti"] = { latitude = 42.241907, longitude = 43.328842, display_name = "Gundaeti"},
    ["Gumista"] = { latitude = 43.027157, longitude = 40.945511, display_name = "Gumista"},
    ["Gumati"] = { latitude = 42.340711, longitude = 42.678726, display_name = "Gumati"},
    ["Guluheti"] = { latitude = 42.240583, longitude = 42.274605, display_name = "Guluheti"},
    ["Gubskaya"] = { latitude = 44.317053, longitude = 40.626591, display_name = "Gubskaya"},
    ["Gubistskali"] = { latitude = 42.304960, longitude = 42.519440, display_name = "Gubistskali"},
    ["Groznyy"] = { latitude = 44.558225, longitude = 40.132270, display_name = "Groznyy"},
    ["Grigor'evskaya"] = { latitude = 44.772915, longitude = 38.839703, display_name = "Grigor'evskaya"},
    ["Grigolishi"] = { latitude = 42.538524, longitude = 41.972442, display_name = "Grigolishi"},
    ["Grigalati"] = { latitude = 42.091739, longitude = 43.397697, display_name = "Grigalati"},
    ["Grebeshok"] = { latitude = 43.349031, longitude = 40.158298, display_name = "Grebeshok"},
    ["Grazhdanskoe"] = { latitude = 44.223626, longitude = 42.765001, display_name = "Grazhdanskoe"},
    ["Goyth"] = { latitude = 44.248215, longitude = 39.372450, display_name = "Goyth"},
    ["Gostagaevskaya"] = { latitude = 45.022014, longitude = 37.503797, display_name = "Gostagaevskaya"},
    ["Gornyy"] = { latitude = 44.284635, longitude = 39.276932, display_name = "Gornyy"},
    ["Gornyy"] = { latitude = 43.958726, longitude = 42.858514, display_name = "Gornyy"},
    ["Gorgadzeebi"] = { latitude = 41.719152, longitude = 41.779467, display_name = "Gorgadzeebi"},
    ["Goresha"] = { latitude = 42.075887, longitude = 43.257362, display_name = "Goresha"},
    ["Goraberezhouli"] = { latitude = 42.004796, longitude = 42.210148, display_name = "Goraberezhouli"},
    ["Gonio"] = { latitude = 41.563835, longitude = 41.573319, display_name = "Gonio"},
    ["Gonebiskari"] = { latitude = 41.902497, longitude = 42.089475, display_name = "Gonebiskari"},
    ["Goncharka"] = { latitude = 44.809236, longitude = 39.954964, display_name = "Goncharka"},
    ["Gomi"] = { latitude = 41.887530, longitude = 42.105644, display_name = "Gomi"},
    ["Gomi"] = { latitude = 42.613091, longitude = 43.534511, display_name = "Gomi"},
    ["Golubitskaya"] = { latitude = 45.325805, longitude = 37.273542, display_name = "Golubitskaya"},
    ["Golubeva Dacha"] = { latitude = 43.985387, longitude = 39.231989, display_name = "Golubeva Dacha"},
    ["Golovinka"] = { latitude = 43.800018, longitude = 39.460479, display_name = "Golovinka"},
    ["Golaskuri"] = { latitude = 42.237078, longitude = 41.979639, display_name = "Golaskuri"},
    ["Gogni"] = { latitude = 42.277496, longitude = 42.987314, display_name = "Gogni"},
    ["Gofitskoe"] = { latitude = 44.252679, longitude = 40.973461, display_name = "Gofitskoe"},
    ["Godogani"] = { latitude = 42.260281, longitude = 42.781294, display_name = "Godogani"},
    ["Gocha-Dzhihaishi"] = { latitude = 42.259725, longitude = 42.404511, display_name = "Gocha-Dzhihaishi"},
    ["Glola"] = { latitude = 42.703835, longitude = 43.646352, display_name = "Glola"},
    ["Glebovskoe"] = { latitude = 44.711034, longitude = 37.639464, display_name = "Glebovskoe"},
    ["Gimozgondzhili"] = { latitude = 42.261354, longitude = 41.952437, display_name = "Gimozgondzhili"},
    ["Gezati"] = { latitude = 42.222157, longitude = 42.259945, display_name = "Gezati"},
    ["Gerpegezh"] = { latitude = 43.375311, longitude = 43.654756, display_name = "Gerpegezh"},
    ["Germenchik"] = { latitude = 43.588735, longitude = 43.766319, display_name = "Germenchik"},
    ["Georgievskoe"] = { latitude = 44.164187, longitude = 39.251783, display_name = "Georgievskoe"},
    ["Georgievskaya"] = { latitude = 44.113588, longitude = 43.480279, display_name = "Georgievskaya"},
    ["Gelati"] = { latitude = 42.299240, longitude = 42.763434, display_name = "Gelati"},
    ["Geguti"] = { latitude = 42.171259, longitude = 42.674274, display_name = "Geguti"},
    ["Gedzheti"] = { latitude = 42.300316, longitude = 42.180255, display_name = "Gedzheti"},
    ["Gebi"] = { latitude = 42.769923, longitude = 43.506725, display_name = "Gebi"},
    ["Gay-Kodzor"] = { latitude = 44.855566, longitude = 37.436302, display_name = "Gay-Kodzor"},
    ["Gaverdovskiy"] = { latitude = 44.615979, longitude = 40.022495, display_name = "Gaverdovskiy"},
    ["Gautskinari"] = { latitude = 42.128638, longitude = 42.272212, display_name = "Gautskinari"},
    ["Gatlukay"] = { latitude = 44.893152, longitude = 39.232372, display_name = "Gatlukay"},
    ["Garkusha"] = { latitude = 45.321862, longitude = 36.849719, display_name = "Garkusha"},
    ["Garaha"] = { latitude = 42.517950, longitude = 42.154883, display_name = "Garaha"},
    ["Gantiadi"] = { latitude = 42.041995, longitude = 42.389471, display_name = "Gantiadi"},
    ["Ganardzhiis-Muhuri"] = { latitude = 42.425698, longitude = 41.627546, display_name = "Ganardzhiis-Muhuri"},
    ["Ganahleba"] = { latitude = 42.043143, longitude = 42.199876, display_name = "Ganahleba"},
    ["Ganahleba"] = { latitude = 42.929130, longitude = 41.276155, display_name = "Ganahleba"},
    ["Gamogma-Shua-Horga"] = { latitude = 42.269829, longitude = 41.795750, display_name = "Gamogma-Shua-Horga"},
    ["Gamogma Kariata"] = { latitude = 42.274404, longitude = 41.748423, display_name = "Gamogma Kariata"},
    ["Gahomela"] = { latitude = 42.347325, longitude = 42.191623, display_name = "Gahomela"},
    ["Gagma-Zanati"] = { latitude = 42.224803, longitude = 42.116445, display_name = "Gagma-Zanati"},
    ["Gagma-Shua-Horga"] = { latitude = 42.259303, longitude = 41.816329, display_name = "Gagma-Shua-Horga"},
    ["Gagma-Sadzhidzhao"] = { latitude = 42.365068, longitude = 42.038665, display_name = "Gagma-Sadzhidzhao"},
    ["Gagma-Dvabzu"] = { latitude = 41.952223, longitude = 42.074319, display_name = "Gagma-Dvabzu"},
    ["Gagma-Boslevi"] = { latitude = 42.187975, longitude = 43.175391, display_name = "Gagma-Boslevi"},
    ["Fontalovskaya"] = { latitude = 45.365963, longitude = 36.932416, display_name = "Fontalovskaya"},
    ["Feria"] = { latitude = 41.632954, longitude = 41.657343, display_name = "Feria"},
    ["Fedorovskaya"] = { latitude = 45.080625, longitude = 38.461367, display_name = "Fedorovskaya"},
    ["Fazannyy"] = { latitude = 44.039908, longitude = 43.589867, display_name = "Fazannyy"},
    ["Fadeevskiy"] = { latitude = 44.642871, longitude = 39.866300, display_name = "Fadeevskiy"},
    ["Fadeevo"] = { latitude = 45.068450, longitude = 37.549202, display_name = "Fadeevo"},
    ["Ezhedughabl'"] = { latitude = 44.978179, longitude = 39.704512, display_name = "Ezhedughabl'"},
    ["Evseevskiy"] = { latitude = 45.055579, longitude = 38.136915, display_name = "Evseevskiy"},
    ["Etseri"] = { latitude = 42.554075, longitude = 42.307231, display_name = "Etseri"},
    ["Etseri"] = { latitude = 42.259355, longitude = 43.163529, display_name = "Etseri"},
    ["Etseri"] = { latitude = 42.212272, longitude = 42.927395, display_name = "Etseri"},
    ["Etoko"] = { latitude = 43.948323, longitude = 43.173746, display_name = "Etoko"},
    ["Etoka"] = { latitude = 43.916666, longitude = 43.055886, display_name = "Etoka"},
    ["Esto-Sadok"] = { latitude = 43.688264, longitude = 40.257190, display_name = "Esto-Sadok"},
    ["Essentukskaya"] = { latitude = 44.029667, longitude = 42.870161, display_name = "Essentukskaya"},
    ["Erivanskaya"] = { latitude = 44.727221, longitude = 38.181969, display_name = "Erivanskaya"},
    ["Erik"] = { latitude = 44.586302, longitude = 39.697324, display_name = "Erik"},
    ["Ergeta"] = { latitude = 42.385459, longitude = 41.676872, display_name = "Ergeta"},
    ["Erge"] = { latitude = 41.562487, longitude = 41.695039, display_name = "Erge"},
    ["Energetik"] = { latitude = 44.071767, longitude = 43.093184, display_name = "Energetik"},
    ["Elizavetinskaya"] = { latitude = 45.047901, longitude = 38.795941, display_name = "Elizavetinskaya"},
    ["Elenovskoe"] = { latitude = 45.102689, longitude = 39.704562, display_name = "Elenovskoe"},
    ["El'tarkach"] = { latitude = 43.982717, longitude = 42.129497, display_name = "El'tarkach"},
    ["El'burgan"] = { latitude = 44.076878, longitude = 41.797260, display_name = "El'burgan"},
    ["El'brusskiy"] = { latitude = 43.568403, longitude = 42.134528, display_name = "El'brusskiy"},
    ["El'brus"] = { latitude = 43.253522, longitude = 42.644636, display_name = "El'brus"},
    ["Ekonomicheskoe"] = { latitude = 44.993663, longitude = 37.940817, display_name = "Ekonomicheskoe"},
    ["Ekaterinovskiy"] = { latitude = 45.091212, longitude = 38.487168, display_name = "Ekaterinovskiy"},
    ["Dzveli-Senaki"] = { latitude = 42.296082, longitude = 42.129269, display_name = "Dzveli-Senaki"},
    ["Dzveli-Hibula"] = { latitude = 42.451272, longitude = 41.939322, display_name = "Dzveli-Hibula"},
    ["Dzuluhi"] = { latitude = 42.026843, longitude = 42.617118, display_name = "Dzuluhi"},
    ["Dzuknuri"] = { latitude = 42.324671, longitude = 42.884988, display_name = "Dzuknuri"},
    ["Dzmuisi"] = { latitude = 42.428631, longitude = 42.914125, display_name = "Dzmuisi"},
    ["Dzirovani"] = { latitude = 42.351689, longitude = 42.944612, display_name = "Dzirovani"},
    ["Dziridzhumati"] = { latitude = 42.017218, longitude = 41.972763, display_name = "Dziridzhumati"},
    ["Dziguta"] = { latitude = 43.006590, longitude = 41.058366, display_name = "Dziguta"},
    ["Dziguri"] = { latitude = 42.238451, longitude = 42.148827, display_name = "Dziguri"},
    ["Dzhvarisa"] = { latitude = 42.383886, longitude = 42.812210, display_name = "Dzhvarisa"},
    ["Dzhurukveti"] = { latitude = 42.076304, longitude = 41.922513, display_name = "Dzhurukveti"},
    ["Dzhumiti 2-e"] = { latitude = 42.578580, longitude = 42.134227, display_name = "Dzhumiti 2-e"},
    ["Dzhumiti 1-e"] = { latitude = 42.551824, longitude = 42.104516, display_name = "Dzhumiti 1-e"},
    ["Dzhumi"] = { latitude = 42.451885, longitude = 41.873382, display_name = "Dzhumi"},
    ["Dzholevi"] = { latitude = 42.373371, longitude = 42.248034, display_name = "Dzholevi"},
    ["Dzhingirik"] = { latitude = 43.736185, longitude = 41.886758, display_name = "Dzhingirik"},
    ["Dzhimostaro"] = { latitude = 42.302029, longitude = 42.707023, display_name = "Dzhimostaro"},
    ["Dzhihaskari"] = { latitude = 42.511728, longitude = 42.017798, display_name = "Dzhihaskari"},
    ["Dzhiginka"] = { latitude = 45.135915, longitude = 37.340154, display_name = "Dzhiginka"},
    ["Dzhidzhihabl'"] = { latitude = 44.951191, longitude = 39.408519, display_name = "Dzhidzhihabl'"},
    ["Dzhgydyrhva"] = { latitude = 43.178246, longitude = 40.703367, display_name = "Dzhgydyrhva"},
    ["Dzhgerda"] = { latitude = 42.910810, longitude = 41.361978, display_name = "Dzhgerda"},
    ["Dzherokay"] = { latitude = 44.990610, longitude = 40.307349, display_name = "Dzherokay"},
    ["Dzheguta"] = { latitude = 43.967182, longitude = 42.043303, display_name = "Dzheguta"},
    ["Dzhapshakari"] = { latitude = 42.409636, longitude = 41.979744, display_name = "Dzhapshakari"},
    ["Dzhapana"] = { latitude = 42.095931, longitude = 42.193642, display_name = "Dzhapana"},
    ["Dzhambichi"] = { latitude = 45.089652, longitude = 39.854614, display_name = "Dzhambichi"},
    ["Dzhalaurta"] = { latitude = 42.248976, longitude = 43.388858, display_name = "Dzhalaurta"},
    ["Dzhahunderi"] = { latitude = 42.799916, longitude = 43.022380, display_name = "Dzhahunderi"},
    ["Dzhagira"] = { latitude = 42.539297, longitude = 42.056917, display_name = "Dzhagira"},
    ["Dzhaga"] = { latitude = 43.954443, longitude = 42.558665, display_name = "Dzhaga"},
    ["Dzedzileti"] = { latitude = 42.422529, longitude = 42.560392, display_name = "Dzedzileti"},
    ["Dutshuni"] = { latitude = 42.014905, longitude = 42.472516, display_name = "Dutshuni"},
    ["Durgena"] = { latitude = 42.240344, longitude = 41.939394, display_name = "Durgena"},
    ["Dukmasov"] = { latitude = 45.007570, longitude = 39.914292, display_name = "Dukmasov"},
    ["Druzhnyy"] = { latitude = 44.733770, longitude = 39.770438, display_name = "Druzhnyy"},
    ["Druzhelyubnyy"] = { latitude = 45.128403, longitude = 39.157197, display_name = "Druzhelyubnyy"},
    ["Druzhba"] = { latitude = 44.202308, longitude = 42.015159, display_name = "Druzhba"},
    ["Doshake"] = { latitude = 42.522922, longitude = 42.247888, display_name = "Doshake"},
    ["Dolina"] = { latitude = 44.237984, longitude = 42.933208, display_name = "Dolina"},
    ["Dolgogusevskiy"] = { latitude = 44.813826, longitude = 39.783494, display_name = "Dolgogusevskiy"},
    ["Dinskaya"] = { latitude = 45.216067, longitude = 39.228527, display_name = "Dinskaya"},
    ["Dimi"] = { latitude = 42.103108, longitude = 42.814050, display_name = "Dimi"},
    ["Dilikauri"] = { latitude = 42.156207, longitude = 43.097258, display_name = "Dilikauri"},
    ["Dihazurga"] = { latitude = 42.609395, longitude = 41.858275, display_name = "Dihazurga"},
    ["Dihashkho"] = { latitude = 42.070267, longitude = 42.560686, display_name = "Dihashkho"},
    ["Didvela"] = { latitude = 42.128678, longitude = 42.773856, display_name = "Didvela"},
    ["Didi-Opeti"] = { latitude = 42.067712, longitude = 42.370916, display_name = "Didi-Opeti"},
    ["Didi-Nedzis-Kahati"] = { latitude = 42.426746, longitude = 41.725656, display_name = "Didi-Nedzis-Kahati"},
    ["Didi-Nedzi"] = { latitude = 42.401566, longitude = 41.698956, display_name = "Didi-Nedzi"},
    ["Didi-Kuhi"] = { latitude = 42.292505, longitude = 42.452544, display_name = "Didi-Kuhi"},
    ["Didi-Horshi"] = { latitude = 42.341781, longitude = 42.084974, display_name = "Didi-Horshi"},
    ["Didi-Gantiadi"] = { latitude = 42.097633, longitude = 43.182894, display_name = "Didi-Gantiadi"},
    ["Didi-Dzhihaishi"] = { latitude = 42.235623, longitude = 42.438275, display_name = "Didi-Dzhihaishi"},
    ["Didi-Chkoni"] = { latitude = 42.494580, longitude = 42.312053, display_name = "Didi-Chkoni"},
    ["Dgvaba"] = { latitude = 42.345038, longitude = 41.733485, display_name = "Dgvaba"},
    ["Dgnorisa"] = { latitude = 42.469657, longitude = 42.815037, display_name = "Dgnorisa"},
    ["Derchi"] = { latitude = 42.468882, longitude = 42.773539, display_name = "Derchi"},
    ["Derbentskaya"] = { latitude = 44.768096, longitude = 38.498924, display_name = "Derbentskaya"},
    ["Deisi"] = { latitude = 41.967611, longitude = 43.351327, display_name = "Deisi"},
    ["Dehviri"] = { latitude = 42.624887, longitude = 42.770402, display_name = "Dehviri"},
    ["Defanovka"] = { latitude = 44.432955, longitude = 38.780654, display_name = "Defanovka"},
    ["Dedalauri"] = { latitude = 42.354735, longitude = 42.515552, display_name = "Dedalauri"},
    ["Davitiani"] = { latitude = 42.458197, longitude = 41.780691, display_name = "Davitiani"},
    ["Dausuz"] = { latitude = 43.799532, longitude = 41.550776, display_name = "Dausuz"},
    ["Darcheli"] = { latitude = 42.440610, longitude = 41.691733, display_name = "Darcheli"},
    ["Dapnari"] = { latitude = 42.103547, longitude = 42.334468, display_name = "Dapnari"},
    ["Damanka"] = { latitude = 44.971429, longitude = 37.786142, display_name = "Damanka"},
    ["Dahovskaya"] = { latitude = 44.231585, longitude = 40.205041, display_name = "Dahovskaya"},
    ["Dagestanskaya"] = { latitude = 44.377390, longitude = 40.019213, display_name = "Dagestanskaya"},
    ["Dabla-Gomi"] = { latitude = 42.092557, longitude = 42.383587, display_name = "Dabla-Gomi"},
    ["Dabadzveli"] = { latitude = 42.326223, longitude = 42.921560, display_name = "Dabadzveli"},
    ["Chvele"] = { latitude = 42.681685, longitude = 41.980907, display_name = "Chvele"},
    ["Chuneshi"] = { latitude = 42.356359, longitude = 42.566710, display_name = "Chuneshi"},
    ["Chukuli"] = { latitude = 42.811275, longitude = 43.016349, display_name = "Chukuli"},
    ["Chuburhindzhi"] = { latitude = 42.582487, longitude = 41.806660, display_name = "Chuburhindzhi"},
    ["Chorvila"] = { latitude = 42.284002, longitude = 43.416914, display_name = "Chorvila"},
    ["Chordzho-Didi"] = { latitude = 42.562477, longitude = 43.053728, display_name = "Chordzho-Didi"},
    ["Chognari"] = { latitude = 42.087419, longitude = 42.284283, display_name = "Chognari"},
    ["Chognari"] = { latitude = 42.220218, longitude = 42.760049, display_name = "Chognari"},
    ["Chochkhati"] = { latitude = 42.034916, longitude = 41.884302, display_name = "Chochkhati"},
    ["Chlou"] = { latitude = 42.872872, longitude = 41.493654, display_name = "Chlou"},
    ["Chkvishi"] = { latitude = 42.124911, longitude = 42.435224, display_name = "Chkvishi"},
    ["Chkvaleri"] = { latitude = 42.719689, longitude = 42.088272, display_name = "Chkvaleri"},
    ["Chkonagora"] = { latitude = 42.088123, longitude = 42.140278, display_name = "Chkonagora"},
    ["Chkhuteli"] = { latitude = 42.649842, longitude = 42.789429, display_name = "Chkhuteli"},
    ["Chkhoria"] = { latitude = 42.605132, longitude = 41.954171, display_name = "Chkhoria"},
    ["Chkhenishi"] = { latitude = 42.228963, longitude = 42.314718, display_name = "Chkhenishi"},
    ["Chkaduashi"] = { latitude = 42.599065, longitude = 42.017694, display_name = "Chkaduashi"},
    ["Chitatskari"] = { latitude = 42.472242, longitude = 41.852826, display_name = "Chitatskari"},
    ["Chiora"] = { latitude = 42.746269, longitude = 43.553631, display_name = "Chiora"},
    ["Chihu"] = { latitude = 42.325806, longitude = 41.889458, display_name = "Chihu"},
    ["Chiha"] = { latitude = 42.348364, longitude = 43.444360, display_name = "Chiha"},
    ["Chernyshov"] = { latitude = 45.053069, longitude = 40.037395, display_name = "Chernyshov"},
    ["Chernomorskaya"] = { latitude = 44.702545, longitude = 39.359728, display_name = "Chernomorskaya"},
    ["Chernigovskoe"] = { latitude = 44.255092, longitude = 39.760512, display_name = "Chernigovskoe"},
    ["Chernigovskaya"] = { latitude = 44.703771, longitude = 39.668488, display_name = "Chernigovskaya"},
    ["Chernigovka"] = { latitude = 43.014930, longitude = 41.164718, display_name = "Chernigovka"},
    ["Chernaya Rechka"] = { latitude = 43.609516, longitude = 43.836128, display_name = "Chernaya Rechka"},
    ["Chereshnya"] = { latitude = 43.444920, longitude = 39.980335, display_name = "Chereshnya"},
    ["Chemitokvadze"] = { latitude = 43.839550, longitude = 39.417020, display_name = "Chemitokvadze"},
    ["Chemburka"] = { latitude = 44.931292, longitude = 37.340168, display_name = "Chemburka"},
    ["Chekon"] = { latitude = 45.109331, longitude = 37.506382, display_name = "Chekon"},
    ["Chegem 2-y"] = { latitude = 43.590681, longitude = 43.599375, display_name = "Chegem 2-y"},
    ["Chapaevskoe"] = { latitude = 44.286271, longitude = 42.063673, display_name = "Chapaevskoe"},
    ["Chalatke"] = { latitude = 42.146661, longitude = 43.025774, display_name = "Chalatke"},
    ["Chala"] = { latitude = 41.929410, longitude = 42.048230, display_name = "Chala"},
    ["Chakvindzhi"] = { latitude = 42.487273, longitude = 41.979719, display_name = "Chakvindzhi"},
    ["Chaisubani"] = { latitude = 41.696688, longitude = 41.781638, display_name = "Chaisubani"},
    ["Chagani"] = { latitude = 42.227874, longitude = 42.373545, display_name = "Chagani"},
    ["Chagan-Tskvishi"] = { latitude = 42.122079, longitude = 42.415750, display_name = "Chagan-Tskvishi"},
    ["Chabanlug"] = { latitude = 43.106712, longitude = 40.797033, display_name = "Chabanlug"},
    ["Bzybta 5-y km"] = { latitude = 43.285386, longitude = 40.395535, display_name = "Bzybta 5-y km"},
    ["Bzybta 3-y km"] = { latitude = 43.269965, longitude = 40.394210, display_name = "Bzybta 3-y km"},
    ["Bzheduhovskaya"] = { latitude = 44.841591, longitude = 39.679353, display_name = "Bzheduhovskaya"},
    ["Bynthva"] = { latitude = 43.156061, longitude = 40.739326, display_name = "Bynthva"},
    ["BYLYM"] = { latitude = 43.461933, longitude = 43.040023, display_name = "BYLYM"},
    ["Bykogorka"] = { latitude = 44.182870, longitude = 42.944364, display_name = "Bykogorka"},
    ["Bulitsku"] = { latitude = 42.353732, longitude = 41.883748, display_name = "Bulitsku"},
    ["Buknari"] = { latitude = 41.999917, longitude = 42.167892, display_name = "Buknari"},
    ["Bratskiy"] = { latitude = 45.233923, longitude = 39.952950, display_name = "Bratskiy"},
    ["Bostana"] = { latitude = 42.550059, longitude = 43.075697, display_name = "Bostana"},
    ["Borodynovka"] = { latitude = 44.146297, longitude = 43.133294, display_name = "Borodynovka"},
    ["Borisovka"] = { latitude = 44.757955, longitude = 37.694273, display_name = "Borisovka"},
    ["Bori"] = { latitude = 42.057428, longitude = 43.117675, display_name = "Bori"},
    ["Borgustanskaya"] = { latitude = 44.054855, longitude = 42.528751, display_name = "Borgustanskaya"},
    ["Bonchkovskiy"] = { latitude = 44.895977, longitude = 38.755977, display_name = "Bonchkovskiy"},
    ["Bolgov"] = { latitude = 45.235017, longitude = 39.885843, display_name = "Bolgov"},
    ["Bol.Raznokol"] = { latitude = 45.146444, longitude = 37.460326, display_name = "Bol.Raznokol"},
    ["Bol'shie Hutora"] = { latitude = 44.747716, longitude = 37.598760, display_name = "Bol'shie Hutora"},
    ["Bol'shesidorovskoe"] = { latitude = 45.036635, longitude = 39.838694, display_name = "Bol'shesidorovskoe"},
    ["Blagoveschenskaya"] = { latitude = 45.057185, longitude = 37.126383, display_name = "Blagoveschenskaya"},
    ["Bia"] = { latitude = 42.344970, longitude = 41.920094, display_name = "Bia"},
    ["Bezymyannoe"] = { latitude = 44.553021, longitude = 39.125341, display_name = "Bezymyannoe"},
    ["Bezengi"] = { latitude = 43.216814, longitude = 43.286057, display_name = "Bezengi"},
    ["Betlemi"] = { latitude = 42.381164, longitude = 42.203732, display_name = "Betlemi"},
    ["Besstrashnaya"] = { latitude = 44.253809, longitude = 41.142638, display_name = "Besstrashnaya"},
    ["Besleney"] = { latitude = 44.245709, longitude = 41.739140, display_name = "Besleney"},
    ["Berezovyy"] = { latitude = 45.150230, longitude = 38.989015, display_name = "Berezovyy"},
    ["Belyy Ugol'"] = { latitude = 44.022682, longitude = 42.805498, display_name = "Belyy Ugol'"},
    ["Belyy"] = { latitude = 45.170924, longitude = 37.268281, display_name = "Belyy"},
    ["Belozernyy"] = { latitude = 45.063400, longitude = 38.674394, display_name = "Belozernyy"},
    ["Belokamenskoe"] = { latitude = 43.883528, longitude = 43.022891, display_name = "Belokamenskoe"},
    ["Beloe"] = { latitude = 45.051372, longitude = 39.647983, display_name = "Beloe"},
    ["Bekeshevskaya"] = { latitude = 44.114902, longitude = 42.433215, display_name = "Bekeshevskaya"},
    ["Bazaleti"] = { latitude = 42.034984, longitude = 43.206468, display_name = "Bazaleti"},
    ["Bateh"] = { latitude = 43.853210, longitude = 43.231378, display_name = "Bateh"},
    ["Bataria"] = { latitude = 42.287102, longitude = 42.014499, display_name = "Bataria"},
    ["Bashi"] = { latitude = 42.566591, longitude = 41.926095, display_name = "Bashi"},
    ["Bardubani"] = { latitude = 42.210034, longitude = 42.886425, display_name = "Bardubani"},
    ["Baranovka"] = { latitude = 43.677734, longitude = 39.708917, display_name = "Baranovka"},
    ["Baranikovskiy"] = { latitude = 45.344972, longitude = 38.014303, display_name = "Baranikovskiy"},
    ["Banodzha"] = { latitude = 42.287579, longitude = 42.657884, display_name = "Banodzha"},
    ["Bandza"] = { latitude = 42.348392, longitude = 42.286405, display_name = "Bandza"},
    ["Baksanenok"] = { latitude = 43.687349, longitude = 43.656569, display_name = "Baksanenok"},
    ["Bakinskaya"] = { latitude = 44.768913, longitude = 39.281876, display_name = "Bakinskaya"},
    ["Bagmarani"] = { latitude = 42.470354, longitude = 41.960753, display_name = "Bagmarani"},
    ["Baglan"] = { latitude = 42.830993, longitude = 41.177605, display_name = "Baglan"},
    ["Bagikyta"] = { latitude = 43.128318, longitude = 40.696648, display_name = "Bagikyta"},
    ["Babugent"] = { latitude = 43.275353, longitude = 43.545330, display_name = "Babugent"},
    ["Azovskaya"] = { latitude = 44.793619, longitude = 38.616989, display_name = "Azovskaya"},
    ["Aushiger"] = { latitude = 43.395553, longitude = 43.735509, display_name = "Aushiger"},
    ["Atydzta"] = { latitude = 43.210846, longitude = 40.392670, display_name = "Atydzta"},
    ["Atsydzhkva"] = { latitude = 43.202142, longitude = 40.342933, display_name = "Atsydzhkva"},
    ["Assokolay"] = { latitude = 44.845762, longitude = 39.469337, display_name = "Assokolay"},
    ["Ashe"] = { latitude = 43.959800, longitude = 39.273025, display_name = "Ashe"},
    ["Asfal'tovaya Gora"] = { latitude = 44.463265, longitude = 39.445555, display_name = "Asfal'tovaya Gora"},
    ["Armyanskiy"] = { latitude = 44.863314, longitude = 37.993278, display_name = "Armyanskiy"},
    ["Arhyz"] = { latitude = 43.565971, longitude = 41.279307, display_name = "Arhyz"},
    ["Arhipovskoe"] = { latitude = 45.011892, longitude = 39.852296, display_name = "Arhipovskoe"},
    ["Argveta"] = { latitude = 42.142478, longitude = 42.986815, display_name = "Argveta"},
    ["Arasadzyh"] = { latitude = 43.233581, longitude = 40.322434, display_name = "Arasadzyh"},
    ["Aosyrhva"] = { latitude = 43.198313, longitude = 40.722300, display_name = "Aosyrhva"},
    ["Anuhva"] = { latitude = 43.121339, longitude = 40.810995, display_name = "Anuhva"},
    ["Anhashtun"] = { latitude = 43.235568, longitude = 40.493729, display_name = "Anhashtun"},
    ["Angisa"] = { latitude = 41.631328, longitude = 41.604173, display_name = "Angisa"},
    ["Andreevskiy"] = { latitude = 44.228128, longitude = 43.626096, display_name = "Andreevskiy"},
    ["Andreevskaya"] = { latitude = 45.320084, longitude = 38.666237, display_name = "Andreevskaya"},
    ["Anastasievskaya"] = { latitude = 45.220343, longitude = 37.887568, display_name = "Anastasievskaya"},
    ["Anapskaya"] = { latitude = 44.900888, longitude = 37.383960, display_name = "Anapskaya"},
    ["Anaklia"] = { latitude = 42.395927, longitude = 41.594732, display_name = "Anaklia"},
    ["Amzara"] = { latitude = 43.092069, longitude = 40.991820, display_name = "Amzara"},
    ["Amtkel"] = { latitude = 43.036020, longitude = 41.317733, display_name = "Amtkel"},
    ["Amsaisi"] = { latitude = 42.135288, longitude = 43.169279, display_name = "Amsaisi"},
    ["Amagleba"] = { latitude = 42.085251, longitude = 42.627377, display_name = "Amagleba"},
    ["Altud"] = { latitude = 43.719412, longitude = 43.869467, display_name = "Altud"},
    ["Alioni"] = { latitude = 42.284723, longitude = 41.949045, display_name = "Alioni"},
    ["Ali-Berdukovskiy"] = { latitude = 43.988847, longitude = 41.737681, display_name = "Ali-Berdukovskiy"},
    ["Aleksee-Tenginskaya"] = { latitude = 45.210939, longitude = 40.179187, display_name = "Aleksee-Tenginskaya"},
    ["Aleksandrovskiy"] = { latitude = 45.255475, longitude = 40.053689, display_name = "Aleksandrovskiy"},
    ["Alaverdi"] = { latitude = 42.052523, longitude = 43.065241, display_name = "Alaverdi"},
    ["Alambari"] = { latitude = 41.827980, longitude = 41.873672, display_name = "Alambari"},
    ["Alahadzy"] = { latitude = 43.221239, longitude = 40.306870, display_name = "Alahadzy"},
    ["Al'piyskoe"] = { latitude = 43.290388, longitude = 40.273733, display_name = "Al'piyskoe"},
    ["Akvara"] = { latitude = 43.239058, longitude = 40.387451, display_name = "Akvara"},
    ["Akvacha"] = { latitude = 43.114639, longitude = 40.833294, display_name = "Akvacha"},
    ["Akapa"] = { latitude = 43.037614, longitude = 41.123880, display_name = "Akapa"},
    ["Akalamra"] = { latitude = 43.111710, longitude = 40.775246, display_name = "Akalamra"},
    ["Ahuti"] = { latitude = 42.471687, longitude = 42.171137, display_name = "Ahuti"},
    ["Ahtanizovskaya"] = { latitude = 45.327959, longitude = 37.106970, display_name = "Ahtanizovskaya"},
    ["Ahmetovskaya"] = { latitude = 44.151458, longitude = 41.050762, display_name = "Ahmetovskaya"},
    ["Ahalsopeli"] = { latitude = 42.251948, longitude = 42.069669, display_name = "Ahalsopeli"},
    ["Ahalsopeli"] = { latitude = 42.310765, longitude = 42.949723, display_name = "Ahalsopeli"},
    ["Ahalsopeli"] = { latitude = 41.579313, longitude = 41.592645, display_name = "Ahalsopeli"},
    ["Ahalsopeli"] = { latitude = 42.166163, longitude = 42.395170, display_name = "Ahalsopeli"},
    ["Ahalsopeli"] = { latitude = 42.054131, longitude = 41.841410, display_name = "Ahalsopeli"},
    ["Ahalsheni"] = { latitude = 41.971235, longitude = 42.443065, display_name = "Ahalsheni"},
    ["Ahalsheni"] = { latitude = 41.624355, longitude = 41.709249, display_name = "Ahalsheni"},
    ["Ahalsheni"] = { latitude = 43.118808, longitude = 41.020391, display_name = "Ahalsheni"},
    ["Ahalkahati"] = { latitude = 42.468560, longitude = 41.733919, display_name = "Ahalkahati"},
    ["Ahali-Terzhola"] = { latitude = 42.232184, longitude = 42.976529, display_name = "Ahali-Terzhola"},
    ["Ahali-Sviri"] = { latitude = 42.161011, longitude = 42.903090, display_name = "Ahali-Sviri"},
    ["Ahali-Kindgi"] = { latitude = 42.797881, longitude = 41.273547, display_name = "Ahali-Kindgi"},
    ["Ahali-Abastumani"] = { latitude = 42.529007, longitude = 41.816390, display_name = "Ahali-Abastumani"},
    ["Ahalhibula"] = { latitude = 42.435747, longitude = 42.009298, display_name = "Ahalhibula"},
    ["Ahalbediseuli"] = { latitude = 42.382299, longitude = 42.478211, display_name = "Ahalbediseuli"},
    ["Agvavera"] = { latitude = 42.733256, longitude = 41.741064, display_name = "Agvavera"},
    ["Aguy_Shapsug"] = { latitude = 44.183957, longitude = 39.065643, display_name = "Aguy_Shapsug"},
    ["Agronom"] = { latitude = 45.143214, longitude = 39.189885, display_name = "Agronom"},
    ["Agoy"] = { latitude = 44.148270, longitude = 39.033715, display_name = "Agoy"},
    ["Agaraki"] = { latitude = 43.201028, longitude = 40.412177, display_name = "Agaraki"},
    ["Afipsip"] = { latitude = 44.995201, longitude = 38.777506, display_name = "Afipsip"},
    ["Adzigezh"] = { latitude = 43.065018, longitude = 40.951904, display_name = "Adzigezh"},
    ["Adzhkhahara"] = { latitude = 43.206934, longitude = 40.489845, display_name = "Adzhkhahara"},
    ["Adzhazhv"] = { latitude = 42.803694, longitude = 41.475439, display_name = "Adzhazhv"},
    ["Adzhapsha"] = { latitude = 43.095728, longitude = 40.735466, display_name = "Adzhapsha"},
    ["Adzhameti"] = { latitude = 42.190927, longitude = 42.795745, display_name = "Adzhameti"},
    ["Adlia"] = { latitude = 41.616935, longitude = 41.603396, display_name = "Adlia"},
    ["Aderbievka"] = { latitude = 44.603960, longitude = 38.106226, display_name = "Aderbievka"},
    ["Adamiy"] = { latitude = 45.072339, longitude = 39.495960, display_name = "Adamiy"},
    ["Adagum"] = { latitude = 45.095386, longitude = 37.722798, display_name = "Adagum"},
    ["Achkvistavi"] = { latitude = 41.829486, longitude = 41.912643, display_name = "Achkvistavi"},
    ["Abzhakva"] = { latitude = 43.025609, longitude = 41.068063, display_name = "Abzhakva"},
    ["Abgarhuk"] = { latitude = 43.115109, longitude = 40.701987, display_name = "Abgarhuk"},
    ["Abedati"] = { latitude = 42.386144, longitude = 42.278763, display_name = "Abedati"},
    ["Abastumani"] = { latitude = 42.396615, longitude = 41.876711, display_name = "Abastumani"},
    ["Abashispiri"] = { latitude = 42.207775, longitude = 42.166122, display_name = "Abashispiri"},
    ["Abanoeti"] = { latitude = 42.539051, longitude = 43.024062, display_name = "Abanoeti"},
    ["Abadzehskaya"] = { latitude = 44.393866, longitude = 40.217713, display_name = "Abadzehskaya"},
    ["Aatsy"] = { latitude = 43.135637, longitude = 40.731036, display_name = "Aatsy"},
    ["Aualitsa"] = { latitude = 43.171207, longitude = 40.665543, display_name = "Aualitsa"},
    ["Mugudzyrhva"] = { latitude = 43.155409, longitude = 40.515015, display_name = "Mugudzyrhva"},
    ["Othara"] = { latitude = 43.228966, longitude = 40.531859, display_name = "Othara"},
    ["Achkatsa"] = { latitude = 43.147649, longitude = 40.685803, display_name = "Achkatsa"},
    ["Tvanaarhu"] = { latitude = 43.197101, longitude = 40.652253, display_name = "Tvanaarhu"},
    ["Duripsh"] = { latitude = 43.206697, longitude = 40.624055, display_name = "Duripsh"},
    ["Abgara"] = { latitude = 43.190179, longitude = 40.624511, display_name = "Abgara"},
    ["Synyrhva"] = { latitude = 43.192922, longitude = 40.552701, display_name = "Synyrhva"},
    ["Dzhirhva"] = { latitude = 43.206313, longitude = 40.548323, display_name = "Dzhirhva"},
    ["Bgardvany"] = { latitude = 43.205393, longitude = 40.595447, display_name = "Bgardvany"},
    ["Arhva"] = { latitude = 43.226315, longitude = 40.572106, display_name = "Arhva"},
    ["Garp"] = { latitude = 43.237879, longitude = 40.546746, display_name = "Garp"},
    ["Adzhimchigra"] = { latitude = 43.167841, longitude = 40.614085, display_name = "Adzhimchigra"},
    ["Adzlagara"] = { latitude = 43.148709, longitude = 40.623547, display_name = "Adzlagara"},
    ["Algyt"] = { latitude = 43.127564, longitude = 40.557333, display_name = "Algyt"},
    ["Mzahva"] = { latitude = 43.137750, longitude = 40.584290, display_name = "Mzahva"},
    ["Ahalsopeli"] = { latitude = 43.141474, longitude = 40.553312, display_name = "Ahalsopeli"},
    ["Tushurebi"] = { latitude = 42.122766, longitude = 44.912051, display_name = "Tushurebi"},
    ["Aloti"] = { latitude = 42.051855, longitude = 44.950279, display_name = "Aloti"},
    ["Kvemo-Chala"] = { latitude = 42.029348, longitude = 44.394200, display_name = "Kvemo-Chala"},
    ["Mchadidzhvari"] = { latitude = 42.020135, longitude = 44.596836, display_name = "Mchadidzhvari"},
    ["Lamiskana"] = { latitude = 42.013894, longitude = 44.490553, display_name = "Lamiskana"},
    ["Igoeti"] = { latitude = 41.991304, longitude = 44.413732, display_name = "Igoeti"},
    ["Okami"] = { latitude = 41.983290, longitude = 44.474383, display_name = "Okami"},
    ["Ksovrisi"] = { latitude = 41.983403, longitude = 44.525294, display_name = "Ksovrisi"},
    ["Magraneti"] = { latitude = 41.933666, longitude = 44.988583, display_name = "Magraneti"},
    ["Misaktsieli"] = { latitude = 41.948382, longitude = 44.738270, display_name = "Misaktsieli"},
    ["Metekhi"] = { latitude = 41.923040, longitude = 44.340959, display_name = "Metekhi"},
    ["Khovle"] = { latitude = 41.895157, longitude = 44.239944, display_name = "Khovle"},
    ["Zemo-Khandaki"] = { latitude = 41.901323, longitude = 44.313301, display_name = "Zemo-Khandaki"},
    ["Garikula"] = { latitude = 41.881470, longitude = 44.334464, display_name = "Garikula"},
    ["Agayani"] = { latitude = 41.913671, longitude = 44.546796, display_name = "Agayani"},
    ["Tskhvarichamia"] = { latitude = 41.880083, longitude = 44.913670, display_name = "Tskhvarichamia"},
    ["Saguramo"] = { latitude = 41.898619, longitude = 44.760362, display_name = "Saguramo"},
    ["Kavtishevi"] = { latitude = 41.856681, longitude = 44.442001, display_name = "Kavtishevi"},
    ["Gorovani"] = { latitude = 41.884935, longitude = 44.670882, display_name = "Gorovani"},
    ["Gldani"] = { latitude = 41.823116, longitude = 44.825815, display_name = "Gldani"},
    ["Dzegvi"] = { latitude = 41.846163, longitude = 44.604396, display_name = "Dzegvi"},
    ["Norio"] = { latitude = 41.790500, longitude = 44.979697, display_name = "Norio"},
    ["Tabakhmela"] = { latitude = 41.653487, longitude = 44.754861, display_name = "Tabakhmela"},
    ["farm Krtsanisi"] = { latitude = 41.615412, longitude = 44.908523, display_name = "farm Krtsanisi"},
    ["Gamardzhveba"] = { latitude = 41.651053, longitude = 44.988995, display_name = "Gamardzhveba"},
    ["Karadzhalari"] = { latitude = 41.622402, longitude = 44.962092, display_name = "Karadzhalari"},
    ["Karatagla"] = { latitude = 41.598333, longitude = 44.978450, display_name = "Karatagla"},
    ["Asureti"] = { latitude = 41.593966, longitude = 44.671603, display_name = "Asureti"},
    ["Tsintskaro"] = { latitude = 41.541691, longitude = 44.617983, display_name = "Tsintskaro"},
    ["Kolagiri"] = { latitude = 41.472666, longitude = 44.715622, display_name = "Kolagiri"},
    ["Azizkendi"] = { latitude = 41.421746, longitude = 44.945430, display_name = "Azizkendi"},
    ["Didi-Mughanlo"] = { latitude = 41.389660, longitude = 44.957250, display_name = "Didi-Mughanlo"},
    ["Kizil-Adzhlo"] = { latitude = 41.480203, longitude = 44.767707, display_name = "Kizil-Adzhlo"},
    ["State Farm Samgori"] = { latitude = 41.597409, longitude = 45.028851, display_name = "State Farm Samgori"},
    ["Birliki"] = { latitude = 41.487802, longitude = 45.072609, display_name = "Birliki"},
    ["Hashmi"] = { latitude = 41.758709, longitude = 45.189443, display_name = "Hashmi"},
    ["Jandari"] = { latitude = 41.447936, longitude = 45.168489, display_name = "Jandari"},
    ["Nazarlo"] = { latitude = 41.423063, longitude = 45.111234, display_name = "Nazarlo"},
    ["Patardzeuli"] = { latitude = 41.744385, longitude = 45.248301, display_name = "Patardzeuli"},
    ["Zhinvali"] = { latitude = 42.145771, longitude = 44.772532, display_name = "Zhinvali"},
    ["Zhinvali"] = { latitude = 42.109544, longitude = 44.765639, display_name = "Zhinvali"},
    ["Tianeti"] = { latitude = 42.109786, longitude = 44.965735, display_name = "Tianeti"},
    ["Didi-Lilo"] = { latitude = 41.737000, longitude = 44.964292, display_name = "Didi-Lilo"},
    ["Manglisi"] = { latitude = 41.699392, longitude = 44.373647, display_name = "Manglisi"},
    ["ZAGES"] = { latitude = 41.825864, longitude = 44.757579, display_name = "ZAGES"},
    ["Sioni"] = { latitude = 41.990655, longitude = 45.028461, display_name = "Sioni"},
    ["Metekhi"] = { latitude = 41.942381, longitude = 44.339043, display_name = "Metekhi"},
    ["settlement workers"] = { latitude = 41.871657, longitude = 44.723509, display_name = "settlement workers"},
    ["p.Hramzavodstroya"] = { latitude = 41.671265, longitude = 44.916548, display_name = "p.Hramzavodstroya"},
    ["Kiketi"] = { latitude = 41.653426, longitude = 44.650357, display_name = "Kiketi"},
    ["Vaziani"] = { latitude = 41.693761, longitude = 45.053892, display_name = "Vaziani"},
    ["MTSKHETA"] = { latitude = 41.836870, longitude = 44.696831, display_name = "MTSKHETA"},
    ["Dzartsemi"] = { latitude = 42.301195, longitude = 43.966521, display_name = "Dzartsemi"},
    ["Dzari"] = { latitude = 42.292148, longitude = 43.872880, display_name = "Dzari"},
    ["Zemo-Dodoti"] = { latitude = 42.270493, longitude = 43.888601, display_name = "Zemo-Dodoti"},
    ["Khetagurov"] = { latitude = 42.212180, longitude = 43.892906, display_name = "Khetagurov"},
    ["Ergneti"] = { latitude = 42.198008, longitude = 43.993198, display_name = "Ergneti"},
    ["Zemo-Nikozi"] = { latitude = 42.198215, longitude = 43.959361, display_name = "Zemo-Nikozi"},
    ["Avnevi"] = { latitude = 42.194477, longitude = 43.876406, display_name = "Avnevi"},
    ["Didmukha"] = { latitude = 42.175856, longitude = 43.880354, display_name = "Didmukha"},
    ["Phvenisi"] = { latitude = 42.157559, longitude = 43.992365, display_name = "Phvenisi"},
    ["Dirbi"] = { latitude = 42.113711, longitude = 43.874980, display_name = "Dirbi"},
    ["Sakasheti"] = { latitude = 42.093926, longitude = 43.970841, display_name = "Sakasheti"},
    ["Dzlevidzhvari"] = { latitude = 42.106787, longitude = 43.929563, display_name = "Dzlevidzhvari"},
    ["Tsveri"] = { latitude = 42.074543, longitude = 43.886696, display_name = "Tsveri"},
    ["Kvemo-Hvedureti"] = { latitude = 42.004504, longitude = 43.933894, display_name = "Kvemo-Hvedureti"},
    ["Charebi"] = { latitude = 42.266892, longitude = 44.113843, display_name = "Charebi"},
    ["Satihari"] = { latitude = 42.256968, longitude = 44.085042, display_name = "Satihari"},
    ["Eredvi"] = { latitude = 42.246823, longitude = 44.035496, display_name = "Eredvi"},
    ["Berula"] = { latitude = 42.237767, longitude = 44.024776, display_name = "Berula"},
    ["Mereti"] = { latitude = 42.225721, longitude = 44.076519, display_name = "Mereti"},
    ["Kvemo-Mahisi"] = { latitude = 42.212282, longitude = 44.229800, display_name = "Kvemo-Mahisi"},
    ["Karbi"] = { latitude = 42.199130, longitude = 44.075680, display_name = "Karbi"},
    ["Ditsi"] = { latitude = 42.209424, longitude = 44.032437, display_name = "Ditsi"},
    ["Megvrekisi"] = { latitude = 42.182147, longitude = 44.003578, display_name = "Megvrekisi"},
    ["Brotsleti"] = { latitude = 42.179239, longitude = 44.035211, display_name = "Brotsleti"},
    ["Didi-Gromi"] = { latitude = 42.159974, longitude = 44.216156, display_name = "Didi-Gromi"},
    ["Goyata"] = { latitude = 42.150563, longitude = 44.160480, display_name = "Goyata"},
    ["Plavi"] = { latitude = 42.167888, longitude = 44.110547, display_name = "Plavi"},
    ["Tkviavi"] = { latitude = 42.158148, longitude = 44.068454, display_name = "Tkviavi"},
    ["Shindisi"] = { latitude = 42.129670, longitude = 44.009758, display_name = "Shindisi"},
    ["Marana"] = { latitude = 42.143492, longitude = 44.060515, display_name = "Marana"},
    ["Kitsnisi"] = { latitude = 42.128901, longitude = 44.091163, display_name = "Kitsnisi"},
    ["Kvemo-Artsevi"] = { latitude = 42.137430, longitude = 44.123166, display_name = "Kvemo-Artsevi"},
    ["Medzhudispiri"] = { latitude = 42.130753, longitude = 44.197309, display_name = "Medzhudispiri"},
    ["Patara-Medzhvrishevi"] = { latitude = 42.138801, longitude = 44.215694, display_name = "Patara-Medzhvrishevi"},
    ["Zerti"] = { latitude = 42.105513, longitude = 44.220610, display_name = "Zerti"},
    ["Ahrisi"] = { latitude = 42.113127, longitude = 44.169207, display_name = "Ahrisi"},
    ["Satemo"] = { latitude = 42.111461, longitude = 44.109531, display_name = "Satemo"},
    ["Dzevera"] = { latitude = 42.118587, longitude = 44.052329, display_name = "Dzevera"},
    ["Variani"] = { latitude = 42.076311, longitude = 44.033619, display_name = "Variani"},
    ["Kvarhiti"] = { latitude = 42.080103, longitude = 44.194531, display_name = "Kvarhiti"},
    ["Zegduleti"] = { latitude = 42.053744, longitude = 44.222579, display_name = "Zegduleti"},
    ["Heltubani"] = { latitude = 42.051003, longitude = 44.153117, display_name = "Heltubani"},
    ["Arashenda"] = { latitude = 42.055347, longitude = 44.026474, display_name = "Arashenda"},
    ["Reha"] = { latitude = 42.037624, longitude = 44.116712, display_name = "Reha"},
    ["Sveneti"] = { latitude = 42.027015, longitude = 44.147642, display_name = "Sveneti"},
    ["Otarasheni"] = { latitude = 42.013074, longitude = 44.086605, display_name = "Otarasheni"},
    ["Kldu"] = { latitude = 41.983296, longitude = 43.856146, display_name = "Kldu"},
    ["Vedreba"] = { latitude = 41.985291, longitude = 43.821404, display_name = "Vedreba"},
    ["Heoba"] = { latitude = 41.970626, longitude = 43.912736, display_name = "Heoba"},
    ["Gvleti"] = { latitude = 41.979952, longitude = 43.971435, display_name = "Gvleti"},
    ["Skra"] = { latitude = 41.997101, longitude = 44.011659, display_name = "Skra"},
    ["Tinihidi"] = { latitude = 41.992653, longitude = 44.084712, display_name = "Tinihidi"},
    ["Kvahvreli"] = { latitude = 41.959105, longitude = 44.218573, display_name = "Kvahvreli"},
    ["Hidistavi"] = { latitude = 41.959612, longitude = 44.134644, display_name = "Hidistavi"},
    ["Bnavisi"] = { latitude = 41.953044, longitude = 44.063967, display_name = "Bnavisi"},
    ["settlement workers"] = { latitude = 42.098091, longitude = 44.177467, display_name = "settlement workers"},
    ["Tedeleti"] = { latitude = 42.415927, longitude = 43.608474, display_name = "Tedeleti"},
    ["Dzhalabeti"] = { latitude = 42.405402, longitude = 43.651559, display_name = "Dzhalabeti"},
    ["Perevi"] = { latitude = 42.371801, longitude = 43.598444, display_name = "Perevi"},
    ["Dzhriya"] = { latitude = 42.348672, longitude = 43.582496, display_name = "Dzhriya"},
    ["Darka"] = { latitude = 42.332744, longitude = 43.555666, display_name = "Darka"},
    ["Didi-Tsihiata"] = { latitude = 42.265642, longitude = 43.777229, display_name = "Didi-Tsihiata"},
    ["Kornisi"] = { latitude = 42.273295, longitude = 43.820488, display_name = "Kornisi"},
    ["Bekmari"] = { latitude = 42.251183, longitude = 43.815920, display_name = "Bekmari"},
    ["Ahalsheni"] = { latitude = 42.229380, longitude = 43.768622, display_name = "Ahalsheni"},
    ["Nedlati"] = { latitude = 42.215220, longitude = 43.770585, display_name = "Nedlati"},
    ["Samtskaro"] = { latitude = 42.200998, longitude = 43.833958, display_name = "Samtskaro"},
    ["Nabakevi"] = { latitude = 42.187277, longitude = 43.770297, display_name = "Nabakevi"},
    ["Balta"] = { latitude = 42.181644, longitude = 43.720810, display_name = "Balta"},
    ["Khvani"] = { latitude = 42.198434, longitude = 43.533608, display_name = "Khvani"},
    ["Chalovani"] = { latitude = 42.179914, longitude = 43.504613, display_name = "Chalovani"},
    ["Caleti"] = { latitude = 42.168803, longitude = 43.717894, display_name = "Caleti"},
    ["Atotsi"] = { latitude = 42.152690, longitude = 43.747844, display_name = "Atotsi"},
    ["Lychee"] = { latitude = 42.155902, longitude = 43.482475, display_name = "Lychee"},
    ["Tsagvli"] = { latitude = 42.120081, longitude = 43.698204, display_name = "Tsagvli"},
    ["Satsihuri"] = { latitude = 42.118858, longitude = 43.731642, display_name = "Satsihuri"},
    ["Bredza"] = { latitude = 42.129362, longitude = 43.745254, display_name = "Bredza"},
    ["Abisi"] = { latitude = 42.094465, longitude = 43.763261, display_name = "Abisi"},
    ["Ptsa"] = { latitude = 42.086530, longitude = 43.786340, display_name = "Ptsa"},
    ["Tkotsa"] = { latitude = 42.093459, longitude = 43.702879, display_name = "Tkotsa"},
    ["Tshetisdzhvari"] = { latitude = 42.105024, longitude = 43.651165, display_name = "Tshetisdzhvari"},
    ["Shaved"] = { latitude = 42.077063, longitude = 43.610677, display_name = "Shaved"},
    ["Didi-Plevi"] = { latitude = 42.073444, longitude = 43.698010, display_name = "Didi-Plevi"},
    ["Mohisi"] = { latitude = 42.053041, longitude = 43.769400, display_name = "Mohisi"},
    ["Vaca"] = { latitude = 42.043164, longitude = 43.713762, display_name = "Vaca"},
    ["Nabahtevi"] = { latitude = 42.060954, longitude = 43.666875, display_name = "Nabahtevi"},
    ["Tsotshnara"] = { latitude = 42.050883, longitude = 43.579242, display_name = "Tsotshnara"},
    ["Gomi"] = { latitude = 42.020584, longitude = 43.725902, display_name = "Gomi"},
    ["Kvishheti"] = { latitude = 41.968277, longitude = 43.501974, display_name = "Kvishheti"},
    ["Savanisubani"] = { latitude = 41.987763, longitude = 43.524168, display_name = "Savanisubani"},
    ["Htsisi"] = { latitude = 41.982473, longitude = 43.675254, display_name = "Htsisi"},
    ["Gverdzineti"] = { latitude = 41.934464, longitude = 43.712739, display_name = "Gverdzineti"},
    ["Patara-Keleti"] = { latitude = 41.978368, longitude = 43.747280, display_name = "Patara-Keleti"},
    ["Sukaantubani"] = { latitude = 41.966189, longitude = 43.782483, display_name = "Sukaantubani"},
    ["Surami"] = { latitude = 42.023579, longitude = 43.551661, display_name = "Surami"},
    ["Kvomo-Hvtse"] = { latitude = 42.417978, longitude = 43.956401, display_name = "Kvomo-Hvtse"},
    ["Garbani"] = { latitude = 42.607571, longitude = 44.583726, display_name = "Garbani"},
    ["Hevsha"] = { latitude = 42.397988, longitude = 44.683859, display_name = "Hevsha"},
    ["Chargali"] = { latitude = 42.329329, longitude = 44.920772, display_name = "Chargali"},
    ["KAZBEGI"] = { latitude = 42.659479, longitude = 44.641080, display_name = "KAZBEGI"},
    ["NIGNIY PASANAURI"] = { latitude = 42.395430, longitude = 44.649680, display_name = "NIGNIY PASANAURI"},
    ["PASANAURI"] = { latitude = 42.354728, longitude = 44.689155, display_name = "PASANAURI"},
    ["MALIY PASANAURI"] = { latitude = 42.352053, longitude = 44.705106, display_name = "MALIY PASANAURI"},
    ["NIGNIY PASANAURI"] = { latitude = 42.327877, longitude = 44.681794, display_name = "NIGNIY PASANAURI"},
    ["Pavlodolskaya"] = { latitude = 43.725429, longitude = 44.476820, display_name = "Pavlodolskaya"},
    ["Kalininskiy"] = { latitude = 43.728693, longitude = 44.688696, display_name = "Kalininskiy"},
    ["Stoderevskaya"] = { latitude = 43.725108, longitude = 44.841465, display_name = "Stoderevskaya"},
    ["Kievskoye"] = { latitude = 43.706989, longitude = 44.649667, display_name = "Kievskoye"},
    ["Kizlyar"] = { latitude = 43.706249, longitude = 44.597507, display_name = "Kizlyar"},
    ["Razdolnoe"] = { latitude = 43.698447, longitude = 44.537709, display_name = "Razdolnoe"},
    ["Vinogradnoye"] = { latitude = 43.699684, longitude = 44.492810, display_name = "Vinogradnoye"},
    ["Novoosetinskaya"] = { latitude = 43.705838, longitude = 44.391716, display_name = "Novoosetinskaya"},
    ["Hamidiye"] = { latitude = 43.675286, longitude = 44.377288, display_name = "Hamidiye"},
    ["Suhotskoe"] = { latitude = 43.678340, longitude = 44.440575, display_name = "Suhotskoe"},
    ["Bratskoye"] = { latitude = 43.654801, longitude = 44.890888, display_name = "Bratskoye"},
    ["Chkalovo"] = { latitude = 43.525346, longitude = 44.861239, display_name = "Chkalovo"},
    ["Voznesenskaya"] = { latitude = 43.544554, longitude = 44.749833, display_name = "Voznesenskaya"},
    ["Stariy Malgobek"] = { latitude = 43.547550, longitude = 44.576478, display_name = "Stariy Malgobek"},
    ["Yugnoye"] = { latitude = 43.517917, longitude = 44.743613, display_name = "Yugnoye"},
    ["Noviy Redant"] = { latitude = 43.473166, longitude = 44.812043, display_name = "Noviy Redant"},
    ["Hurikau"] = { latitude = 43.457393, longitude = 44.459515, display_name = "Hurikau"},
    ["Nigniye Achaluki"] = { latitude = 43.402997, longitude = 44.763903, display_name = "Nigniye Achaluki"},
    ["Sredniye Achaluki"] = { latitude = 43.371071, longitude = 44.731465, display_name = "Sredniye Achaluki"},
    ["Stariy Bataksyurt"] = { latitude = 43.377826, longitude = 44.539760, display_name = "Stariy Bataksyurt"},
    ["Verhniy Kurpie"] = { latitude = 43.481149, longitude = 44.372481, display_name = "Verhniy Kurpie"},
    ["Zamankul"] = { latitude = 43.349395, longitude = 44.405192, display_name = "Zamankul"},
    ["Coban"] = { latitude = 42.917909, longitude = 44.478096, display_name = "Coban"},
    ["Tarskoye"] = { latitude = 42.966945, longitude = 44.776350, display_name = "Tarskoye"},
    ["Kardzhin"] = { latitude = 43.275762, longitude = 44.304301, display_name = "Kardzhin"},
    ["Darg-Koh"] = { latitude = 43.270288, longitude = 44.363370, display_name = "Darg-Koh"},
    ["Brut"] = { latitude = 43.269754, longitude = 44.443816, display_name = "Brut"},
    ["Humalag"] = { latitude = 43.241187, longitude = 44.478346, display_name = "Humalag"},
    ["Zilga"] = { latitude = 43.239199, longitude = 44.522993, display_name = "Zilga"},
    ["Dalakova"] = { latitude = 43.240951, longitude = 44.588774, display_name = "Dalakova"},
    ["Fahrn"] = { latitude = 43.181612, longitude = 44.497644, display_name = "Fahrn"},
    ["Kirovo"] = { latitude = 43.176702, longitude = 44.404846, display_name = "Kirovo"},
    ["Noviy Batakoyurt"] = { latitude = 43.220011, longitude = 44.498692, display_name = "Noviy Batakoyurt"},
    ["Kadgaron"] = { latitude = 43.134260, longitude = 44.327729, display_name = "Kadgaron"},
    ["Ali-Yurt"] = { latitude = 43.143623, longitude = 44.855216, display_name = "Ali-Yurt"},
    ["Galashki"] = { latitude = 43.084641, longitude = 44.986559, display_name = "Galashki"},
    ["Dongaron"] = { latitude = 43.108707, longitude = 44.720818, display_name = "Dongaron"},
    ["Oktyabrskoe"] = { latitude = 43.053531, longitude = 44.746773, display_name = "Oktyabrskoe"},
    ["Arhonskaya"] = { latitude = 43.109215, longitude = 44.514273, display_name = "Arhonskaya"},
    ["Nart"] = { latitude = 43.119502, longitude = 44.429664, display_name = "Nart"},
    ["Komgaron"] = { latitude = 43.054635, longitude = 44.873359, display_name = "Komgaron"},
    ["Mayramadag"] = { latitude = 43.022685, longitude = 44.480014, display_name = "Mayramadag"},
    ["Dzuarikau"] = { latitude = 43.021167, longitude = 44.406716, display_name = "Dzuarikau"},
    ["Nowaya Sabiba"] = { latitude = 43.042338, longitude = 44.533605, display_name = "Nowaya Sabiba"},
    ["Hataldon"] = { latitude = 43.037818, longitude = 44.359631, display_name = "Hataldon"},
    ["Terk"] = { latitude = 42.931648, longitude = 44.661428, display_name = "Terk"},
    ["Bamut"] = { latitude = 43.153786, longitude = 45.199457, display_name = "Bamut"},
    ["KARABULAK"] = { latitude = 43.308244, longitude = 44.902324, display_name = "KARABULAK"},
    ["Sett. Chapaeva"] = { latitude = 43.544240, longitude = 44.659527, display_name = "Sett. Chapaeva"},
    ["Sett. Sheripova"] = { latitude = 43.542711, longitude = 44.617595, display_name = "Sett. Sheripova"},
    ["Barzikau"] = { latitude = 42.840858, longitude = 44.311978, display_name = "Barzikau"},
    ["Lats"] = { latitude = 42.830939, longitude = 44.291897, display_name = "Lats"},
    ["Maloe Kantyshevo"] = { latitude = 43.215812, longitude = 44.676116, display_name = "Maloe Kantyshevo"},
    ["Malie Galashki"] = { latitude = 43.119389, longitude = 44.991516, display_name = "Malie Galashki"},
    ["Chermen"] = { latitude = 43.124973, longitude = 44.707396, display_name = "Chermen"},
    ["YUGNIY"] = { latitude = 42.965960, longitude = 44.689516, display_name = "YUGNIY"},
    ["KARTSA"] = { latitude = 43.044612, longitude = 44.729882, display_name = "KARTSA"},
    ["Verhniy Komgaron"] = { latitude = 43.056066, longitude = 44.907189, display_name = "Verhniy Komgaron"},
    ["Redant 2nd"] = { latitude = 42.988657, longitude = 44.670222, display_name = "Redant 2nd"},
    ["DATCHNOE"] = { latitude = 42.976949, longitude = 44.670218, display_name = "DATCHNOE"},
    ["Redant 1st"] = { latitude = 42.961780, longitude = 44.658172, display_name = "Redant 1st"},
    ["Maliy Terk"] = { latitude = 42.910962, longitude = 44.641989, display_name = "Maliy Terk"},
    ["Novopoltavskoe"] = { latitude = 43.690510, longitude = 43.971403, display_name = "Novopoltavskoe"},
    ["Novoivanovskoe"] = { latitude = 43.640512, longitude = 43.957150, display_name = "Novoivanovskoe"},
    ["Priblizhnaya"] = { latitude = 43.771987, longitude = 44.123433, display_name = "Priblizhnaya"},
    ["Yrogainoe"] = { latitude = 43.702547, longitude = 44.216846, display_name = "Yrogainoe"},
    ["Terekskoe"] = { latitude = 43.673364, longitude = 44.297188, display_name = "Terekskoe"},
    ["Stavd-Durta"] = { latitude = 43.362683, longitude = 44.056242, display_name = "Stavd-Durta"},
    ["Zmeyskaya"] = { latitude = 43.344694, longitude = 44.149100, display_name = "Zmeyskaya"},
    ["Arik"] = { latitude = 43.581259, longitude = 44.125132, display_name = "Arik"},
    ["Verh.Akbash"] = { latitude = 43.474962, longitude = 44.235607, display_name = "Verh.Akbash"},
    ["Planovskoye"] = { latitude = 43.403198, longitude = 44.200263, display_name = "Planovskoye"},
    ["Lesken"] = { latitude = 43.277818, longitude = 43.829816, display_name = "Lesken"},
    ["Chikola"] = { latitude = 43.188667, longitude = 43.919901, display_name = "Chikola"},
    ["Ahsay"] = { latitude = 42.957234, longitude = 43.717210, display_name = "Ahsay"},
    ["Galiat"] = { latitude = 42.924405, longitude = 43.849611, display_name = "Galiat"},
    ["Verhniy Tsey"] = { latitude = 42.803812, longitude = 43.939140, display_name = "Verhniy Tsey"},
    ["Verhniy Zaramag"] = { latitude = 42.699861, longitude = 43.961273, display_name = "Verhniy Zaramag"},
    ["Tib"] = { latitude = 42.673957, longitude = 43.909848, display_name = "Tib"},
    ["Dur-Dur"] = { latitude = 43.122325, longitude = 44.026365, display_name = "Dur-Dur"},
    ["Khora"] = { latitude = 43.083592, longitude = 44.067681, display_name = "Khora"},
    ["Hod"] = { latitude = 42.878956, longitude = 44.011978, display_name = "Hod"},
    ["Nogkau"] = { latitude = 42.869024, longitude = 44.044631, display_name = "Nogkau"},
    ["Gusoyta"] = { latitude = 42.866307, longitude = 44.066896, display_name = "Gusoyta"},
    ["Nigniy Unal"] = { latitude = 42.863339, longitude = 44.151339, display_name = "Nigniy Unal"},
    ["Chasavali"] = { latitude = 42.527264, longitude = 43.645950, display_name = "Chasavali"},
    ["Cobet"] = { latitude = 42.524742, longitude = 43.769973, display_name = "Cobet"},
    ["Kasagini"] = { latitude = 42.489340, longitude = 43.728148, display_name = "Kasagini"},
    ["Biteta"] = { latitude = 42.470286, longitude = 43.696121, display_name = "Biteta"},
    ["Hampalgomi"] = { latitude = 42.456412, longitude = 43.736447, display_name = "Hampalgomi"},
    ["Dadikau"] = { latitude = 42.463106, longitude = 43.755227, display_name = "Dadikau"},
    ["Ertso"] = { latitude = 42.463195, longitude = 43.778098, display_name = "Ertso"},
    ["Muguti"] = { latitude = 42.426175, longitude = 43.931004, display_name = "Muguti"},
    ["Kotanto"] = { latitude = 42.435038, longitude = 43.842742, display_name = "Kotanto"},
    ["Kvemo-Korsevi"] = { latitude = 42.404016, longitude = 43.870838, display_name = "Kvemo-Korsevi"},
    ["Stariye Kvemo-Korsevi"] = { latitude = 42.411866, longitude = 43.873899, display_name = "Stariye Kvemo-Korsevi"},
    ["Sakire"] = { latitude = 42.378590, longitude = 43.910177, display_name = "Sakire"},
    ["Didi-Gupta"] = { latitude = 42.352565, longitude = 43.902265, display_name = "Didi-Gupta"},
    ["Kvemo-Sba"] = { latitude = 42.569275, longitude = 44.168972, display_name = "Kvemo-Sba"},
    ["Zemo-Roka"] = { latitude = 42.578612, longitude = 44.119959, display_name = "Zemo-Roka"},
    ["Kvemo-Roka"] = { latitude = 42.546843, longitude = 44.115709, display_name = "Kvemo-Roka"},
    ["Edisa"] = { latitude = 42.538407, longitude = 44.215896, display_name = "Edisa"},
    ["Kvemo-Khoshka"] = { latitude = 42.468209, longitude = 44.057935, display_name = "Kvemo-Khoshka"},
    ["Elbakita"] = { latitude = 42.428988, longitude = 44.005024, display_name = "Elbakita"},
    ["Tsru"] = { latitude = 42.383653, longitude = 44.022967, display_name = "Tsru"},
    ["Shua-Tshviri"] = { latitude = 42.372013, longitude = 44.184655, display_name = "Shua-Tshviri"},
    ["Klarsi"] = { latitude = 42.353604, longitude = 44.051562, display_name = "Klarsi"},
    ["Tsiara"] = { latitude = 42.353420, longitude = 44.022577, display_name = "Tsiara"},
    ["VERHNIY ZGID"] = { latitude = 42.871325, longitude = 43.960613, display_name = "VERHNIY ZGID"},
    ["SADON"] = { latitude = 42.852012, longitude = 43.995197, display_name = "SADON"},
    ["BURON"] = { latitude = 42.795036, longitude = 44.006793, display_name = "BURON"},
    ["MIZUR"] = { latitude = 42.851027, longitude = 44.056755, display_name = "MIZUR"},
    ["JAVA"] = { latitude = 42.396920, longitude = 43.926887, display_name = "JAVA"},
    ["MAISKIY"] = { latitude = 43.641799, longitude = 44.033974, display_name = "MAISKIY"},
    ["Uvarovskoye"] = { latitude = 43.815010, longitude = 44.427222, display_name = "Uvarovskoye"},
    ["Inarkiev"] = { latitude = 43.472488, longitude = 44.543341, display_name = "Inarkiev"},
    ["Verhniye Achaluki"] = { latitude = 43.350538, longitude = 44.699020, display_name = "Verhniye Achaluki"},
    ["Yandyrka"] = { latitude = 43.273597, longitude = 44.916689, display_name = "Yandyrka"},
    ["Ekazhevo"] = { latitude = 43.210327, longitude = 44.823799, display_name = "Ekazhevo"},
    ["Surkhakhi"] = { latitude = 43.187870, longitude = 44.905782, display_name = "Surkhakhi"},
    ["Chermen"] = { latitude = 43.148441, longitude = 44.712624, display_name = "Chermen"},
    ["Olginskoe"] = { latitude = 43.161052, longitude = 44.691985, display_name = "Olginskoe"},
    ["Sunzha"] = { latitude = 43.057253, longitude = 44.825846, display_name = "Sunzha"},
    ["Gizel"] = { latitude = 43.047232, longitude = 44.567211, display_name = "Gizel"},
    ["Nesterovskaya"] = { latitude = 43.239607, longitude = 45.059246, display_name = "Nesterovskaya"},
    ["MALGOBEK"] = { latitude = 43.518590, longitude = 44.599399, display_name = "MALGOBEK"},
    ["Carman"] = { latitude = 43.109120, longitude = 44.115426, display_name = "Carman"},
    ["Ursdon"] = { latitude = 43.095719, longitude = 44.085430, display_name = "Ursdon"},
    ["DIGORA"] = { latitude = 43.159497, longitude = 44.161233, display_name = "DIGORA"},
    ["Dzalisi"] = { latitude = 41.963611, longitude = 44.605056, display_name = "Dzalisi"},
    ["Digomi"] = { latitude = 41.770418, longitude = 44.741961, display_name = "Digomi"},
    ["Kumisi"] = { latitude = 41.615003, longitude = 44.781197, display_name = "Kumisi"},
    ["Algeti"] = { latitude = 41.444696, longitude = 44.905321, display_name = "Algeti"},
    ["Tsereteli"] = { latitude = 41.449287, longitude = 44.822309, display_name = "Tsereteli"},
    ["Akhali-Samgori"] = { latitude = 41.571700, longitude = 45.074511, display_name = "Akhali-Samgori"},
    ["Ulyanovka"] = { latitude = 41.389915, longitude = 45.118498, display_name = "Ulyanovka"},
    ["Sadyhly"] = { latitude = 41.374920, longitude = 45.144642, display_name = "Sadyhly"},
    ["Tskneti"] = { latitude = 41.691738, longitude = 44.698812, display_name = "Tskneti"},
    ["TETRA-Tskaro"] = { latitude = 41.547350, longitude = 44.468209, display_name = "TETRA-Tskaro"},
    ["Kurta"] = { latitude = 42.283254, longitude = 43.956290, display_name = "Kurta"},
    ["Kekhvi"] = { latitude = 42.307566, longitude = 43.940875, display_name = "Kekhvi"},
    ["Russkoye"] = { latitude = 43.835069, longitude = 44.579918, display_name = "Russkoye"},
    ["Novogeorgievskoe"] = { latitude = 43.763115, longitude = 44.703801, display_name = "Novogeorgievskoe"},
    ["Veselovskoye"] = { latitude = 43.771709, longitude = 44.727163, display_name = "Veselovskoye"},
    ["Barsuki"] = { latitude = 43.263814, longitude = 44.810861, display_name = "Barsuki"},
    ["Pliyevo"] = { latitude = 43.283551, longitude = 44.840391, display_name = "Pliyevo"},
    ["Kantyshevo"] = { latitude = 43.230063, longitude = 44.631288, display_name = "Kantyshevo"},
    ["Nogir"] = { latitude = 43.078872, longitude = 44.638090, display_name = "Nogir"},
    ["Troickaya"] = { latitude = 43.305363, longitude = 45.010858, display_name = "Troickaya"},
    ["ZAVODSKOY"] = { latitude = 43.098096, longitude = 44.654284, display_name = "ZAVODSKOY"},
    ["BESLAN"] = { latitude = 43.195476, longitude = 44.531621, display_name = "BESLAN"},
    ["Nazran"] = { latitude = 43.226212, longitude = 44.777861, display_name = "Nazran"},
    ["Ekaterinogradskaya"] = { latitude = 43.766466, longitude = 44.232838, display_name = "Ekaterinogradskaya"},
    ["Elhotovo"] = { latitude = 43.356191, longitude = 44.210087, display_name = "Elhotovo"},
    ["TEREK"] = { latitude = 43.482380, longitude = 44.141438, display_name = "TEREK"},
    ["ALAGIR"] = { latitude = 43.039424, longitude = 44.220280, display_name = "ALAGIR"},
    ["Aleksandrovskaya"] = { latitude = 43.485711, longitude = 44.068899, display_name = "Aleksandrovskaya"},
    ["Ikoti"] = { latitude = 42.152918, longitude = 44.495123, display_name = "Ikoti"},
    ["Mukhrani"] = { latitude = 41.936298, longitude = 44.575647, display_name = "Mukhrani"},
    ["Martkobi"] = { latitude = 41.787685, longitude = 45.020138, display_name = "Martkobi"},
    ["Mughanlo"] = { latitude = 41.731105, longitude = 45.160083, display_name = "Mughanlo"},
    ["Sartichala"] = { latitude = 41.709697, longitude = 45.171715, display_name = "Sartichala"},
    ["Leningori"] = { latitude = 42.131932, longitude = 44.485444, display_name = "Leningori"},
    ["Lilo"] = { latitude = 41.681176, longitude = 44.976380, display_name = "Lilo"},
    ["Dusheti"] = { latitude = 42.084899, longitude = 44.689336, display_name = "Dusheti"},
    ["KASPI"] = { latitude = 41.924404, longitude = 44.425303, display_name = "KASPI"},
    ["Gardabani"] = { latitude = 41.461640, longitude = 45.092311, display_name = "Gardabani"},
    ["Sagarejo"] = { latitude = 41.728389, longitude = 45.332577, display_name = "Sagarejo"},
    ["TSKHINVALI"] = { latitude = 42.230304, longitude = 43.970695, display_name = "TSKHINVALI"},
    ["GORI"] = { latitude = 41.983833, longitude = 44.110383, display_name = "GORI"},
    ["AGARA"] = { latitude = 42.044088, longitude = 43.826962, display_name = "AGARA"},
    ["KARELI"] = { latitude = 42.020888, longitude = 43.892648, display_name = "KARELI"},
    ["Ruisi"] = { latitude = 42.035966, longitude = 43.964884, display_name = "Ruisi"},
    ["Karaleti"] = { latitude = 42.068062, longitude = 44.091374, display_name = "Karaleti"},
    ["MOZDOK"] = { latitude = 43.752121, longitude = 44.640819, display_name = "MOZDOK"},
    ["VLADIKAVKAZ"] = { latitude = 43.029636, longitude = 44.679665, display_name = "VLADIKAVKAZ"},
    ["PROHLADNIY"] = { latitude = 43.756426, longitude = 44.038869, display_name = "PROHLADNIY"},
    ["MAYSKIY"] = { latitude = 43.630769, longitude = 44.066323, display_name = "MAYSKIY"},
    ["Kurtat"] = { latitude = 43.071083, longitude = 44.751840, display_name = "Kurtat"},
    ["RUSTAVI"] = { latitude = 41.559705, longitude = 44.986424, display_name = "RUSTAVI"},
    ["Marneuli"] = { latitude = 41.479596, longitude = 44.808583, display_name = "Marneuli"},
    ["TBILISI"] = { latitude = 41.736457, longitude = 44.825608, display_name = "TBILISI"},
    ["KHASHURI"] = { latitude = 41.985651, longitude = 43.604230, display_name = "KHASHURI"},
}

veafNamedPoints._citiesPersianGulf =
{
    ["Aqr"] = { latitude = 24.810000, longitude = 56.440000, display_name = "Aqr"},
    ["Al Hadd"] = { latitude = 24.490000, longitude = 56.590000, display_name = "Al Hadd"},
    ["Magan"] = { latitude = 24.420000, longitude = 56.580000, display_name = "Magan"},
    ["Lar"] = { latitude = 27.661306, longitude = 54.323267, display_name = "Lar"},
    ["Bukha"] = { latitude = 26.140000, longitude = 56.150000, display_name = "Bukha"},
    ["Al Khadhrawain"] = { latitude = 24.850000, longitude = 56.400000, display_name = "Al Khadhrawain"},
    ["Al Bulaydah"] = { latitude = 24.840000, longitude = 56.410000, display_name = "Al Bulaydah"},
    ["Al Wadiyat"] = { latitude = 24.800180, longitude = 56.450043, display_name = "Al Wadiyat"},
    ["Dabbagh"] = { latitude = 24.540000, longitude = 56.560000, display_name = "Dabbagh"},
    ["Sallan"] = { latitude = 24.394327, longitude = 56.726862, display_name = "Sallan"},
    ["Majhal"] = { latitude = 24.470000, longitude = 56.330000, display_name = "Majhal"},
    ["An Naqdah"] = { latitude = 24.500000, longitude = 56.590000, display_name = "An Naqdah"},
    ["Al Liwa"] = { latitude = 24.540000, longitude = 56.570000, display_name = "Al Liwa"},
    ["Lekfayir"] = { latitude = 26.040000, longitude = 56.350000, display_name = "Lekfayir"},
    ["Maritime City"] = { latitude = 25.267084, longitude = 55.268159, display_name = "Maritime City"},
    ["Shaqu"] = { latitude = 27.236470, longitude = 56.361992, display_name = "Shaqu"},
    ["Larak Island"] = { latitude = 26.883481, longitude = 56.388735, display_name = "Larak Island"},
    ["A Treef"] = { latitude = 24.370000, longitude = 56.710000, display_name = "A Treef"},
    ["Falaj al Qabail"] = { latitude = 24.430000, longitude = 56.610000, display_name = "Falaj al Qabail"},
    ["Al Mamzar"] = { latitude = 25.303968, longitude = 55.342519, display_name = "Al Mamzar"},
    ["Ghadfan"] = { latitude = 24.470000, longitude = 56.600000, display_name = "Ghadfan"},
    ["Al Ghashbah"] = { latitude = 24.390000, longitude = 56.690000, display_name = "Al Ghashbah"},
    ["Sohar"] = { latitude = 24.344558, longitude = 56.742477, display_name = "Sohar"},
    ["Abu Musa Island"] = { latitude = 25.871497, longitude = 55.031741, display_name = "Abu Musa Island"},
    ["Ar Ramlah"] = { latitude = 25.508331, longitude = 55.587911, display_name = "Ar Ramlah"},
    ["As Salamah"] = { latitude = 25.493873, longitude = 55.591668, display_name = "As Salamah"},
    ["Sayh al Asfal"] = { latitude = 26.079096, longitude = 56.340000, display_name = "Sayh al Asfal"},
    ["Al Burayk"] = { latitude = 24.399949, longitude = 56.669999, display_name = "Al Burayk"},
    ["Sharyat Ayqal"] = { latitude = 26.081123, longitude = 56.341464, display_name = "Sharyat Ayqal"},
    ["Al Afifah"] = { latitude = 24.410000, longitude = 56.700000, display_name = "Al Afifah"},
    ["Hillat Jan Muhammad Khan"] = { latitude = 24.619982, longitude = 56.520001, display_name = "Hillat Jan Muhammad Khan"},
    ["Sur al Mazari"] = { latitude = 24.639898, longitude = 56.519994, display_name = "Sur al Mazari"},
    ["Asrar Bani Sad"] = { latitude = 24.590000, longitude = 56.550000, display_name = "Asrar Bani Sad"},
    ["Al Makhamarah"] = { latitude = 24.622574, longitude = 56.523492, display_name = "Al Makhamarah"},
    ["Juyom"] = { latitude = 28.252947, longitude = 53.982912, display_name = "Juyom"},
    ["Khatam Malahah"] = { latitude = 24.970000, longitude = 56.370000, display_name = "Khatam Malahah"},
    ["Dadna"] = { latitude = 25.522473, longitude = 56.357902, display_name = "Dadna"},
    ["Niad"] = { latitude = 26.100000, longitude = 56.330000, display_name = "Niad"},
    ["Hayr Salam"] = { latitude = 26.080000, longitude = 56.330000, display_name = "Hayr Salam"},
    ["Ghubn Hamad"] = { latitude = 26.080000, longitude = 56.290000, display_name = "Ghubn Hamad"},
    ["Ar Rawdah"] = { latitude = 25.860000, longitude = 56.300000, display_name = "Ar Rawdah"},
    ["Al Dhaid"] = { latitude = 25.284179, longitude = 55.879651, display_name = "Al Dhaid"},
    ["Hajiabad"] = { latitude = 28.359072, longitude = 54.420670, display_name = "Hajiabad"},
    ["Murbah"] = { latitude = 25.282317, longitude = 56.363613, display_name = "Murbah"},
    ["Baharestan"] = { latitude = 26.936736, longitude = 56.259130, display_name = "Baharestan"},
    ["Hormoz"] = { latitude = 27.095503, longitude = 56.452739, display_name = "Hormoz"},
    ["Greater Tunb"] = { latitude = 26.263735, longitude = 55.304914, display_name = "Greater Tunb"},
    ["Humaydah"] = { latitude = 24.571042, longitude = 56.387500, display_name = "Humaydah"},
    ["Wab Mubarak"] = { latitude = 26.200000, longitude = 56.220000, display_name = "Wab Mubarak"},
    ["Tawj"] = { latitude = 26.179087, longitude = 56.219030, display_name = "Tawj"},
    ["Farfarah"] = { latitude = 24.720000, longitude = 56.460000, display_name = "Farfarah"},
    ["Humayrah"] = { latitude = 24.690031, longitude = 56.469996, display_name = "Humayrah"},
    ["Siri Island"] = { latitude = 25.911203, longitude = 54.529233, display_name = "Siri Island"},
    ["Rekab al Shib"] = { latitude = 26.130000, longitude = 56.200000, display_name = "Rekab al Shib"},
    ["Harf Ghabi"] = { latitude = 26.230105, longitude = 56.210382, display_name = "Harf Ghabi"},
    ["Qarat az Zingi"] = { latitude = 26.138520, longitude = 56.216353, display_name = "Qarat az Zingi"},
    ["Chahchekor"] = { latitude = 27.278383, longitude = 56.346864, display_name = "Chahchekor"},
    ["Baghoo"] = { latitude = 27.311286, longitude = 56.441230, display_name = "Baghoo"},
    ["Falara"] = { latitude = 24.749915, longitude = 56.470030, display_name = "Falara"},
    ["Al Shinas"] = { latitude = 24.740188, longitude = 56.467926, display_name = "Al Shinas"},
    ["Al Hamiliyah"] = { latitude = 24.729932, longitude = 56.459993, display_name = "Al Hamiliyah"},
    ["Al Umani"] = { latitude = 24.759906, longitude = 56.459930, display_name = "Al Umani"},
    ["Al Ima"] = { latitude = 25.942633, longitude = 56.422684, display_name = "Al Ima"},
    ["Salhad"] = { latitude = 25.860000, longitude = 56.220000, display_name = "Salhad"},
    ["Masafi"] = { latitude = 25.300995, longitude = 56.161498, display_name = "Masafi"},
    ["Al Maksuriyah"] = { latitude = 25.938574, longitude = 56.421403, display_name = "Al Maksuriyah"},
    ["Al Quoz"] = { latitude = 25.169070, longitude = 55.253398, display_name = "Al Quoz"},
    ["Dib Dibba"] = { latitude = 26.200000, longitude = 56.260000, display_name = "Dib Dibba"},
    ["As Sudiyah"] = { latitude = 24.553249, longitude = 56.152529, display_name = "As Sudiyah"},
    ["Jask"] = { latitude = 25.643616, longitude = 57.774564, display_name = "Jask"},
    ["Dibba Al-Hisn"] = { latitude = 25.614408, longitude = 56.267410, display_name = "Dibba Al-Hisn"},
    ["Waab al Lif"] = { latitude = 26.100000, longitude = 56.150000, display_name = "Waab al Lif"},
    ["Ghubrat ar Ras"] = { latitude = 26.160000, longitude = 56.250000, display_name = "Ghubrat ar Ras"},
    ["Lahbab"] = { latitude = 25.041542, longitude = 55.592377, display_name = "Lahbab"},
    ["Nazwa"] = { latitude = 25.005300, longitude = 55.661728, display_name = "Nazwa"},
    ["Al Awir"] = { latitude = 25.170430, longitude = 55.546354, display_name = "Al Awir"},
    ["Murqquab"] = { latitude = 24.820809, longitude = 55.586540, display_name = "Murqquab"},
    ["Sima"] = { latitude = 26.059458, longitude = 56.310949, display_name = "Sima"},
    ["Margham"] = { latitude = 24.899518, longitude = 55.625454, display_name = "Margham"},
    ["Salakh"] = { latitude = 26.691055, longitude = 55.708540, display_name = "Salakh"},
    ["Ghamtha"] = { latitude = 26.110000, longitude = 56.130000, display_name = "Ghamtha"},
    ["Al Usayli"] = { latitude = 25.626760, longitude = 56.008144, display_name = "Al Usayli"},
    ["Wad Wid"] = { latitude = 25.625149, longitude = 56.013964, display_name = "Wad Wid"},
    ["Al Hillah"] = { latitude = 26.101415, longitude = 56.139522, display_name = "Al Hillah"},
    ["Fawhfallam"] = { latitude = 26.109031, longitude = 56.150113, display_name = "Fawhfallam"},
    ["Al Khan"] = { latitude = 24.225589, longitude = 56.326442, display_name = "Al Khan"},
    ["Shahr-e ghadim"] = { latitude = 27.680462, longitude = 54.339229, display_name = "Shahr-e ghadim"},
    ["Shahr-e jadid"] = { latitude = 27.649446, longitude = 54.318973, display_name = "Shahr-e jadid"},
    ["Gharbiyah"] = { latitude = 25.610141, longitude = 56.250002, display_name = "Gharbiyah"},
    ["Kavarzin"] = { latitude = 26.796507, longitude = 55.831578, display_name = "Kavarzin"},
    ["Khonj"] = { latitude = 27.889926, longitude = 53.436782, display_name = "Khonj"},
    ["Bayah"] = { latitude = 25.670000, longitude = 56.260000, display_name = "Bayah"},
    ["Al Shuwara"] = { latitude = 26.150000, longitude = 56.290000, display_name = "Al Shuwara"},
    ["Diba al Bayah"] = { latitude = 25.640977, longitude = 56.267373, display_name = "Diba al Bayah"},
    ["Al Karsha"] = { latitude = 25.656732, longitude = 56.267520, display_name = "Al Karsha"},
    ["Al Hubayl"] = { latitude = 26.100000, longitude = 56.240000, display_name = "Al Hubayl"},
    ["Bur Dubai"] = { latitude = 25.262134, longitude = 55.297289, display_name = "Bur Dubai"},
    ["Port Saeed"] = { latitude = 25.245766, longitude = 55.333673, display_name = "Port Saeed"},
    ["Gerash"] = { latitude = 27.670884, longitude = 54.139243, display_name = "Gerash"},
    ["Al Raffa"] = { latitude = 25.255162, longitude = 55.288470, display_name = "Al Raffa"},
    ["Al Mankhool"] = { latitude = 25.250062, longitude = 55.294734, display_name = "Al Mankhool"},
    ["Deh-e Now"] = { latitude = 27.347680, longitude = 56.544424, display_name = "Deh-e Now"},
    ["Umm Al Quwain"] = { latitude = 25.552043, longitude = 55.547498, display_name = "Umm Al Quwain"},
    ["Majaz al Kubra"] = { latitude = 24.240000, longitude = 56.840000, display_name = "Majaz al Kubra"},
    ["Simah"] = { latitude = 26.061530, longitude = 56.309655, display_name = "Simah"},
    ["Al Garhoud"] = { latitude = 25.241767, longitude = 55.350073, display_name = "Al Garhoud"},
    ["Umm Ramool"] = { latitude = 25.232361, longitude = 55.366826, display_name = "Umm Ramool"},
    ["Bandar Abbas"] = { latitude = 27.178121, longitude = 56.276645, display_name = "Bandar Abbas"},
    ["Dayrestan"] = { latitude = 26.745164, longitude = 55.934918, display_name = "Dayrestan"},
    ["Al Madam"] = { latitude = 24.976101, longitude = 55.789605, display_name = "Al Madam"},
    ["Abu Musa"] = { latitude = 25.885745, longitude = 55.037629, display_name = "Abu Musa"},
    ["Ajman"] = { latitude = 25.393656, longitude = 55.445143, display_name = "Ajman"},
    ["Dehbarez"] = { latitude = 27.447061, longitude = 57.190751, display_name = "Dehbarez"},
    ["Al Fiduk"] = { latitude = 26.030000, longitude = 56.360000, display_name = "Al Fiduk"},
    ["Tomban"] = { latitude = 26.769382, longitude = 55.864493, display_name = "Tomban"},
    ["Dargahan"] = { latitude = 26.967400, longitude = 56.078113, display_name = "Dargahan"},
    ["Al Wasl"] = { latitude = 25.197181, longitude = 55.254978, display_name = "Al Wasl"},
    ["Ziaratali"] = { latitude = 27.739415, longitude = 57.219915, display_name = "Ziaratali"},
    ["Muslaf"] = { latitude = 24.700000, longitude = 56.290000, display_name = "Muslaf"},
    ["Subakh"] = { latitude = 24.720000, longitude = 56.180000, display_name = "Subakh"},
    ["Sur Khusaybi"] = { latitude = 24.660000, longitude = 56.510000, display_name = "Sur Khusaybi"},
    ["Birkat Khaldiyah"] = { latitude = 26.040000, longitude = 56.360000, display_name = "Birkat Khaldiyah"},
    ["Turayf"] = { latitude = 24.670000, longitude = 56.480000, display_name = "Turayf"},
    ["Salib"] = { latitude = 26.369819, longitude = 56.359555, display_name = "Salib"},
    ["Al Darai"] = { latitude = 26.150000, longitude = 56.260000, display_name = "Al Darai"},
    ["Sharjah"] = { latitude = 25.350866, longitude = 55.384186, display_name = "Sharjah"},
    ["Rayy"] = { latitude = 24.650000, longitude = 56.110000, display_name = "Rayy"},
    ["Fin"] = { latitude = 27.627471, longitude = 55.902724, display_name = "Fin"},
    ["Al Jari"] = { latitude = 26.216764, longitude = 56.185602, display_name = "Al Jari"},
    ["Sarriq-e Meyguni"] = { latitude = 27.300313, longitude = 56.276998, display_name = "Sarriq-e Meyguni"},
    ["Sawt"] = { latitude = 25.660000, longitude = 56.260000, display_name = "Sawt"},
    ["Jenah"] = { latitude = 27.017683, longitude = 54.282941, display_name = "Jenah"},
    ["Hashtbandi"] = { latitude = 26.813131, longitude = 57.832783, display_name = "Hashtbandi"},
    ["Fiqa"] = { latitude = 24.716533, longitude = 55.621624, display_name = "Fiqa"},
    ["Arabian Ranches"] = { latitude = 25.051751, longitude = 55.265756, display_name = "Arabian Ranches"},
    ["Mukhaylif"] = { latitude = 24.520000, longitude = 56.570000, display_name = "Mukhaylif"},
    ["Umm Suqeim 3"] = { latitude = 25.138722, longitude = 55.195982, display_name = "Umm Suqeim 3"},
    ["Al Manara"] = { latitude = 25.145203, longitude = 55.214933, display_name = "Al Manara"},
    ["Umm Suqueim 1"] = { latitude = 25.165045, longitude = 55.217017, display_name = "Umm Suqueim 1"},
    ["Harat ash Shaykh"] = { latitude = 24.500000, longitude = 56.580000, display_name = "Harat ash Shaykh"},
    ["Al Jowar"] = { latitude = 26.070000, longitude = 56.220000, display_name = "Al Jowar"},
    ["Bandar-e Lengeh"] = { latitude = 26.557134, longitude = 54.881714, display_name = "Bandar-e Lengeh"},
    ["Suza"] = { latitude = 26.777574, longitude = 56.063126, display_name = "Suza"},
    ["Al Mabrak"] = { latitude = 26.150000, longitude = 56.310000, display_name = "Al Mabrak"},
    ["Seerik"] = { latitude = 26.518435, longitude = 57.104636, display_name = "Seerik"},
    ["Bandar Khamir"] = { latitude = 26.951638, longitude = 55.587708, display_name = "Bandar Khamir"},
    ["Khasab"] = { latitude = 26.180182, longitude = 56.249618, display_name = "Khasab"},
    ["Al Marqadh"] = { latitude = 25.169438, longitude = 55.289759, display_name = "Al Marqadh"},
    ["Fujairah"] = { latitude = 25.125387, longitude = 56.343680, display_name = "Fujairah"},
    ["Al Masharta"] = { latitude = 26.150000, longitude = 56.280000, display_name = "Al Masharta"},
    ["Senderk"] = { latitude = 26.593623, longitude = 57.863963, display_name = "Senderk"},
    ["Bikah"] = { latitude = 27.354812, longitude = 57.180416, display_name = "Bikah"},
    ["Al Jadi"] = { latitude = 26.160000, longitude = 56.170000, display_name = "Al Jadi"},
    ["Fareghan"] = { latitude = 28.007798, longitude = 56.255127, display_name = "Fareghan"},
    ["Bastak"] = { latitude = 27.105005, longitude = 54.455254, display_name = "Bastak"},
    ["Patil Posht-e Banu Band"] = { latitude = 27.298063, longitude = 56.180439, display_name = "Patil Posht-e Banu Band"},
    ["Mobarakabad"] = { latitude = 28.359589, longitude = 53.328186, display_name = "Mobarakabad"},
    ["Din ar Rukayb"] = { latitude = 26.050000, longitude = 56.300000, display_name = "Din ar Rukayb"},
    ["Evaz"] = { latitude = 27.762532, longitude = 54.005150, display_name = "Evaz"},
    ["Sabakh"] = { latitude = 24.527897, longitude = 56.468961, display_name = "Sabakh"},
    ["Sahil Harmul"] = { latitude = 24.530000, longitude = 56.600000, display_name = "Sahil Harmul"},
    ["Sistan"] = { latitude = 26.940766, longitude = 56.257473, display_name = "Sistan"},
    ["Bayt ash Shaykh"] = { latitude = 26.070000, longitude = 56.210000, display_name = "Bayt ash Shaykh"},
    ["Kabir"] = { latitude = 25.432204, longitude = 55.705133, display_name = "Kabir"},
    ["United Arab Emirates"] = { latitude = 24.871963, longitude = 55.255096, display_name = "United Arab Emirates"},
    ["Pa Tall-e Isin"] = { latitude = 27.301953, longitude = 56.239318, display_name = "Pa Tall-e Isin"},
    ["Abu Dhabi"] = { latitude = 24.474796, longitude = 54.370576, display_name = "Abu Dhabi"},
    ["Sihlat"] = { latitude = 24.336032, longitude = 56.498075, display_name = "Sihlat"},
    ["Shufra"] = { latitude = 26.180000, longitude = 56.260000, display_name = "Shufra"},
    ["Al Uwaynat"] = { latitude = 24.270000, longitude = 56.810000, display_name = "Al Uwaynat"},
    ["Ras al Salam"] = { latitude = 26.030000, longitude = 56.350000, display_name = "Ras al Salam"},
    ["New Madha"] = { latitude = 25.284452, longitude = 56.332977, display_name = "New Madha"},
    ["Suhaylah"] = { latitude = 24.270000, longitude = 56.370000, display_name = "Suhaylah"},
    ["Tabl"] = { latitude = 26.755208, longitude = 55.726098, display_name = "Tabl"},
    ["Haqil"] = { latitude = 25.445791, longitude = 56.358072, display_name = "Haqil"},
    ["`Aqqah"] = { latitude = 25.499746, longitude = 56.357323, display_name = "`Aqqah"},
    ["Rul Dadna"] = { latitude = 25.554184, longitude = 56.345325, display_name = "Rul Dadna"},
    ["Wab al Sebil"] = { latitude = 26.050000, longitude = 56.330000, display_name = "Wab al Sebil"},
    ["Latifi"] = { latitude = 27.690078, longitude = 54.387140, display_name = "Latifi"},
    ["Khur"] = { latitude = 27.645907, longitude = 54.345362, display_name = "Khur"},
    ["Fudgha"] = { latitude = 26.120061, longitude = 56.129647, display_name = "Fudgha"},
    ["Zaymi"] = { latitude = 24.450000, longitude = 56.280000, display_name = "Zaymi"},
    ["Tazeyan-e Zir"] = { latitude = 27.291694, longitude = 56.153102, display_name = "Tazeyan-e Zir"},
    ["Zabyat"] = { latitude = 24.350000, longitude = 56.370000, display_name = "Zabyat"},
    ["Hadhf"] = { latitude = 24.789995, longitude = 56.010010, display_name = "Hadhf"},
    ["Ar Rajmi"] = { latitude = 24.640000, longitude = 56.290000, display_name = "Ar Rajmi"},
    ["Hansi"] = { latitude = 24.221653, longitude = 56.349073, display_name = "Hansi"},
    ["Ghuzayyil"] = { latitude = 24.480000, longitude = 56.600000, display_name = "Ghuzayyil"},
    ["Al Khywayriyyah"] = { latitude = 24.450000, longitude = 56.630000, display_name = "Al Khywayriyyah"},
    ["Dubai"] = { latitude = 25.268352, longitude = 55.296196, display_name = "Dubai"},
    ["Bayda"] = { latitude = 24.364497, longitude = 56.408018, display_name = "Bayda"},
    ["Hadirah"] = { latitude = 24.380000, longitude = 56.740000, display_name = "Hadirah"},
    ["Bu Baqarah"] = { latitude = 24.879549, longitude = 56.408535, display_name = "Bu Baqarah"},
    ["As Sifah"] = { latitude = 24.830000, longitude = 56.430000, display_name = "As Sifah"},
    ["Aswad"] = { latitude = 24.870000, longitude = 56.330000, display_name = "Aswad"},
    ["Ash Sharjah"] = { latitude = 24.530000, longitude = 56.280000, display_name = "Ash Sharjah"},
    ["Ghassah"] = { latitude = 26.240422, longitude = 56.318323, display_name = "Ghassah"},
    ["Bat"] = { latitude = 24.520000, longitude = 56.280000, display_name = "Bat"},
    ["Sal al Ala"] = { latitude = 26.050206, longitude = 56.374287, display_name = "Sal al Ala"},
    ["Towla"] = { latitude = 26.972305, longitude = 56.223269, display_name = "Towla"},
    ["Kaboli"] = { latitude = 26.951816, longitude = 56.209809, display_name = "Kaboli"},
    ["Hamiri"] = { latitude = 26.944973, longitude = 56.202666, display_name = "Hamiri"},
    ["Guran"] = { latitude = 26.725618, longitude = 55.617993, display_name = "Guran"},
    ["Chahu Sharghi"] = { latitude = 26.691591, longitude = 55.508721, display_name = "Chahu Sharghi"},
    ["Chahu Gharbi"] = { latitude = 26.683830, longitude = 55.482802, display_name = "Chahu Gharbi"},
    ["Bandar-e-Doulab"] = { latitude = 26.676472, longitude = 55.460577, display_name = "Bandar-e-Doulab"},
    ["Konar Siah"] = { latitude = 26.661945, longitude = 55.433527, display_name = "Konar Siah"},
    ["Gori"] = { latitude = 26.632805, longitude = 55.365481, display_name = "Gori"},
    ["Moradi"] = { latitude = 26.636508, longitude = 55.345794, display_name = "Moradi"},
    ["Basa'idu"] = { latitude = 26.640952, longitude = 55.283018, display_name = "Basa'idu"},
    ["Ash Shishah"] = { latitude = 26.260724, longitude = 56.393230, display_name = "Ash Shishah"},
    ["Ras Salti Ali"] = { latitude = 26.213012, longitude = 56.233535, display_name = "Ras Salti Ali"},
    ["Al Haqt"] = { latitude = 26.123078, longitude = 56.250251, display_name = "Al Haqt"},
    ["Luwayb"] = { latitude = 25.770222, longitude = 56.226061, display_name = "Luwayb"},
    ["Ras al Khaimah"] = { latitude = 25.761937, longitude = 55.935012, display_name = "Ras al Khaimah"},
    ["Dibba al Fujairah"] = { latitude = 25.588498, longitude = 56.264200, display_name = "Dibba al Fujairah"},
    ["Sharm"] = { latitude = 25.469538, longitude = 56.353863, display_name = "Sharm"},
    ["Al Hawayah"] = { latitude = 25.372171, longitude = 56.345103, display_name = "Al Hawayah"},
    ["Al Hutain"] = { latitude = 25.361035, longitude = 56.344583, display_name = "Al Hutain"},
    ["Al Qadisa"] = { latitude = 25.337383, longitude = 56.346792, display_name = "Al Qadisa"},
    ["Al Hayl"] = { latitude = 25.091823, longitude = 56.246812, display_name = "Al Hayl"},
    ["Khalba"] = { latitude = 25.051499, longitude = 56.352167, display_name = "Khalba"},
    ["Bani Jabir"] = { latitude = 24.776926, longitude = 56.449487, display_name = "Bani Jabir"},
    ["Liwa"] = { latitude = 24.518034, longitude = 56.561109, display_name = "Liwa"},
    ["At Tarayf"] = { latitude = 24.388600, longitude = 56.708776, display_name = "At Tarayf"},
    ["Bahjat al Anzar"] = { latitude = 24.320845, longitude = 56.760297, display_name = "Bahjat al Anzar"},
    ["Yal Burayk"] = { latitude = 24.012293, longitude = 57.035907, display_name = "Yal Burayk"},
    ["Muzeira"] = { latitude = 24.839201, longitude = 56.035361, display_name = "Muzeira"},
    ["Hatta"] = { latitude = 24.809872, longitude = 56.114054, display_name = "Hatta"},
    ["Al Hayer"] = { latitude = 24.584657, longitude = 55.762584, display_name = "Al Hayer"},
    ["Nahel"] = { latitude = 24.533336, longitude = 55.559137, display_name = "Nahel"},
    ["Sweihan"] = { latitude = 24.488015, longitude = 55.343260, display_name = "Sweihan"},
    ["Al Uthrat"] = { latitude = 24.353779, longitude = 56.050471, display_name = "Al Uthrat"},
    ["Al Khubayn"] = { latitude = 24.323580, longitude = 56.100938, display_name = "Al Khubayn"},
    ["Al Ashqar"] = { latitude = 24.309377, longitude = 56.132881, display_name = "Al Ashqar"},
    ["Al Khrair"] = { latitude = 24.150677, longitude = 55.825251, display_name = "Al Khrair"},
    ["Al Salamat"] = { latitude = 24.214094, longitude = 55.591883, display_name = "Al Salamat"},
    ["Al Yahar North"] = { latitude = 24.241616, longitude = 55.557785, display_name = "Al Yahar North"},
    ["Al Yahar South"] = { latitude = 24.202040, longitude = 55.534305, display_name = "Al Yahar South"},
    ["Al Khazna"] = { latitude = 24.172745, longitude = 55.118734, display_name = "Al Khazna"},
    ["Al Khatim"] = { latitude = 24.212594, longitude = 55.004704, display_name = "Al Khatim"},
    ["Al Mafreq Industrial Area"] = { latitude = 24.272547, longitude = 54.591075, display_name = "Al Mafreq Industrial Area"},
    ["Kang"] = { latitude = 26.596327, longitude = 54.936964, display_name = "Kang"},
    ["Al Ain"] = { latitude = 24.222700, longitude = 55.692779, display_name = "Al Ain"},
    ["Al Buraimi"] = { latitude = 24.261588, longitude = 55.792935, display_name = "Al Buraimi"},
    ["Bandar Shenas"] = { latitude = 26.517778, longitude = 54.784529, display_name = "Bandar Shenas"},
    ["Ghazi Castle"] = { latitude = 27.455000, longitude = 56.564467, display_name = "Ghazi Castle"},
    ["Berentin"] = { latitude = 27.293682, longitude = 57.252660, display_name = "Berentin"},
    ["Minab"] = { latitude = 27.138592, longitude = 57.072786, display_name = "Minab"},
    ["The World Islands"] = { latitude = 25.224572, longitude = 55.164474, display_name = "The World Islands"},
    ["Palm Jumeirah"] = { latitude = 25.120103, longitude = 55.129655, display_name = "Palm Jumeirah"},
    [" Palm Jebel Ali"] = { latitude = 25.009022, longitude = 54.987897, display_name = " Palm Jebel Ali"},
    ["Jebel Ali Port"] = { latitude = 24.974838, longitude = 55.070788, display_name = "Jebel Ali Port"},
    ["Dubai Investments Park"] = { latitude = 24.981667, longitude = 55.179209, display_name = "Dubai Investments Park"},
    ["Sir Abu Ny'air"] = { latitude = 25.226516, longitude = 54.217534, display_name = "Sir Abu Ny'air"},
    ["Ghantoot"] = { latitude = 24.848710, longitude = 54.831340, display_name = "Ghantoot"},
    ["EMAL Industrial City"] = { latitude = 24.791622, longitude = 54.721420, display_name = "EMAL Industrial City"},
    ["Kizad"] = { latitude = 24.707434, longitude = 54.811143, display_name = "Kizad"},
    ["Al Samkha"] = { latitude = 24.690096, longitude = 54.758042, display_name = "Al Samkha"},
    ["Al Sharia"] = { latitude = 24.628021, longitude = 54.781774, display_name = "Al Sharia"},
    ["Al Rahba"] = { latitude = 24.622200, longitude = 54.705903, display_name = "Al Rahba"},
    ["Al Bahia"] = { latitude = 24.572522, longitude = 54.650999, display_name = "Al Bahia"},
    ["Rawdat al Reef"] = { latitude = 24.508450, longitude = 54.718527, display_name = "Rawdat al Reef"},
    ["Yas Island"] = { latitude = 24.490761, longitude = 54.612231, display_name = "Yas Island"},
    ["Al Reef"] = { latitude = 24.454224, longitude = 54.670993, display_name = "Al Reef"},
    ["Al Falah New Community"] = { latitude = 24.442883, longitude = 54.726928, display_name = "Al Falah New Community"},
    ["Al Shamkha"] = { latitude = 24.381746, longitude = 54.709066, display_name = "Al Shamkha"},
    ["Masdar City"] = { latitude = 24.427375, longitude = 54.625464, display_name = "Masdar City"},
    ["Khalifa City"] = { latitude = 24.419081, longitude = 54.576085, display_name = "Khalifa City"},
    ["Shakhbout City"] = { latitude = 24.364088, longitude = 54.632717, display_name = "Shakhbout City"},
    ["Al Shawamekh"] = { latitude = 24.346064, longitude = 54.665524, display_name = "Al Shawamekh"},
    ["Baniyas City"] = { latitude = 24.302115, longitude = 54.636742, display_name = "Baniyas City"},
    ["Mohammed bin Zayed City"] = { latitude = 24.334233, longitude = 54.552368, display_name = "Mohammed bin Zayed City"},
    ["Musaffah Industrial Area"] = { latitude = 24.348649, longitude = 54.491514, display_name = "Musaffah Industrial Area"},
    ["Abu Dhabi Industrial Area"] = { latitude = 24.286298, longitude = 54.466256, display_name = "Abu Dhabi Industrial Area"},
    ["Al Muqatra"] = { latitude = 24.215471, longitude = 54.462245, display_name = "Al Muqatra"},
    ["Al Wathba"] = { latitude = 24.201053, longitude = 54.722964, display_name = "Al Wathba"},
    ["Zayed Military City"] = { latitude = 24.503534, longitude = 54.872741, display_name = "Zayed Military City"},
    ["Al Maqta"] = { latitude = 24.406182, longitude = 54.502626, display_name = "Al Maqta"},
    ["Umm al Nar"] = { latitude = 24.434628, longitude = 54.500575, display_name = "Umm al Nar"},
    ["Al Sa'adah"] = { latitude = 24.437465, longitude = 54.425519, display_name = "Al Sa'adah"},
    ["Al Rawdah"] = { latitude = 24.421667, longitude = 54.437131, display_name = "Al Rawdah"},
    ["Al Muzoun"] = { latitude = 24.411611, longitude = 54.430731, display_name = "Al Muzoun"},
    ["Al Mushrif"] = { latitude = 24.432836, longitude = 54.394401, display_name = "Al Mushrif"},
    ["Al Qurm"] = { latitude = 24.422772, longitude = 54.393330, display_name = "Al Qurm"},
    ["Al Bateen"] = { latitude = 24.446453, longitude = 54.352998, display_name = "Al Bateen"},
    ["Al Kasir"] = { latitude = 24.474630, longitude = 54.320160, display_name = "Al Kasir"},
    ["Al Mina"] = { latitude = 24.519893, longitude = 54.370686, display_name = "Al Mina"},
    ["Saadiyat Beach"] = { latitude = 24.543537, longitude = 54.435444, display_name = "Saadiyat Beach"},
    ["Oman"] = { latitude = 26.128194, longitude = 56.245636, display_name = "Oman"},
    ["Iran"] = { latitude = 27.697861, longitude = 55.847496, display_name = "Iran"},
    ["Sir Abu Ny'air Island"] = { latitude = 25.234987, longitude = 54.215139, display_name = "Sir Abu Ny'air Island"},
    ["Qeshm Island"] = { latitude = 26.818904, longitude = 55.901246, display_name = "Qeshm Island"},
    ["Naif"] = { latitude = 25.272292, longitude = 55.311253, display_name = "Naif"},
    ["Oud Metha"] = { latitude = 25.240796, longitude = 55.309122, display_name = "Oud Metha"},
    ["Al Hudaiba"] = { latitude = 25.239402, longitude = 55.274196, display_name = "Al Hudaiba"},
    ["Al Rigga"] = { latitude = 25.259060, longitude = 55.320567, display_name = "Al Rigga"},
    ["Downtown Burj Khalifa"] = { latitude = 25.193372, longitude = 55.276023, display_name = "Downtown Burj Khalifa"},
    ["Al Majaz"] = { latitude = 25.330634, longitude = 55.384092, display_name = "Al Majaz"},
    ["Abu Hail"] = { latitude = 25.285615, longitude = 55.329179, display_name = "Abu Hail"},
    ["Jumeirah"] = { latitude = 25.218588, longitude = 55.254966, display_name = "Jumeirah"},
    ["Al Barsha"] = { latitude = 25.095399, longitude = 55.206466, display_name = "Al Barsha"},
    ["Emirates Hills"] = { latitude = 25.068473, longitude = 55.171652, display_name = "Emirates Hills"},
    ["Merdonu"] = { latitude = 27.322500, longitude = 54.474428, display_name = "Merdonu"},
    ["Gowdegaz"] = { latitude = 27.300118, longitude = 54.496949, display_name = "Gowdegaz"},
    ["Todruyeh"] = { latitude = 27.304580, longitude = 54.708803, display_name = "Todruyeh"},
    ["Dehong"] = { latitude = 27.310218, longitude = 54.655915, display_name = "Dehong"},
    ["Chah Benard"] = { latitude = 27.247221, longitude = 54.624649, display_name = "Chah Benard"},
    ["Kookherd"] = { latitude = 27.088970, longitude = 54.493979, display_name = "Kookherd"},
    ["Eelood"] = { latitude = 27.217003, longitude = 54.673731, display_name = "Eelood"},
    ["Berkeh Lary"] = { latitude = 27.211954, longitude = 54.716274, display_name = "Berkeh Lary"},
    ["Chah Dezdan"] = { latitude = 27.209354, longitude = 54.745048, display_name = "Chah Dezdan"},
    ["Dehtal"] = { latitude = 27.210763, longitude = 54.785315, display_name = "Dehtal"},
    ["Dashte Jayhun"] = { latitude = 27.276316, longitude = 55.079159, display_name = "Dashte Jayhun"},
    ["Tang-e Dalan"] = { latitude = 27.333724, longitude = 55.093729, display_name = "Tang-e Dalan"},
    ["Khoerde"] = { latitude = 27.781969, longitude = 54.420895, display_name = "Khoerde"},
    ["Kanakh"] = { latitude = 26.899358, longitude = 55.389228, display_name = "Kanakh"},
    ["Gavmiri"] = { latitude = 26.890173, longitude = 55.316569, display_name = "Gavmiri"},
    ["Sayeh Khvosh"] = { latitude = 26.830745, longitude = 55.373321, display_name = "Sayeh Khvosh"},
    ["Bandar e Pol"] = { latitude = 27.008738, longitude = 55.739849, display_name = "Bandar e Pol"},
    ["Demilu"] = { latitude = 27.182336, longitude = 55.832653, display_name = "Demilu"},
    ["Keshar e Bala"] = { latitude = 27.267073, longitude = 55.955476, display_name = "Keshar e Bala"},
    ["Sargap"] = { latitude = 27.247097, longitude = 55.931328, display_name = "Sargap"},
    ["Chamerdan"] = { latitude = 27.247713, longitude = 55.977965, display_name = "Chamerdan"},
    ["Ghalat e Bala"] = { latitude = 27.319302, longitude = 56.099257, display_name = "Ghalat e Bala"},
    ["Konaru"] = { latitude = 27.310582, longitude = 56.135679, display_name = "Konaru"},
    ["Keshar e Paeen"] = { latitude = 27.230860, longitude = 55.906989, display_name = "Keshar e Paeen"},
    ["Mogh Ahmad Paeen"] = { latitude = 27.155902, longitude = 55.882218, display_name = "Mogh Ahmad Paeen"},
    ["Gachin Bala"] = { latitude = 27.125532, longitude = 55.872022, display_name = "Gachin Bala"},
    ["Gachin Paeen"] = { latitude = 27.090410, longitude = 55.891007, display_name = "Gachin Paeen"},
    ["Chahu"] = { latitude = 27.242365, longitude = 56.066446, display_name = "Chahu"},
    ["Bostanu"] = { latitude = 27.080847, longitude = 55.996042, display_name = "Bostanu"},
    ["Rajaei Port"] = { latitude = 27.102766, longitude = 56.060752, display_name = "Rajaei Port"},
    ["Paiposht"] = { latitude = 26.879825, longitude = 55.928558, display_name = "Paiposht"},
    ["Kuvei"] = { latitude = 26.946337, longitude = 56.005579, display_name = "Kuvei"},
    ["Giahdan"] = { latitude = 26.923002, longitude = 56.069731, display_name = "Giahdan"},
    ["Tourian"] = { latitude = 26.880562, longitude = 56.030528, display_name = "Tourian"},
    ["Kousha"] = { latitude = 26.857848, longitude = 56.017255, display_name = "Kousha"},
    ["Sarkhun"] = { latitude = 27.402982, longitude = 56.412849, display_name = "Sarkhun"},
    ["Sarhez"] = { latitude = 27.580306, longitude = 56.098990, display_name = "Sarhez"},
    ["Tola Industrial City"] = { latitude = 26.968887, longitude = 56.179724, display_name = "Tola Industrial City"},
    ["Hajji Khademi"] = { latitude = 27.230324, longitude = 57.043880, display_name = "Hajji Khademi"},
    ["Tirour"] = { latitude = 27.324834, longitude = 56.965962, display_name = "Tirour"},
    ["Kormon"] = { latitude = 27.432573, longitude = 56.874832, display_name = "Kormon"},
    ["Takht"] = { latitude = 27.493904, longitude = 56.656532, display_name = "Takht"},
    ["Chahestan"] = { latitude = 27.519748, longitude = 56.752911, display_name = "Chahestan"},
    ["Ramkan"] = { latitude = 26.867639, longitude = 56.043939, display_name = "Ramkan"},
    ["Zirang"] = { latitude = 26.854199, longitude = 56.061282, display_name = "Zirang"},
    ["Kerman"] = { latitude = 30.323533, longitude = 57.023611, display_name = "Kerman"},
    ["Shiraz"] = { latitude = 29.559969, longitude = 52.618058, display_name = "Shiraz"},
    ["Fasa"] = { latitude = 28.936639, longitude = 53.651068, display_name = "Fasa"},
    ["Darab"] = { latitude = 28.753194, longitude = 54.548149, display_name = "Darab"},
    ["Choghadak"] = { latitude = 28.985545, longitude = 51.032761, display_name = "Choghadak"},
    ["Borazjan"] = { latitude = 29.266143, longitude = 51.212060, display_name = "Borazjan"},
    ["Dalaki"] = { latitude = 29.430861, longitude = 51.295021, display_name = "Dalaki"},
    ["Firuzabad"] = { latitude = 28.845604, longitude = 52.572724, display_name = "Firuzabad"},
    ["Masiri"] = { latitude = 30.244395, longitude = 51.524174, display_name = "Masiri"},
    ["Nurabad Mamasani"] = { latitude = 30.115407, longitude = 51.522963, display_name = "Nurabad Mamasani"},
    ["Qaemiyeh"] = { latitude = 29.851002, longitude = 51.581912, display_name = "Qaemiyeh"},
    ["Konartakhteh"] = { latitude = 29.532781, longitude = 51.396080, display_name = "Konartakhteh"},
    ["Saadat Shahr"] = { latitude = 30.079995, longitude = 53.136456, display_name = "Saadat Shahr"},
    ["Arsanjan"] = { latitude = 29.914968, longitude = 53.308976, display_name = "Arsanjan"},
    ["Farashband"] = { latitude = 28.855378, longitude = 52.094644, display_name = "Farashband"},
    ["Najafshahr"] = { latitude = 29.389703, longitude = 55.720301, display_name = "Najafshahr"},
    ["Ab Pakhsh"] = { latitude = 29.360784, longitude = 51.071375, display_name = "Ab Pakhsh"},
    ["Shabankareh"] = { latitude = 29.469683, longitude = 50.990973, display_name = "Shabankareh"},
    ["Saadabad"] = { latitude = 29.384835, longitude = 51.116629, display_name = "Saadabad"},
    ["Delvar"] = { latitude = 28.762873, longitude = 51.071923, display_name = "Delvar"},
    ["Vahdatiyeh"] = { latitude = 29.482557, longitude = 51.243308, display_name = "Vahdatiyeh"},
    ["Ahram"] = { latitude = 28.883028, longitude = 51.274202, display_name = "Ahram"},
    ["Qir"] = { latitude = 28.482802, longitude = 53.035756, display_name = "Qir"},
    ["Qotbabad"] = { latitude = 28.639302, longitude = 53.637805, display_name = "Qotbabad"},
    ["Zahedshahr"] = { latitude = 28.748435, longitude = 53.804106, display_name = "Zahedshahr"},
    ["Miandeh"] = { latitude = 28.716296, longitude = 53.855430, display_name = "Miandeh"},
    ["Now Bandegan"] = { latitude = 28.854560, longitude = 53.825902, display_name = "Now Bandegan"},
    ["Sheshdeh"] = { latitude = 28.948635, longitude = 53.995505, display_name = "Sheshdeh"},
    ["Emamshahr"] = { latitude = 28.446670, longitude = 53.152544, display_name = "Emamshahr"},
    ["Aliabad"] = { latitude = 28.789843, longitude = 51.055410, display_name = "Aliabad"},
    ["Jaeinak"] = { latitude = 28.786242, longitude = 51.069132, display_name = "Jaeinak"},
    ["Marvdasht"] = { latitude = 29.876291, longitude = 52.806290, display_name = "Marvdasht"},
    ["Jahrom"] = { latitude = 28.496176, longitude = 53.559337, display_name = "Jahrom"},
    ["Ij"] = { latitude = 29.020374, longitude = 54.247745, display_name = "Ij"},
    ["Aviz"] = { latitude = 28.916223, longitude = 52.057212, display_name = "Aviz"},
    ["Nudan"] = { latitude = 29.801949, longitude = 51.693599, display_name = "Nudan"},
    ["Khesht"] = { latitude = 29.567944, longitude = 51.339531, display_name = "Khesht"},
}

veafNamedPoints._citiesTheChannel =
{
    ["Canterbury"] = { latitude = 51.280028, longitude = 1.080253, display_name = "Canterbury"},
    ["Rochester"] = { latitude = 51.389062, longitude = 0.504935, display_name = "Rochester"},
    ["Battle"] = { latitude = 50.917771, longitude = 0.483654, display_name = "Battle"},
    ["Rye"] = { latitude = 50.951187, longitude = 0.732767, display_name = "Rye"},
    ["Minnis Bay"] = { latitude = 51.380177, longitude = 1.285780, display_name = "Minnis Bay"},
    ["Margate"] = { latitude = 51.389433, longitude = 1.382151, display_name = "Margate"},
    ["Deal"] = { latitude = 51.223924, longitude = 1.402865, display_name = "Deal"},
    ["Ramsgate"] = { latitude = 51.333473, longitude = 1.419648, display_name = "Ramsgate"},
    ["Chatham"] = { latitude = 51.380484, longitude = 0.529276, display_name = "Chatham"},
    ["Gillingham"] = { latitude = 51.387656, longitude = 0.545771, display_name = "Gillingham"},
    ["Winchelsea"] = { latitude = 50.924390, longitude = 0.708636, display_name = "Winchelsea"},
    ["Winchelsea Beach"] = { latitude = 50.915916, longitude = 0.723579, display_name = "Winchelsea Beach"},
    ["Cliff End"] = { latitude = 50.888757, longitude = 0.683912, display_name = "Cliff End"},
    ["Pett Level"] = { latitude = 50.889759, longitude = 0.687214, display_name = "Pett Level"},
    ["Sheerness"] = { latitude = 51.439170, longitude = 0.758572, display_name = "Sheerness"},
    ["Whitstable"] = { latitude = 51.360629, longitude = 1.024063, display_name = "Whitstable"},
    ["Dover"] = { latitude = 51.125128, longitude = 1.313423, display_name = "Dover"},
    ["Elham"] = { latitude = 51.153771, longitude = 1.110679, display_name = "Elham"},
    ["Wingham"] = { latitude = 51.272594, longitude = 1.215429, display_name = "Wingham"},
    ["Eastry"] = { latitude = 51.247183, longitude = 1.307598, display_name = "Eastry"},
    ["Maidstone"] = { latitude = 51.274826, longitude = 0.523165, display_name = "Maidstone"},
    ["Loose"] = { latitude = 51.241150, longitude = 0.516822, display_name = "Loose"},
    ["Aylesford"] = { latitude = 51.303717, longitude = 0.482638, display_name = "Aylesford"},
    ["Headcorn"] = { latitude = 51.168427, longitude = 0.627622, display_name = "Headcorn"},
    ["Ashford"] = { latitude = 51.148555, longitude = 0.872257, display_name = "Ashford"},
    ["Wye"] = { latitude = 51.183248, longitude = 0.936922, display_name = "Wye"},
    ["Folkestone"] = { latitude = 51.079134, longitude = 1.179407, display_name = "Folkestone"},
    ["Burham"] = { latitude = 51.328820, longitude = 0.482294, display_name = "Burham"},
    ["Herne Bay"] = { latitude = 51.371951, longitude = 1.130695, display_name = "Herne Bay"},
    ["Hawkinge"] = { latitude = 51.118042, longitude = 1.166441, display_name = "Hawkinge"},
    ["Boughton Lees"] = { latitude = 51.188913, longitude = 0.892375, display_name = "Boughton Lees"},
    ["Challock"] = { latitude = 51.219886, longitude = 0.876510, display_name = "Challock"},
    ["Sevington"] = { latitude = 51.133271, longitude = 0.904052, display_name = "Sevington"},
    ["Borstal"] = { latitude = 51.374409, longitude = 0.485466, display_name = "Borstal"},
    ["Brede"] = { latitude = 50.935783, longitude = 0.596550, display_name = "Brede"},
    ["Chartham"] = { latitude = 51.254268, longitude = 1.020390, display_name = "Chartham"},
    ["Charing"] = { latitude = 51.211424, longitude = 0.794972, display_name = "Charing"},
    ["Harrietsham"] = { latitude = 51.243350, longitude = 0.671526, display_name = "Harrietsham"},
    ["Hinxhill"] = { latitude = 51.145818, longitude = 0.928268, display_name = "Hinxhill"},
    ["Smeeth"] = { latitude = 51.118147, longitude = 0.959869, display_name = "Smeeth"},
    ["Mersham"] = { latitude = 51.119423, longitude = 0.932362, display_name = "Mersham"},
    ["Hythe"] = { latitude = 51.069142, longitude = 1.084163, display_name = "Hythe"},
    ["Stowting"] = { latitude = 51.136687, longitude = 1.035279, display_name = "Stowting"},
    ["Lyminge"] = { latitude = 51.128744, longitude = 1.087791, display_name = "Lyminge"},
    ["Molash"] = { latitude = 51.230119, longitude = 0.898823, display_name = "Molash"},
    ["Godmersham"] = { latitude = 51.215166, longitude = 0.950298, display_name = "Godmersham"},
    ["Lydd"] = { latitude = 50.951567, longitude = 0.907511, display_name = "Lydd"},
    ["Pluckley"] = { latitude = 51.175264, longitude = 0.754645, display_name = "Pluckley"},
    ["Hamstreet"] = { latitude = 51.064901, longitude = 0.855344, display_name = "Hamstreet"},
    ["Woodchurch"] = { latitude = 51.076741, longitude = 0.775834, display_name = "Woodchurch"},
    ["Tenterden"] = { latitude = 51.070096, longitude = 0.688827, display_name = "Tenterden"},
    ["High Halden"] = { latitude = 51.103550, longitude = 0.711234, display_name = "High Halden"},
    ["Faversham"] = { latitude = 51.314409, longitude = 0.891189, display_name = "Faversham"},
    ["Shepherdswell"] = { latitude = 51.185588, longitude = 1.231137, display_name = "Shepherdswell"},
    ["West Malling"] = { latitude = 51.295403, longitude = 0.409461, display_name = "West Malling"},
    ["Little Chart"] = { latitude = 51.179579, longitude = 0.780473, display_name = "Little Chart"},
    ["Minster"] = { latitude = 51.333898, longitude = 1.316084, display_name = "Minster"},
    ["Acol"] = { latitude = 51.357756, longitude = 1.311721, display_name = "Acol"},
    ["Alkham"] = { latitude = 51.135740, longitude = 1.223693, display_name = "Alkham"},
    ["Adisham"] = { latitude = 51.239153, longitude = 1.188543, display_name = "Adisham"},
    ["Ditton"] = { latitude = 51.296383, longitude = 0.453567, display_name = "Ditton"},
    ["Biddenden"] = { latitude = 51.115357, longitude = 0.642861, display_name = "Biddenden"},
    ["Smarden"] = { latitude = 51.148932, longitude = 0.686724, display_name = "Smarden"},
    ["Preston"] = { latitude = 51.303539, longitude = 1.227388, display_name = "Preston"},
    ["Sandwich"] = { latitude = 51.275253, longitude = 1.340831, display_name = "Sandwich"},
    ["St. Margaret's at Cliffe"] = { latitude = 51.154196, longitude = 1.372227, display_name = "St. Margaret's at Cliffe"},
    ["Whitfield"] = { latitude = 51.159496, longitude = 1.288821, display_name = "Whitfield"},
    ["Detling"] = { latitude = 51.294758, longitude = 0.569084, display_name = "Detling"},
    ["Ash"] = { latitude = 51.280986, longitude = 1.280306, display_name = "Ash"},
    ["New Romney"] = { latitude = 50.985120, longitude = 0.942657, display_name = "New Romney"},
    ["Dymchurch"] = { latitude = 51.026329, longitude = 0.993650, display_name = "Dymchurch"},
    ["Dungeness"] = { latitude = 50.914178, longitude = 0.972894, display_name = "Dungeness"},
    ["Blean"] = { latitude = 51.309226, longitude = 1.041400, display_name = "Blean"},
    ["Bramling"] = { latitude = 51.266099, longitude = 1.191608, display_name = "Bramling"},
    ["Upstreet"] = { latitude = 51.323878, longitude = 1.195885, display_name = "Upstreet"},
    ["Cliffsend"] = { latitude = 51.328048, longitude = 1.365472, display_name = "Cliffsend"},
    ["Broadstairs"] = { latitude = 51.358676, longitude = 1.440785, display_name = "Broadstairs"},
    ["Wickhambreaux"] = { latitude = 51.285060, longitude = 1.183971, display_name = "Wickhambreaux"},
    ["Pegwell"] = { latitude = 51.327407, longitude = 1.392171, display_name = "Pegwell"},
    ["Chilton"] = { latitude = 51.330792, longitude = 1.391433, display_name = "Chilton"},
    ["Manston"] = { latitude = 51.345980, longitude = 1.369626, display_name = "Manston"},
    ["Westwood"] = { latitude = 51.361166, longitude = 1.394531, display_name = "Westwood"},
    ["Northiam"] = { latitude = 50.990862, longitude = 0.606276, display_name = "Northiam"},
    ["St Marys Bay"] = { latitude = 51.011714, longitude = 0.978640, display_name = "St Marys Bay"},
    ["Lympne"] = { latitude = 51.075782, longitude = 1.027252, display_name = "Lympne"},
    ["Brabourne Lees"] = { latitude = 51.125756, longitude = 0.972561, display_name = "Brabourne Lees"},
    ["Lymbridge Green"] = { latitude = 51.156119, longitude = 1.037883, display_name = "Lymbridge Green"},
    ["Dunkirk"] = { latitude = 51.292033, longitude = 0.977125, display_name = "Dunkirk"},
    ["Rough Common"] = { latitude = 51.291596, longitude = 1.047419, display_name = "Rough Common"},
    ["Seasalter"] = { latitude = 51.348220, longitude = 1.005063, display_name = "Seasalter"},
    ["Tankerton"] = { latitude = 51.364112, longitude = 1.043862, display_name = "Tankerton"},
    ["Swalecliffe"] = { latitude = 51.366045, longitude = 1.068187, display_name = "Swalecliffe"},
    ["Herne"] = { latitude = 51.350351, longitude = 1.133792, display_name = "Herne"},
    ["Westbere"] = { latitude = 51.308149, longitude = 1.144101, display_name = "Westbere"},
    ["Wouldham"] = { latitude = 51.351228, longitude = 0.460243, display_name = "Wouldham"},
    ["Stelling Minnis"] = { latitude = 51.179301, longitude = 1.068386, display_name = "Stelling Minnis"},
    ["Saltwood"] = { latitude = 51.080165, longitude = 1.081790, display_name = "Saltwood"},
    ["Sandgate"] = { latitude = 51.074379, longitude = 1.148713, display_name = "Sandgate"},
    ["Hersden"] = { latitude = 51.314705, longitude = 1.160170, display_name = "Hersden"},
    ["Broomfield"] = { latitude = 51.357427, longitude = 1.154917, display_name = "Broomfield"},
    ["St Nicholas-at-Wade"] = { latitude = 51.352050, longitude = 1.253523, display_name = "St Nicholas-at-Wade"},
    ["Monkton"] = { latitude = 51.338792, longitude = 1.281998, display_name = "Monkton"},
    ["Woodnesborough"] = { latitude = 51.265848, longitude = 1.305345, display_name = "Woodnesborough"},
    ["Elvington"] = { latitude = 51.206252, longitude = 1.248205, display_name = "Elvington"},
    ["Eythorne"] = { latitude = 51.198082, longitude = 1.266917, display_name = "Eythorne"},
    ["Stockbury"] = { latitude = 51.327200, longitude = 0.641439, display_name = "Stockbury"},
    ["Hartlip"] = { latitude = 51.348055, longitude = 0.639520, display_name = "Hartlip"},
    ["Halling"] = { latitude = 51.354593, longitude = 0.444094, display_name = "Halling"},
    ["Birling"] = { latitude = 51.319026, longitude = 0.410614, display_name = "Birling"},
    ["Strood"] = { latitude = 51.395857, longitude = 0.495080, display_name = "Strood"},
    ["Westgate-on-Sea"] = { latitude = 51.381577, longitude = 1.337211, display_name = "Westgate-on-Sea"},
    ["Etchingham"] = { latitude = 51.008586, longitude = 0.438025, display_name = "Etchingham"},
    ["Hurst Green"] = { latitude = 51.018451, longitude = 0.469524, display_name = "Hurst Green"},
    ["Lydden"] = { latitude = 51.162264, longitude = 1.241301, display_name = "Lydden"},
    ["Temple Ewell"] = { latitude = 51.152250, longitude = 1.269622, display_name = "Temple Ewell"},
    ["Kearsney"] = { latitude = 51.148482, longitude = 1.266451, display_name = "Kearsney"},
    ["Ringwould"] = { latitude = 51.183706, longitude = 1.376369, display_name = "Ringwould"},
    ["Ripple"] = { latitude = 51.200491, longitude = 1.357389, display_name = "Ripple"},
    ["Walmer"] = { latitude = 51.206888, longitude = 1.399915, display_name = "Walmer"},
    ["Northbourne"] = { latitude = 51.220424, longitude = 1.337825, display_name = "Northbourne"},
    ["Sholden"] = { latitude = 51.224973, longitude = 1.375277, display_name = "Sholden"},
    ["Worth"] = { latitude = 51.256253, longitude = 1.348298, display_name = "Worth"},
    ["Stone Cross"] = { latitude = 51.265876, longitude = 1.338865, display_name = "Stone Cross"},
    ["Bossingham"] = { latitude = 51.200445, longitude = 1.076820, display_name = "Bossingham"},
    ["Petham"] = { latitude = 51.222798, longitude = 1.045879, display_name = "Petham"},
    ["Robertsbridge"] = { latitude = 50.985640, longitude = 0.474452, display_name = "Robertsbridge"},
    ["Kemsley"] = { latitude = 51.363471, longitude = 0.739047, display_name = "Kemsley"},
    ["Harbledown"] = { latitude = 51.281829, longitude = 1.056575, display_name = "Harbledown"},
    ["Orlestone"] = { latitude = 51.076644, longitude = 0.853228, display_name = "Orlestone"},
    ["Horsmonden"] = { latitude = 51.139145, longitude = 0.431837, display_name = "Horsmonden"},
    ["Denton"] = { latitude = 51.181449, longitude = 1.169768, display_name = "Denton"},
    ["Goodnestone"] = { latitude = 51.246747, longitude = 1.230529, display_name = "Goodnestone"},
    ["Bexhill-on-Sea"] = { latitude = 50.842438, longitude = 0.467572, display_name = "Bexhill-on-Sea"},
    ["Hastings"] = { latitude = 50.855389, longitude = 0.582470, display_name = "Hastings"},
    ["Sutton Valence"] = { latitude = 51.212634, longitude = 0.592560, display_name = "Sutton Valence"},
    ["Egerton"] = { latitude = 51.195112, longitude = 0.729017, display_name = "Egerton"},
    ["Walderslade"] = { latitude = 51.342945, longitude = 0.526780, display_name = "Walderslade"},
    ["Shorne"] = { latitude = 51.413153, longitude = 0.433041, display_name = "Shorne"},
    ["Cuxton"] = { latitude = 51.375046, longitude = 0.455713, display_name = "Cuxton"},
    ["Higham"] = { latitude = 51.415105, longitude = 0.459730, display_name = "Higham"},
    ["Bluebell Hill"] = { latitude = 51.332465, longitude = 0.505855, display_name = "Bluebell Hill"},
    ["Bredhurst"] = { latitude = 51.331700, longitude = 0.579602, display_name = "Bredhurst"},
    ["Upper Upnor"] = { latitude = 51.405943, longitude = 0.525429, display_name = "Upper Upnor"},
    ["Wateringbury"] = { latitude = 51.255749, longitude = 0.416423, display_name = "Wateringbury"},
    ["East Malling"] = { latitude = 51.287380, longitude = 0.438542, display_name = "East Malling"},
    ["Lower Upnor"] = { latitude = 51.412433, longitude = 0.530373, display_name = "Lower Upnor"},
    ["Frindsbury"] = { latitude = 51.400196, longitude = 0.506504, display_name = "Frindsbury"},
    ["Wainscott"] = { latitude = 51.411234, longitude = 0.510528, display_name = "Wainscott"},
    ["Hale"] = { latitude = 51.363531, longitude = 0.555123, display_name = "Hale"},
    ["Lower Rainham"] = { latitude = 51.379150, longitude = 0.605817, display_name = "Lower Rainham"},
    ["Moor Street"] = { latitude = 51.358628, longitude = 0.628863, display_name = "Moor Street"},
    ["Eccles"] = { latitude = 51.317774, longitude = 0.481066, display_name = "Eccles"},
    ["Hempstead"] = { latitude = 51.351133, longitude = 0.569747, display_name = "Hempstead"},
    ["Boxley"] = { latitude = 51.301603, longitude = 0.543120, display_name = "Boxley"},
    ["Thurnham"] = { latitude = 51.291306, longitude = 0.591831, display_name = "Thurnham"},
    ["Royal British Legion Village"] = { latitude = 51.293880, longitude = 0.475714, display_name = "Royal British Legion Village"},
    ["Lower Halstow"] = { latitude = 51.372477, longitude = 0.669655, display_name = "Lower Halstow"},
    ["Upchurch"] = { latitude = 51.376198, longitude = 0.649188, display_name = "Upchurch"},
    ["Newington"] = { latitude = 51.351563, longitude = 0.665701, display_name = "Newington"},
    ["Yalding"] = { latitude = 51.224363, longitude = 0.431451, display_name = "Yalding"},
    ["Nettlestead"] = { latitude = 51.243649, longitude = 0.412021, display_name = "Nettlestead"},
    ["Hunton"] = { latitude = 51.218873, longitude = 0.459449, display_name = "Hunton"},
    ["West Farleigh"] = { latitude = 51.251037, longitude = 0.455152, display_name = "West Farleigh"},
    ["East Farleigh"] = { latitude = 51.253071, longitude = 0.483908, display_name = "East Farleigh"},
    ["St Michaels"] = { latitude = 51.082873, longitude = 0.692405, display_name = "St Michaels"},
    ["Broad Oak"] = { latitude = 50.950030, longitude = 0.599972, display_name = "Broad Oak"},
    ["Crowhurst"] = { latitude = 50.882407, longitude = 0.497905, display_name = "Crowhurst"},
    ["Ninfield"] = { latitude = 50.888399, longitude = 0.418847, display_name = "Ninfield"},
    ["Westfield"] = { latitude = 50.908967, longitude = 0.576927, display_name = "Westfield"},
    ["Icklesham"] = { latitude = 50.916764, longitude = 0.666258, display_name = "Icklesham"},
    ["Newingreen"] = { latitude = 51.084584, longitude = 1.034409, display_name = "Newingreen"},
    ["Bethersden"] = { latitude = 51.127249, longitude = 0.752789, display_name = "Bethersden"},
    ["Netherfield"] = { latitude = 50.943373, longitude = 0.435069, display_name = "Netherfield"},
    ["Hollingbourne"] = { latitude = 51.265694, longitude = 0.641462, display_name = "Hollingbourne"},
    ["Birchington"] = { latitude = 51.373802, longitude = 1.307125, display_name = "Birchington"},
    ["Sedlescombe"] = { latitude = 50.940424, longitude = 0.529209, display_name = "Sedlescombe"},
    ["Sedlescombe Street"] = { latitude = 50.932365, longitude = 0.533243, display_name = "Sedlescombe Street"},
    ["Fairlight"] = { latitude = 50.875316, longitude = 0.662418, display_name = "Fairlight"},
    ["St Leonards"] = { latitude = 50.855726, longitude = 0.548014, display_name = "St Leonards"},
    ["Old Hawkinge"] = { latitude = 51.114871, longitude = 1.184821, display_name = "Old Hawkinge"},
    ["Hawkhurst"] = { latitude = 51.046963, longitude = 0.508255, display_name = "Hawkhurst"},
    ["Ticehurst"] = { latitude = 51.045772, longitude = 0.413924, display_name = "Ticehurst"},
    ["Pett"] = { latitude = 50.895003, longitude = 0.669064, display_name = "Pett"},
    ["Boughton Monchelsea"] = { latitude = 51.232170, longitude = 0.530222, display_name = "Boughton Monchelsea"},
    ["Catsfield"] = { latitude = 50.897500, longitude = 0.450511, display_name = "Catsfield"},
    ["East Guldeford"] = { latitude = 50.958813, longitude = 0.755803, display_name = "East Guldeford"},
    ["Rye Harbour"] = { latitude = 50.938298, longitude = 0.759768, display_name = "Rye Harbour"},
    ["Camber"] = { latitude = 50.935201, longitude = 0.795683, display_name = "Camber"},
    ["Peasmarsh"] = { latitude = 50.972788, longitude = 0.690662, display_name = "Peasmarsh"},
    ["Oversland"] = { latitude = 51.279307, longitude = 0.951784, display_name = "Oversland"},
    ["Penenden Heath"] = { latitude = 51.288097, longitude = 0.538713, display_name = "Penenden Heath"},
    ["Sittingbourne"] = { latitude = 51.339737, longitude = 0.734232, display_name = "Sittingbourne"},
    ["Snodland"] = { latitude = 51.329623, longitude = 0.442632, display_name = "Snodland"},
    ["River"] = { latitude = 51.143546, longitude = 1.274147, display_name = "River"},
    ["Sole Street"] = { latitude = 51.204643, longitude = 0.999889, display_name = "Sole Street"},
    ["Shorne Ridgeway"] = { latitude = 51.408383, longitude = 0.431770, display_name = "Shorne Ridgeway"},
    ["Flimwell"] = { latitude = 51.054847, longitude = 0.446861, display_name = "Flimwell"},
    ["Tyler Hill"] = { latitude = 51.307809, longitude = 1.069309, display_name = "Tyler Hill"},
    ["Chestfield"] = { latitude = 51.353758, longitude = 1.067317, display_name = "Chestfield"},
    ["Boughton Malherbe"] = { latitude = 51.215256, longitude = 0.694976, display_name = "Boughton Malherbe"},
    ["Iwade"] = { latitude = 51.376599, longitude = 0.727962, display_name = "Iwade"},
    ["Bobbing"] = { latitude = 51.353954, longitude = 0.709361, display_name = "Bobbing"},
    ["Rodmersham Green"] = { latitude = 51.319209, longitude = 0.747733, display_name = "Rodmersham Green"},
    ["Bredgar"] = { latitude = 51.313023, longitude = 0.696771, display_name = "Bredgar"},
    ["Milstead"] = { latitude = 51.295834, longitude = 0.728404, display_name = "Milstead"},
    ["Borden"] = { latitude = 51.333949, longitude = 0.702481, display_name = "Borden"},
    ["Tunstall"] = { latitude = 51.323568, longitude = 0.717667, display_name = "Tunstall"},
    ["Wormshill"] = { latitude = 51.283577, longitude = 0.694398, display_name = "Wormshill"},
    ["Sarre"] = { latitude = 51.338879, longitude = 1.238516, display_name = "Sarre"},
    ["Capel-le-Ferne"] = { latitude = 51.102164, longitude = 1.211693, display_name = "Capel-le-Ferne"},
    ["Playden"] = { latitude = 50.962397, longitude = 0.732905, display_name = "Playden"},
    ["Mountfield"] = { latitude = 50.956032, longitude = 0.479411, display_name = "Mountfield"},
    ["Seabrook"] = { latitude = 51.072958, longitude = 1.118642, display_name = "Seabrook"},
    ["Conyer"] = { latitude = 51.347093, longitude = 0.816969, display_name = "Conyer"},
    ["Guestling"] = { latitude = 50.890229, longitude = 0.630193, display_name = "Guestling"},
    ["Staplecross"] = { latitude = 50.973103, longitude = 0.539617, display_name = "Staplecross"},
    ["Kingsdown"] = { latitude = 51.186841, longitude = 1.402737, display_name = "Kingsdown"},
    ["Sandwich Bay"] = { latitude = 51.267788, longitude = 1.385374, display_name = "Sandwich Bay"},
    ["Minster on Sea"] = { latitude = 51.420236, longitude = 0.803194, display_name = "Minster on Sea"},
    ["Tonbridge"] = { latitude = 51.198712, longitude = 0.277477, display_name = "Tonbridge"},
    ["Royal Tunbridge Wells"] = { latitude = 51.138667, longitude = 0.261915, display_name = "Royal Tunbridge Wells"},
    ["Sevenoaks"] = { latitude = 51.275888, longitude = 0.182776, display_name = "Sevenoaks"},
    ["Oxted"] = { latitude = 51.260298, longitude = 0.031627, display_name = "Oxted"},
    ["East Grinstead"] = { latitude = 51.129870, longitude = -0.010519, display_name = "East Grinstead"},
    ["Crowborough"] = { latitude = 51.057392, longitude = 0.159431, display_name = "Crowborough"},
    ["Heathfield"] = { latitude = 50.965302, longitude = 0.250549, display_name = "Heathfield"},
    ["Burwash Common"] = { latitude = 50.984654, longitude = 0.316699, display_name = "Burwash Common"},
    ["Horam"] = { latitude = 50.933414, longitude = 0.246359, display_name = "Horam"},
    ["Hailsham"] = { latitude = 50.866286, longitude = 0.258547, display_name = "Hailsham"},
    ["Windmill Hill"] = { latitude = 50.883438, longitude = 0.348513, display_name = "Windmill Hill"},
    ["Eastbourne"] = { latitude = 50.769830, longitude = 0.282343, display_name = "Eastbourne"},
    ["Dunkirk"] = { latitude = 51.034771, longitude = 2.377253, display_name = "Dunkirk"},
    ["Coudekerque-Branche"] = { latitude = 51.020878, longitude = 2.389432, display_name = "Coudekerque-Branche"},
    ["Bailleul"] = { latitude = 50.739667, longitude = 2.734929, display_name = "Bailleul"},
    ["Grande-Synthe"] = { latitude = 51.013481, longitude = 2.302997, display_name = "Grande-Synthe"},
    ["Le Portel"] = { latitude = 50.707458, longitude = 1.573716, display_name = "Le Portel"},
    ["Isbergues"] = { latitude = 50.621124, longitude = 2.457463, display_name = "Isbergues"},
    ["Longuenesse"] = { latitude = 50.737100, longitude = 2.248330, display_name = "Longuenesse"},
    ["Gravelines"] = { latitude = 50.987070, longitude = 2.127312, display_name = "Gravelines"},
    ["Calais"] = { latitude = 50.948800, longitude = 1.874680, display_name = "Calais"},
    ["Saint-Martin-Boulogne"] = { latitude = 50.722905, longitude = 1.646774, display_name = "Saint-Martin-Boulogne"},
    ["Saint-Omer"] = { latitude = 50.752191, longitude = 2.254075, display_name = "Saint-Omer"},
    ["Hazebrouck"] = { latitude = 50.722611, longitude = 2.536033, display_name = "Hazebrouck"},
    ["Boulogne-sur-Mer"] = { latitude = 50.725998, longitude = 1.611877, display_name = "Boulogne-sur-Mer"},
    ["Ghyvelde"] = { latitude = 51.051857, longitude = 2.526378, display_name = "Ghyvelde"},
    ["Aire-sur-la-Lys"] = { latitude = 50.639594, longitude = 2.400060, display_name = "Aire-sur-la-Lys"},
    ["Vieux-Berquin"] = { latitude = 50.694805, longitude = 2.643792, display_name = "Vieux-Berquin"},
    ["Cappelle-la-Grande"] = { latitude = 50.997722, longitude = 2.367794, display_name = "Cappelle-la-Grande"},
    ["Bergues"] = { latitude = 50.968389, longitude = 2.432525, display_name = "Bergues"},
    ["Vendin-les-Bethune"] = { latitude = 50.546200, longitude = 2.604230, display_name = "Vendin-les-Bethune"},
    ["Desvres"] = { latitude = 50.667874, longitude = 1.834827, display_name = "Desvres"},
    ["Armbouts-Cappel"] = { latitude = 50.978044, longitude = 2.351581, display_name = "Armbouts-Cappel"},
    ["Cassel"] = { latitude = 50.800001, longitude = 2.486831, display_name = "Cassel"},
    ["Steenvoorde"] = { latitude = 50.810126, longitude = 2.581641, display_name = "Steenvoorde"},
    ["Teteghem"] = { latitude = 51.016805, longitude = 2.441295, display_name = "Teteghem"},
    ["Eperlecques"] = { latitude = 50.815400, longitude = 2.158610, display_name = "Eperlecques"},
    ["Camiers"] = { latitude = 50.563800, longitude = 1.614410, display_name = "Camiers"},
    ["Chocques"] = { latitude = 50.539186, longitude = 2.569606, display_name = "Chocques"},
    ["Meteren"] = { latitude = 50.740908, longitude = 2.691174, display_name = "Meteren"},
    ["Blendecques"] = { latitude = 50.717084, longitude = 2.281795, display_name = "Blendecques"},
    ["Marquise"] = { latitude = 50.812582, longitude = 1.699554, display_name = "Marquise"},
    ["Lestrem"] = { latitude = 50.621621, longitude = 2.685753, display_name = "Lestrem"},
    ["Saint-Etienne-au-Mont"] = { latitude = 50.681686, longitude = 1.626043, display_name = "Saint-Etienne-au-Mont"},
    ["Saint-Folquin"] = { latitude = 50.945910, longitude = 2.123789, display_name = "Saint-Folquin"},
    ["Beuvry"] = { latitude = 50.533719, longitude = 2.686479, display_name = "Beuvry"},
    ["Bourbourg"] = { latitude = 50.946662, longitude = 2.197370, display_name = "Bourbourg"},
    ["Rinxent"] = { latitude = 50.806400, longitude = 1.739970, display_name = "Rinxent"},
    ["Morbecque"] = { latitude = 50.691853, longitude = 2.515558, display_name = "Morbecque"},
    ["Saint-Venant"] = { latitude = 50.623765, longitude = 2.549267, display_name = "Saint-Venant"},
    ["Samer"] = { latitude = 50.639272, longitude = 1.746919, display_name = "Samer"},
    ["Saint-Leonard"] = { latitude = 50.688814, longitude = 1.627086, display_name = "Saint-Leonard"},
    ["Marck"] = { latitude = 50.949000, longitude = 1.951990, display_name = "Marck"},
    ["Ambleteuse"] = { latitude = 50.811233, longitude = 1.606669, display_name = "Ambleteuse"},
    ["Renescure"] = { latitude = 50.727611, longitude = 2.369521, display_name = "Renescure"},
    ["Hondschoote"] = { latitude = 50.980856, longitude = 2.586264, display_name = "Hondschoote"},
    ["Coulogne"] = { latitude = 50.925743, longitude = 1.884090, display_name = "Coulogne"},
    ["Grand-Fort-Philippe"] = { latitude = 51.001446, longitude = 2.104597, display_name = "Grand-Fort-Philippe"},
    ["Equihen-Plage"] = { latitude = 50.679393, longitude = 1.571665, display_name = "Equihen-Plage"},
    ["Guines"] = { latitude = 50.869086, longitude = 1.870441, display_name = "Guines"},
    ["Merville"] = { latitude = 50.643658, longitude = 2.638750, display_name = "Merville"},
    ["Loon-Plage"] = { latitude = 50.994610, longitude = 2.219246, display_name = "Loon-Plage"},
    ["Locon"] = { latitude = 50.570500, longitude = 2.666170, display_name = "Locon"},
    ["Lillers"] = { latitude = 50.560600, longitude = 2.476040, display_name = "Lillers"},
    ["Wimille"] = { latitude = 50.764109, longitude = 1.630328, display_name = "Wimille"},
    ["Gonnehem"] = { latitude = 50.562287, longitude = 2.573843, display_name = "Gonnehem"},
    ["Racquinghem"] = { latitude = 50.692974, longitude = 2.356774, display_name = "Racquinghem"},
    ["Watten"] = { latitude = 50.832402, longitude = 2.212066, display_name = "Watten"},
    ["La Gorgue"] = { latitude = 50.637260, longitude = 2.713401, display_name = "La Gorgue"},
    ["Esquelbecq"] = { latitude = 50.886213, longitude = 2.430913, display_name = "Esquelbecq"},
    ["Audruicq"] = { latitude = 50.878649, longitude = 2.075236, display_name = "Audruicq"},
    ["Saint-Martin-au-Laert"] = { latitude = 50.754529, longitude = 2.234968, display_name = "Saint-Martin-au-Laert"},
    ["Condette"] = { latitude = 50.649000, longitude = 1.642110, display_name = "Condette"},
    ["Arques"] = { latitude = 50.739664, longitude = 2.306207, display_name = "Arques"},
    ["Oye-Plage"] = { latitude = 50.980900, longitude = 2.041980, display_name = "Oye-Plage"},
    ["Bray-Dunes"] = { latitude = 51.071002, longitude = 2.524513, display_name = "Bray-Dunes"},
    ["Leffrinckoucke"] = { latitude = 51.050940, longitude = 2.438674, display_name = "Leffrinckoucke"},
    ["Hoymille"] = { latitude = 50.973018, longitude = 2.448505, display_name = "Hoymille"},
    ["Wizernes"] = { latitude = 50.711594, longitude = 2.230686, display_name = "Wizernes"},
    ["Burbure"] = { latitude = 50.537016, longitude = 2.465564, display_name = "Burbure"},
    ["Neufchatel-Hardelot"] = { latitude = 50.618849, longitude = 1.630616, display_name = "Neufchatel-Hardelot"},
    ["La Couture"] = { latitude = 50.580187, longitude = 2.712909, display_name = "La Couture"},
    ["Lumbres"] = { latitude = 50.705136, longitude = 2.121790, display_name = "Lumbres"},
    ["Ardres"] = { latitude = 50.853632, longitude = 1.978607, display_name = "Ardres"},
    ["Estaires"] = { latitude = 50.644026, longitude = 2.722651, display_name = "Estaires"},
    ["Richebourg"] = { latitude = 50.579253, longitude = 2.739930, display_name = "Richebourg"},
    ["Allouagne"] = { latitude = 50.532500, longitude = 2.507060, display_name = "Allouagne"},
    ["Annezin"] = { latitude = 50.541500, longitude = 2.619300, display_name = "Annezin"},
    ["Killem"] = { latitude = 50.957849, longitude = 2.562633, display_name = "Killem"},
    ["Watou"] = { latitude = 50.858554, longitude = 2.620280, display_name = "Watou"},
    ["Saint-Tricat"] = { latitude = 50.893532, longitude = 1.831895, display_name = "Saint-Tricat"},
    ["Escalles"] = { latitude = 50.917985, longitude = 1.713605, display_name = "Escalles"},
    ["Sint-Idesbald"] = { latitude = 51.109274, longitude = 2.612880, display_name = "Sint-Idesbald"},
    ["De Panne"] = { latitude = 51.098831, longitude = 2.592409, display_name = "De Panne"},
    ["Poperinge"] = { latitude = 50.855665, longitude = 2.726496, display_name = "Poperinge"},
    ["Winnezeele"] = { latitude = 50.840061, longitude = 2.551238, display_name = "Winnezeele"},
    ["Les Moeres"] = { latitude = 51.014607, longitude = 2.548824, display_name = "Les Moeres"},
    ["Wylder"] = { latitude = 50.911643, longitude = 2.493981, display_name = "Wylder"},
    ["Alveringem"] = { latitude = 51.011998, longitude = 2.710503, display_name = "Alveringem"},
    ["La Capelle-les-Boulogne"] = { latitude = 50.730602, longitude = 1.701125, display_name = "La Capelle-les-Boulogne"},
    ["Fletre"] = { latitude = 50.753348, longitude = 2.645652, display_name = "Fletre"},
    ["Godewaersvelde"] = { latitude = 50.794256, longitude = 2.642912, display_name = "Godewaersvelde"},
    ["Hesdigneul-les-Boulogne"] = { latitude = 50.659243, longitude = 1.672432, display_name = "Hesdigneul-les-Boulogne"},
    ["Saint-Inglevert"] = { latitude = 50.875508, longitude = 1.742566, display_name = "Saint-Inglevert"},
    ["Febvin-Palfart"] = { latitude = 50.538485, longitude = 2.315797, display_name = "Febvin-Palfart"},
    ["Laires"] = { latitude = 50.539137, longitude = 2.255314, display_name = "Laires"},
    ["Beaumetz-les-Aire"] = { latitude = 50.542628, longitude = 2.224930, display_name = "Beaumetz-les-Aire"},
    ["Uxem"] = { latitude = 51.020525, longitude = 2.485008, display_name = "Uxem"},
    ["Le Doulieu"] = { latitude = 50.682338, longitude = 2.718519, display_name = "Le Doulieu"},
    ["Neuf-Berquin"] = { latitude = 50.660048, longitude = 2.670113, display_name = "Neuf-Berquin"},
    ["Haverskerque"] = { latitude = 50.640764, longitude = 2.541629, display_name = "Haverskerque"},
    ["Merris"] = { latitude = 50.715997, longitude = 2.661152, display_name = "Merris"},
    ["Saint-Jans-Cappel"] = { latitude = 50.763955, longitude = 2.721262, display_name = "Saint-Jans-Cappel"},
    ["Berthen"] = { latitude = 50.783094, longitude = 2.694833, display_name = "Berthen"},
    ["Strazeele"] = { latitude = 50.726931, longitude = 2.632214, display_name = "Strazeele"},
    ["Pradelles"] = { latitude = 50.731925, longitude = 2.604972, display_name = "Pradelles"},
    ["Borre"] = { latitude = 50.731291, longitude = 2.584293, display_name = "Borre"},
    ["Caestre"] = { latitude = 50.758358, longitude = 2.603998, display_name = "Caestre"},
    ["Hondeghem"] = { latitude = 50.756499, longitude = 2.521257, display_name = "Hondeghem"},
    ["Thiennes"] = { latitude = 50.651121, longitude = 2.465992, display_name = "Thiennes"},
    ["Boeseghem"] = { latitude = 50.662021, longitude = 2.436942, display_name = "Boeseghem"},
    ["Steenbecque"] = { latitude = 50.673869, longitude = 2.484934, display_name = "Steenbecque"},
    ["Wallon-Cappel"] = { latitude = 50.727114, longitude = 2.474017, display_name = "Wallon-Cappel"},
    ["Boeschepe"] = { latitude = 50.800274, longitude = 2.691633, display_name = "Boeschepe"},
    ["Blaringhem"] = { latitude = 50.691533, longitude = 2.404284, display_name = "Blaringhem"},
    ["Sercus"] = { latitude = 50.706649, longitude = 2.456155, display_name = "Sercus"},
    ["Courset"] = { latitude = 50.647060, longitude = 1.840437, display_name = "Courset"},
    ["Clairmarais"] = { latitude = 50.769714, longitude = 2.303873, display_name = "Clairmarais"},
    ["Lynde"] = { latitude = 50.712720, longitude = 2.419570, display_name = "Lynde"},
    ["Ebblinghem"] = { latitude = 50.733310, longitude = 2.409402, display_name = "Ebblinghem"},
    ["Staple"] = { latitude = 50.749534, longitude = 2.454854, display_name = "Staple"},
    ["Bavinchove"] = { latitude = 50.786146, longitude = 2.455895, display_name = "Bavinchove"},
    ["Oxelaere"] = { latitude = 50.788573, longitude = 2.476408, display_name = "Oxelaere"},
    ["Saint-Sylvestre-Cappel"] = { latitude = 50.776539, longitude = 2.554424, display_name = "Saint-Sylvestre-Cappel"},
    ["Sainte-Marie-Cappel"] = { latitude = 50.783643, longitude = 2.510021, display_name = "Sainte-Marie-Cappel"},
    ["Eecke"] = { latitude = 50.779085, longitude = 2.596637, display_name = "Eecke"},
    ["Terdeghem"] = { latitude = 50.798750, longitude = 2.540184, display_name = "Terdeghem"},
    ["Wissant"] = { latitude = 50.885779, longitude = 1.663546, display_name = "Wissant"},
    ["Hardifort"] = { latitude = 50.820723, longitude = 2.485908, display_name = "Hardifort"},
    ["Oudezeele"] = { latitude = 50.838533, longitude = 2.510881, display_name = "Oudezeele"},
    ["Zuytpeene"] = { latitude = 50.793706, longitude = 2.430557, display_name = "Zuytpeene"},
    ["Noordpeene"] = { latitude = 50.805883, longitude = 2.396303, display_name = "Noordpeene"},
    ["Nieurlet"] = { latitude = 50.788599, longitude = 2.282161, display_name = "Nieurlet"},
    ["Saint-Momelin"] = { latitude = 50.794897, longitude = 2.250181, display_name = "Saint-Momelin"},
    ["Buysscheure"] = { latitude = 50.803823, longitude = 2.333426, display_name = "Buysscheure"},
    ["Lederzeele"] = { latitude = 50.819633, longitude = 2.303059, display_name = "Lederzeele"},
    ["Broxeele"] = { latitude = 50.830041, longitude = 2.319521, display_name = "Broxeele"},
    ["Rubrouck"] = { latitude = 50.838312, longitude = 2.353890, display_name = "Rubrouck"},
    ["Ochtezeele"] = { latitude = 50.817454, longitude = 2.400885, display_name = "Ochtezeele"},
    ["Wemaers-Cappel"] = { latitude = 50.806728, longitude = 2.440173, display_name = "Wemaers-Cappel"},
    ["Arneke"] = { latitude = 50.832041, longitude = 2.411757, display_name = "Arneke"},
    ["Frethun"] = { latitude = 50.919591, longitude = 1.825603, display_name = "Frethun"},
    ["Zermezeele"] = { latitude = 50.823506, longitude = 2.450104, display_name = "Zermezeele"},
    ["Wulverdinghe"] = { latitude = 50.831729, longitude = 2.255670, display_name = "Wulverdinghe"},
    ["Volckerinckhove"] = { latitude = 50.838367, longitude = 2.305745, display_name = "Volckerinckhove"},
    ["Bollezeele"] = { latitude = 50.865595, longitude = 2.327453, display_name = "Bollezeele"},
    ["Ledringhem"] = { latitude = 50.854568, longitude = 2.439630, display_name = "Ledringhem"},
    ["Holque"] = { latitude = 50.853697, longitude = 2.203616, display_name = "Holque"},
    ["Millam"] = { latitude = 50.854929, longitude = 2.248551, display_name = "Millam"},
    ["Merckeghem"] = { latitude = 50.861588, longitude = 2.295689, display_name = "Merckeghem"},
    ["Saint-Pierre-Brouck"] = { latitude = 50.896030, longitude = 2.189389, display_name = "Saint-Pierre-Brouck"},
    ["Cappelle-Brouck"] = { latitude = 50.901860, longitude = 2.223000, display_name = "Cappelle-Brouck"},
    ["Eringhem"] = { latitude = 50.896922, longitude = 2.344936, display_name = "Eringhem"},
    ["Zegerscappel"] = { latitude = 50.887716, longitude = 2.385718, display_name = "Zegerscappel"},
    ["Houtkerque"] = { latitude = 50.876696, longitude = 2.595551, display_name = "Houtkerque"},
    ["Bambecque"] = { latitude = 50.901262, longitude = 2.547679, display_name = "Bambecque"},
    ["Drincham"] = { latitude = 50.906747, longitude = 2.310390, display_name = "Drincham"},
    ["Bissezeele"] = { latitude = 50.911634, longitude = 2.408987, display_name = "Bissezeele"},
    ["Crochte"] = { latitude = 50.935284, longitude = 2.386984, display_name = "Crochte"},
    ["Pitgam"] = { latitude = 50.927799, longitude = 2.330493, display_name = "Pitgam"},
    ["Looberghe"] = { latitude = 50.916504, longitude = 2.272060, display_name = "Looberghe"},
    ["Steene"] = { latitude = 50.952225, longitude = 2.369409, display_name = "Steene"},
    ["West-Cappel"] = { latitude = 50.928273, longitude = 2.507585, display_name = "West-Cappel"},
    ["Socx"] = { latitude = 50.936013, longitude = 2.422840, display_name = "Socx"},
    ["Quaedypre"] = { latitude = 50.935395, longitude = 2.455633, display_name = "Quaedypre"},
    ["Bierne"] = { latitude = 50.963476, longitude = 2.409818, display_name = "Bierne"},
    ["Oost-Cappel"] = { latitude = 50.924611, longitude = 2.597743, display_name = "Oost-Cappel"},
    ["Rexpoede"] = { latitude = 50.939354, longitude = 2.538858, display_name = "Rexpoede"},
    ["Warhem"] = { latitude = 50.976760, longitude = 2.492958, display_name = "Warhem"},
    ["Brouckerque"] = { latitude = 50.954119, longitude = 2.292949, display_name = "Brouckerque"},
    ["Spycker"] = { latitude = 50.968436, longitude = 2.322961, display_name = "Spycker"},
    ["Saint-Georges-sur-l'Aa"] = { latitude = 50.970102, longitude = 2.164564, display_name = "Saint-Georges-sur-l'Aa"},
    ["Craywick"] = { latitude = 50.970686, longitude = 2.235816, display_name = "Craywick"},
    ["Coudekerque-Village"] = { latitude = 50.994443, longitude = 2.417163, display_name = "Coudekerque-Village"},
    ["Zuydcoote"] = { latitude = 51.063909, longitude = 2.490711, display_name = "Zuydcoote"},
    ["Hardinghen"] = { latitude = 50.805869, longitude = 1.821639, display_name = "Hardinghen"},
    ["Boursin"] = { latitude = 50.776524, longitude = 1.834829, display_name = "Boursin"},
    ["Colembert"] = { latitude = 50.747612, longitude = 1.838257, display_name = "Colembert"},
    ["Le Wast"] = { latitude = 50.750291, longitude = 1.802118, display_name = "Le Wast"},
    ["Wierre-Effroy"] = { latitude = 50.779597, longitude = 1.738891, display_name = "Wierre-Effroy"},
    ["Belle-et-Houllefort"] = { latitude = 50.744787, longitude = 1.760298, display_name = "Belle-et-Houllefort"},
    ["Cremarest"] = { latitude = 50.701073, longitude = 1.785933, display_name = "Cremarest"},
    ["Bellebrune"] = { latitude = 50.726348, longitude = 1.774510, display_name = "Bellebrune"},
    ["Pittefaux"] = { latitude = 50.756594, longitude = 1.684987, display_name = "Pittefaux"},
    ["Pernes-les-Boulogne"] = { latitude = 50.752184, longitude = 1.701034, display_name = "Pernes-les-Boulogne"},
    ["Offrethun"] = { latitude = 50.783693, longitude = 1.693148, display_name = "Offrethun"},
    ["Wacquinghen"] = { latitude = 50.783066, longitude = 1.667264, display_name = "Wacquinghen"},
    ["Audresselles"] = { latitude = 50.824181, longitude = 1.595736, display_name = "Audresselles"},
    ["Audinghen"] = { latitude = 50.852665, longitude = 1.614320, display_name = "Audinghen"},
    ["Tardinghen"] = { latitude = 50.866592, longitude = 1.631452, display_name = "Tardinghen"},
    ["Bazinghen"] = { latitude = 50.825670, longitude = 1.662221, display_name = "Bazinghen"},
    ["Leulinghen-Bernes"] = { latitude = 50.832894, longitude = 1.712123, display_name = "Leulinghen-Bernes"},
    ["Audembert"] = { latitude = 50.861176, longitude = 1.694268, display_name = "Audembert"},
    ["Leubringhen"] = { latitude = 50.858229, longitude = 1.720168, display_name = "Leubringhen"},
    ["Hervelinghen"] = { latitude = 50.881809, longitude = 1.713988, display_name = "Hervelinghen"},
    ["Pihen-les-Guines"] = { latitude = 50.870387, longitude = 1.787442, display_name = "Pihen-les-Guines"},
    ["Landrethun-le-Nord"] = { latitude = 50.847112, longitude = 1.782324, display_name = "Landrethun-le-Nord"},
    ["Ferques"] = { latitude = 50.831049, longitude = 1.767750, display_name = "Ferques"},
    ["Caffiers"] = { latitude = 50.841202, longitude = 1.809751, display_name = "Caffiers"},
    ["Fiennes"] = { latitude = 50.828352, longitude = 1.827018, display_name = "Fiennes"},
    ["Doudeauville"] = { latitude = 50.614815, longitude = 1.835048, display_name = "Doudeauville"},
    ["Wirwignes"] = { latitude = 50.683400, longitude = 1.762810, display_name = "Wirwignes"},
    ["Bonningues-les-Calais"] = { latitude = 50.890385, longitude = 1.774088, display_name = "Bonningues-les-Calais"},
    ["Hucqueliers"] = { latitude = 50.569576, longitude = 1.907205, display_name = "Hucqueliers"},
    ["Bourthes"] = { latitude = 50.606253, longitude = 1.932188, display_name = "Bourthes"},
    ["Brunembert"] = { latitude = 50.713746, longitude = 1.893926, display_name = "Brunembert"},
    ["Selles"] = { latitude = 50.700539, longitude = 1.896464, display_name = "Selles"},
    ["Dannes"] = { latitude = 50.588960, longitude = 1.616037, display_name = "Dannes"},
    ["Wardrecques"] = { latitude = 50.708784, longitude = 2.345237, display_name = "Wardrecques"},
    ["Quiestede"] = { latitude = 50.679231, longitude = 2.316379, display_name = "Quiestede"},
    ["Roquetoire"] = { latitude = 50.668017, longitude = 2.337788, display_name = "Roquetoire"},
    ["Wittes"] = { latitude = 50.669305, longitude = 2.389990, display_name = "Wittes"},
    ["Tatinghem"] = { latitude = 50.742310, longitude = 2.206800, display_name = "Tatinghem"},
    ["Wisques"] = { latitude = 50.722827, longitude = 2.192666, display_name = "Wisques"},
    ["Hallines"] = { latitude = 50.709050, longitude = 2.209680, display_name = "Hallines"},
    ["Helfaut"] = { latitude = 50.696726, longitude = 2.240340, display_name = "Helfaut"},
    ["Echinghen"] = { latitude = 50.703418, longitude = 1.647763, display_name = "Echinghen"},
    ["Baincthun"] = { latitude = 50.710464, longitude = 1.680928, display_name = "Baincthun"},
    ["Isques"] = { latitude = 50.682451, longitude = 1.644526, display_name = "Isques"},
    ["Alincthun"] = { latitude = 50.731247, longitude = 1.803067, display_name = "Alincthun"},
    ["Henneveux"] = { latitude = 50.723805, longitude = 1.851491, display_name = "Henneveux"},
    ["Menneville"] = { latitude = 50.675532, longitude = 1.861136, display_name = "Menneville"},
    ["Saint-Martin-Choquel"] = { latitude = 50.671384, longitude = 1.880557, display_name = "Saint-Martin-Choquel"},
    ["Senlecques"] = { latitude = 50.648719, longitude = 1.937276, display_name = "Senlecques"},
    ["Lottinghen"] = { latitude = 50.683841, longitude = 1.932854, display_name = "Lottinghen"},
    ["Quesques"] = { latitude = 50.704463, longitude = 1.934006, display_name = "Quesques"},
    ["Longueville"] = { latitude = 50.731896, longitude = 1.879146, display_name = "Longueville"},
    ["Nabringhen"] = { latitude = 50.743744, longitude = 1.863321, display_name = "Nabringhen"},
    ["Bournonville"] = { latitude = 50.706454, longitude = 1.850190, display_name = "Bournonville"},
    ["Bainghen"] = { latitude = 50.752865, longitude = 1.907283, display_name = "Bainghen"},
    ["Saint-Omer-Capelle"] = { latitude = 50.939181, longitude = 2.104024, display_name = "Saint-Omer-Capelle"},
    ["Vieille-Eglise"] = { latitude = 50.928821, longitude = 2.078158, display_name = "Vieille-Eglise"},
    ["Nouvelle-Eglise"] = { latitude = 50.924954, longitude = 2.053725, display_name = "Nouvelle-Eglise"},
    ["Offekerque"] = { latitude = 50.941212, longitude = 2.019745, display_name = "Offekerque"},
    ["Guemps"] = { latitude = 50.916922, longitude = 1.996767, display_name = "Guemps"},
    ["Nortkerque"] = { latitude = 50.876134, longitude = 2.025252, display_name = "Nortkerque"},
    ["Sainte-Marie-Kerque"] = { latitude = 50.900592, longitude = 2.137458, display_name = "Sainte-Marie-Kerque"},
    ["Zutkerque"] = { latitude = 50.853410, longitude = 2.067984, display_name = "Zutkerque"},
    ["Polincove"] = { latitude = 50.854509, longitude = 2.102259, display_name = "Polincove"},
    ["Ruminghem"] = { latitude = 50.860075, longitude = 2.157454, display_name = "Ruminghem"},
    ["Recques-sur-Hem"] = { latitude = 50.836537, longitude = 2.088250, display_name = "Recques-sur-Hem"},
    ["Muncq-Nieurlet"] = { latitude = 50.848774, longitude = 2.115295, display_name = "Muncq-Nieurlet"},
    ["Houlle"] = { latitude = 50.797741, longitude = 2.172855, display_name = "Houlle"},
    ["Moulle"] = { latitude = 50.788109, longitude = 2.175840, display_name = "Moulle"},
    ["Serques"] = { latitude = 50.793517, longitude = 2.202695, display_name = "Serques"},
    ["Tilques"] = { latitude = 50.777735, longitude = 2.205176, display_name = "Tilques"},
    ["Campagne-les-Boulonnais"] = { latitude = 50.613667, longitude = 1.996914, display_name = "Campagne-les-Boulonnais"},
    ["Coyecques"] = { latitude = 50.603247, longitude = 2.183082, display_name = "Coyecques"},
    ["Remilly-Wirquin"] = { latitude = 50.667494, longitude = 2.164347, display_name = "Remilly-Wirquin"},
    ["Acquin-Westbecourt"] = { latitude = 50.728756, longitude = 2.089946, display_name = "Acquin-Westbecourt"},
    ["Quercamps"] = { latitude = 50.753074, longitude = 2.051011, display_name = "Quercamps"},
    ["Bomy"] = { latitude = 50.572070, longitude = 2.234239, display_name = "Bomy"},
    ["Tournehem-sur-la-Hem"] = { latitude = 50.804719, longitude = 2.044890, display_name = "Tournehem-sur-la-Hem"},
    ["Coulomby"] = { latitude = 50.706189, longitude = 2.008824, display_name = "Coulomby"},
    ["Conteville-les-Boulogne"] = { latitude = 50.745072, longitude = 1.730434, display_name = "Conteville-les-Boulogne"},
    ["Widehem"] = { latitude = 50.585707, longitude = 1.672850, display_name = "Widehem"},
    ["Halinghen"] = { latitude = 50.602146, longitude = 1.691053, display_name = "Halinghen"},
    ["Carly"] = { latitude = 50.652045, longitude = 1.701175, display_name = "Carly"},
    ["Questrecques"] = { latitude = 50.664563, longitude = 1.746874, display_name = "Questrecques"},
    ["Hesdin-l'Abbe"] = { latitude = 50.667044, longitude = 1.680201, display_name = "Hesdin-l'Abbe"},
    ["Tubeauville"] = { latitude = 50.589381, longitude = 1.785342, display_name = "Tubeauville"},
    ["Parenty"] = { latitude = 50.588629, longitude = 1.810162, display_name = "Parenty"},
    ["Rety"] = { latitude = 50.796762, longitude = 1.774046, display_name = "Rety"},
    ["Le Bois-Julien"] = { latitude = 50.637308, longitude = 1.815147, display_name = "Le Bois-Julien"},
    ["Zouafques"] = { latitude = 50.816142, longitude = 2.054475, display_name = "Zouafques"},
    ["La Capelle-les-Boulogne"] = { latitude = 50.731180, longitude = 1.714619, display_name = "La Capelle-les-Boulogne"},
    ["Beuvrequen"] = { latitude = 50.801434, longitude = 1.665049, display_name = "Beuvrequen"},
    ["Heuringhem"] = { latitude = 50.696059, longitude = 2.283668, display_name = "Heuringhem"},
    ["Inghem"] = { latitude = 50.668344, longitude = 2.243240, display_name = "Inghem"},
    ["Therouanne"] = { latitude = 50.636334, longitude = 2.257719, display_name = "Therouanne"},
    ["Herbelles"] = { latitude = 50.655227, longitude = 2.222924, display_name = "Herbelles"},
    ["Pollinkhove"] = { latitude = 50.969266, longitude = 2.730865, display_name = "Pollinkhove"},
    ["Proven"] = { latitude = 50.890344, longitude = 2.654287, display_name = "Proven"},
    ["Roesbrugge-Haringe"] = { latitude = 50.915053, longitude = 2.626259, display_name = "Roesbrugge-Haringe"},
    ["Westvleteren"] = { latitude = 50.927286, longitude = 2.717444, display_name = "Westvleteren"},
    ["Oostvleteren"] = { latitude = 50.933701, longitude = 2.741004, display_name = "Oostvleteren"},
    ["Adinkerke"] = { latitude = 51.076216, longitude = 2.598567, display_name = "Adinkerke"},
    ["Avekapelle"] = { latitude = 51.066764, longitude = 2.733326, display_name = "Avekapelle"},
    ["Oostduinkerke"] = { latitude = 51.115634, longitude = 2.681266, display_name = "Oostduinkerke"},
    ["Sangatte"] = { latitude = 50.945804, longitude = 1.753713, display_name = "Sangatte"},
    ["Alquines"] = { latitude = 50.740526, longitude = 1.992636, display_name = "Alquines"},
    ["Amettes"] = { latitude = 50.531129, longitude = 2.394840, display_name = "Amettes"},
    ["Andres"] = { latitude = 50.863969, longitude = 1.914154, display_name = "Andres"},
    ["Auchy-au-Bois"] = { latitude = 50.555179, longitude = 2.371047, display_name = "Auchy-au-Bois"},
    ["Audincthun"] = { latitude = 50.582893, longitude = 2.143174, display_name = "Audincthun"},
    ["Avroult"] = { latitude = 50.633519, longitude = 2.146319, display_name = "Avroult"},
    ["Busnes"] = { latitude = 50.587892, longitude = 2.518470, display_name = "Busnes"},
    ["Witternesse"] = { latitude = 50.610836, longitude = 2.359894, display_name = "Witternesse"},
    ["Blessy"] = { latitude = 50.616305, longitude = 2.331492, display_name = "Blessy"},
    ["Bourecq"] = { latitude = 50.571648, longitude = 2.433932, display_name = "Bourecq"},
    ["Calonne-sur-la-Lys"] = { latitude = 50.623651, longitude = 2.615153, display_name = "Calonne-sur-la-Lys"},
    ["Clety"] = { latitude = 50.651854, longitude = 2.181948, display_name = "Clety"},
    ["Dennebroeucq"] = { latitude = 50.572489, longitude = 2.152864, display_name = "Dennebroeucq"},
    ["Ecquedecques"] = { latitude = 50.562119, longitude = 2.448703, display_name = "Ecquedecques"},
    ["Enguinegatte"] = { latitude = 50.607409, longitude = 2.271558, display_name = "Enguinegatte"},
    ["Enquin-les-Mines"] = { latitude = 50.588395, longitude = 2.286359, display_name = "Enquin-les-Mines"},
    ["Erny-Saint-Julien"] = { latitude = 50.585429, longitude = 2.253229, display_name = "Erny-Saint-Julien"},
    ["Essars"] = { latitude = 50.547241, longitude = 2.664094, display_name = "Essars"},
    ["Estree-Blanche"] = { latitude = 50.592872, longitude = 2.321795, display_name = "Estree-Blanche"},
    ["Festubert"] = { latitude = 50.542610, longitude = 2.736511, display_name = "Festubert"},
    ["Flechin"] = { latitude = 50.558738, longitude = 2.292781, display_name = "Flechin"},
    ["Guarbecque"] = { latitude = 50.609751, longitude = 2.488475, display_name = "Guarbecque"},
    ["Hames-Boucres"] = { latitude = 50.880241, longitude = 1.841748, display_name = "Hames-Boucres"},
    ["Ham-en-Artois"] = { latitude = 50.590203, longitude = 2.454749, display_name = "Ham-en-Artois"},
    ["Hinges"] = { latitude = 50.565461, longitude = 2.621913, display_name = "Hinges"},
    ["Le Maisnil-Boutry"] = { latitude = 50.644241, longitude = 2.002657, display_name = "Le Maisnil-Boutry"},
    ["Drionville"] = { latitude = 50.645111, longitude = 2.053126, display_name = "Drionville"},
    ["Lambres"] = { latitude = 50.617833, longitude = 2.395999, display_name = "Lambres"},
    ["Ledinghem"] = { latitude = 50.652450, longitude = 1.988667, display_name = "Ledinghem"},
    ["Liettres"] = { latitude = 50.595750, longitude = 2.341847, display_name = "Liettres"},
    ["Ligny-les-Aire"] = { latitude = 50.557335, longitude = 2.349206, display_name = "Ligny-les-Aire"},
    ["Linghem"] = { latitude = 50.595558, longitude = 2.371814, display_name = "Linghem"},
    ["Westrehem"] = { latitude = 50.545780, longitude = 2.345838, display_name = "Westrehem"},
    ["Mont-Bernanchon"] = { latitude = 50.593087, longitude = 2.602329, display_name = "Mont-Bernanchon"},
    ["Norrent-Fontes"] = { latitude = 50.585552, longitude = 2.410162, display_name = "Norrent-Fontes"},
    ["Merck-Saint-Lievin"] = { latitude = 50.627330, longitude = 2.114108, display_name = "Merck-Saint-Lievin"},
    ["Matringhem"] = { latitude = 50.545919, longitude = 2.165546, display_name = "Matringhem"},
    ["Quernes"] = { latitude = 50.605240, longitude = 2.363373, display_name = "Quernes"},
    ["Robecq"] = { latitude = 50.595018, longitude = 2.563650, display_name = "Robecq"},
    ["Saint-Floris"] = { latitude = 50.627363, longitude = 2.570130, display_name = "Saint-Floris"},
    ["Vieille-Chapelle"] = { latitude = 50.590701, longitude = 2.703245, display_name = "Vieille-Chapelle"},
    ["Renty"] = { latitude = 50.580661, longitude = 2.073618, display_name = "Renty"},
    ["Radinghem"] = { latitude = 50.553405, longitude = 2.122892, display_name = "Radinghem"},
    ["Rely"] = { latitude = 50.572872, longitude = 2.360502, display_name = "Rely"},
    ["Saint-Hilaire-Cottes"] = { latitude = 50.570344, longitude = 2.414031, display_name = "Saint-Hilaire-Cottes"},
    ["Senlis"] = { latitude = 50.533161, longitude = 2.152329, display_name = "Senlis"},
    ["Ecques"] = { latitude = 50.669543, longitude = 2.286275, display_name = "Ecques"},
    ["Clarques"] = { latitude = 50.646610, longitude = 2.276508, display_name = "Clarques"},
    ["Nielles-les-Blequin"] = { latitude = 50.673466, longitude = 2.030379, display_name = "Nielles-les-Blequin"},
    ["Delettes"] = { latitude = 50.618732, longitude = 2.213249, display_name = "Delettes"},
    ["Dohem"] = { latitude = 50.637767, longitude = 2.186722, display_name = "Dohem"},
    ["Setques"] = { latitude = 50.712219, longitude = 2.157863, display_name = "Setques"},
    ["Sainte-Cecile-Plage"] = { latitude = 50.574716, longitude = 1.582627, display_name = "Sainte-Cecile-Plage"},
    ["Inxent"] = { latitude = 50.535681, longitude = 1.784042, display_name = "Inxent"},
    ["Campagne-les-Guines"] = { latitude = 50.841095, longitude = 1.902068, display_name = "Campagne-les-Guines"},
    ["Peuplingues"] = { latitude = 50.915136, longitude = 1.769888, display_name = "Peuplingues"},
    ["Framezelle"] = { latitude = 50.863571, longitude = 1.592289, display_name = "Framezelle"},
    ["Wormhout"] = { latitude = 50.882763, longitude = 2.468733, display_name = "Wormhout"},
    ["Herzeele"] = { latitude = 50.886001, longitude = 2.531993, display_name = "Herzeele"},
    ["Le Grand Millebrugghe"] = { latitude = 50.965001, longitude = 2.347062, display_name = "Le Grand Millebrugghe"},
    ["Veurne"] = { latitude = 51.072427, longitude = 2.662132, display_name = "Veurne"},
    ["Moringhem"] = { latitude = 50.763120, longitude = 2.127110, display_name = "Moringhem"},
    ["Quelmes"] = { latitude = 50.732691, longitude = 2.136718, display_name = "Quelmes"},
    ["Leulinghem"] = { latitude = 50.734748, longitude = 2.164308, display_name = "Leulinghem"},
    ["Affringues"] = { latitude = 50.689861, longitude = 2.074325, display_name = "Affringues"},
    ["Bayenghem-les-Seninghem"] = { latitude = 50.699930, longitude = 2.075930, display_name = "Bayenghem-les-Seninghem"},
    ["Bernieulles"] = { latitude = 50.554600, longitude = 1.772850, display_name = "Bernieulles"},
    ["Bezinghem"] = { latitude = 50.593710, longitude = 1.825480, display_name = "Bezinghem"},
    ["Bimont"] = { latitude = 50.541460, longitude = 1.902770, display_name = "Bimont"},
    ["Blequin"] = { latitude = 50.663360, longitude = 1.989060, display_name = "Blequin"},
    ["Boisdinghem"] = { latitude = 50.748520, longitude = 2.093420, display_name = "Boisdinghem"},
    ["Bouvelinghem"] = { latitude = 50.734246, longitude = 2.034073, display_name = "Bouvelinghem"},
    ["Elnes"] = { latitude = 50.689520, longitude = 2.125570, display_name = "Elnes"},
    ["Enquin-sur-Baillons"] = { latitude = 50.572260, longitude = 1.835850, display_name = "Enquin-sur-Baillons"},
    ["Ergny"] = { latitude = 50.583410, longitude = 1.980800, display_name = "Ergny"},
    ["Escoeuilles"] = { latitude = 50.725650, longitude = 1.925600, display_name = "Escoeuilles"},
    ["Esquerdes"] = { latitude = 50.705520, longitude = 2.182975, display_name = "Esquerdes"},
    ["Haut-Loquin"] = { latitude = 50.739450, longitude = 1.966310, display_name = "Haut-Loquin"},
    ["Herly"] = { latitude = 50.546650, longitude = 1.985770, display_name = "Herly"},
    ["Mametz"] = { latitude = 50.634220, longitude = 2.324090, display_name = "Mametz"},
    ["Maninghem"] = { latitude = 50.542670, longitude = 1.939360, display_name = "Maninghem"},
    ["Ouve-Wirquin"] = { latitude = 50.650160, longitude = 2.143190, display_name = "Ouve-Wirquin"},
    ["Preures"] = { latitude = 50.571740, longitude = 1.876730, display_name = "Preures"},
    ["Rebecques"] = { latitude = 50.644890, longitude = 2.305600, display_name = "Rebecques"},
    ["Rumilly"] = { latitude = 50.576510, longitude = 2.014770, display_name = "Rumilly"},
    ["Seninghem"] = { latitude = 50.702354, longitude = 2.036721, display_name = "Seninghem"},
    ["Surques"] = { latitude = 50.739934, longitude = 1.916742, display_name = "Surques"},
    ["Verchocq"] = { latitude = 50.564189, longitude = 2.036242, display_name = "Verchocq"},
    ["Wavrans-sur-l'Aa"] = { latitude = 50.682410, longitude = 2.135580, display_name = "Wavrans-sur-l'Aa"},
    ["Wicquinghem"] = { latitude = 50.575170, longitude = 1.961230, display_name = "Wicquinghem"},
    ["Wismes"] = { latitude = 50.653230, longitude = 2.071140, display_name = "Wismes"},
    ["Zoteux"] = { latitude = 50.610360, longitude = 1.878690, display_name = "Zoteux"},
    ["Les Attaques"] = { latitude = 50.907231, longitude = 1.933694, display_name = "Les Attaques"},
    ["Nielles-les-Calais"] = { latitude = 50.907587, longitude = 1.829358, display_name = "Nielles-les-Calais"},
    ["Maninghen-Henne"] = { latitude = 50.768253, longitude = 1.668139, display_name = "Maninghen-Henne"},
    ["Hermelinghen"] = { latitude = 50.802584, longitude = 1.858957, display_name = "Hermelinghen"},
    ["Bouquehault"] = { latitude = 50.826595, longitude = 1.902510, display_name = "Bouquehault"},
    ["Alembon"] = { latitude = 50.784734, longitude = 1.887005, display_name = "Alembon"},
    ["Sanghen"] = { latitude = 50.776102, longitude = 1.900075, display_name = "Sanghen"},
    ["Balinghem"] = { latitude = 50.860659, longitude = 1.943506, display_name = "Balinghem"},
    ["Bremes"] = { latitude = 50.857017, longitude = 1.961340, display_name = "Bremes"},
    ["Rodelinghem"] = { latitude = 50.838050, longitude = 1.928728, display_name = "Rodelinghem"},
    ["Herbinghen"] = { latitude = 50.772682, longitude = 1.912526, display_name = "Herbinghen"},
    ["Licques"] = { latitude = 50.784662, longitude = 1.931790, display_name = "Licques"},
    ["Nielles-les-Ardres"] = { latitude = 50.842190, longitude = 2.016594, display_name = "Nielles-les-Ardres"},
    ["Autingues"] = { latitude = 50.842121, longitude = 1.985072, display_name = "Autingues"},
    ["Louches"] = { latitude = 50.829819, longitude = 2.004976, display_name = "Louches"},
    ["Hocquinghen"] = { latitude = 50.769037, longitude = 1.937095, display_name = "Hocquinghen"},
    ["Clerques"] = { latitude = 50.793353, longitude = 1.993673, display_name = "Clerques"},
    ["Audrehem"] = { latitude = 50.781378, longitude = 1.989835, display_name = "Audrehem"},
    ["Bonningues-les-Ardres"] = { latitude = 50.791896, longitude = 2.013201, display_name = "Bonningues-les-Ardres"},
    ["Rebergues"] = { latitude = 50.754289, longitude = 1.958913, display_name = "Rebergues"},
    ["Journy"] = { latitude = 50.753379, longitude = 1.994703, display_name = "Journy"},
    ["Nordausques"] = { latitude = 50.818666, longitude = 2.079704, display_name = "Nordausques"},
    ["Nort-Leulinghem"] = { latitude = 50.801674, longitude = 2.092434, display_name = "Nort-Leulinghem"},
    ["Bayenghem-les-Eperlecques"] = { latitude = 50.806406, longitude = 2.125144, display_name = "Bayenghem-les-Eperlecques"},
    ["Mentque-Nortbecourt"] = { latitude = 50.769882, longitude = 2.090772, display_name = "Mentque-Nortbecourt"},
    ["Zudausques"] = { latitude = 50.749725, longitude = 2.149092, display_name = "Zudausques"},
    ["Vaudringhem"] = { latitude = 50.661744, longitude = 2.029476, display_name = "Vaudringhem"},
    ["Campagne-les-Wardrecques"] = { latitude = 50.717803, longitude = 2.333551, display_name = "Campagne-les-Wardrecques"},
    ["Longfosse"] = { latitude = 50.651674, longitude = 1.805305, display_name = "Longfosse"},
    ["Wierre-au-Bois"] = { latitude = 50.645164, longitude = 1.762960, display_name = "Wierre-au-Bois"},
    ["Tingry"] = { latitude = 50.618031, longitude = 1.730393, display_name = "Tingry"},
    ["Lacres"] = { latitude = 50.600977, longitude = 1.761913, display_name = "Lacres"},
    ["Becourt"] = { latitude = 50.638133, longitude = 1.910800, display_name = "Becourt"},
    ["Hubersent"] = { latitude = 50.581108, longitude = 1.726437, display_name = "Hubersent"},
    ["Frencq"] = { latitude = 50.561810, longitude = 1.699536, display_name = "Frencq"},
    ["Cormont"] = { latitude = 50.561374, longitude = 1.735293, display_name = "Cormont"},
    ["Lefaux"] = { latitude = 50.542416, longitude = 1.659116, display_name = "Lefaux"},
    ["Longvilliers"] = { latitude = 50.543611, longitude = 1.727947, display_name = "Longvilliers"},
    ["Beussent"] = { latitude = 50.545930, longitude = 1.795064, display_name = "Beussent"},
    ["Avesnes"] = { latitude = 50.550698, longitude = 1.969351, display_name = "Avesnes"},
    ["Reclinghem"] = { latitude = 50.572213, longitude = 2.174423, display_name = "Reclinghem"},
    ["Vincly"] = { latitude = 50.559224, longitude = 2.171241, display_name = "Vincly"},
    ["Ames"] = { latitude = 50.536715, longitude = 2.408654, display_name = "Ames"},
    ["Lespesses"] = { latitude = 50.563281, longitude = 2.422028, display_name = "Lespesses"},
    ["Lieres"] = { latitude = 50.554453, longitude = 2.417038, display_name = "Lieres"},
    ["Mazinghem"] = { latitude = 50.601999, longitude = 2.405251, display_name = "Mazinghem"},
    ["Rombly"] = { latitude = 50.596850, longitude = 2.388703, display_name = "Rombly"},
    ["Oblinghem"] = { latitude = 50.549723, longitude = 2.599434, display_name = "Oblinghem"},
    ["Landrethun-les-Ardres"] = { latitude = 50.825020, longitude = 1.960463, display_name = "Landrethun-les-Ardres"},
    ["Audenfort"] = { latitude = 50.783137, longitude = 1.970780, display_name = "Audenfort"},
    ["Mentque"] = { latitude = 50.783977, longitude = 2.085649, display_name = "Mentque"},
    ["Pihem"] = { latitude = 50.682756, longitude = 2.213203, display_name = "Pihem"},
    ["Wandonne"] = { latitude = 50.564399, longitude = 2.126731, display_name = "Wandonne"},
    ["Cuhem"] = { latitude = 50.569579, longitude = 2.277348, display_name = "Cuhem"},
    ["Le Clivet"] = { latitude = 50.567150, longitude = 1.888315, display_name = "Le Clivet"},
    ["Wimereux"] = { latitude = 50.769686, longitude = 1.611861, display_name = "Wimereux"},
    ["Thiembronne"] = { latitude = 50.621035, longitude = 2.058926, display_name = "Thiembronne"},
    ["Outtersteene"] = { latitude = 50.712098, longitude = 2.682043, display_name = "Outtersteene"},
    ["Salperwick"] = { latitude = 50.771874, longitude = 2.230078, display_name = "Salperwick"},
    ["Noeux-les-Mines"] = { latitude = 50.475400, longitude = 2.662190, display_name = "Noeux-les-Mines"},
    ["Bethune"] = { latitude = 50.519900, longitude = 2.647810, display_name = "Bethune"},
    ["Abbeville"] = { latitude = 50.106083, longitude = 1.833703, display_name = "Abbeville"},
    ["Etaples"] = { latitude = 50.513955, longitude = 1.638625, display_name = "Etaples"},
    ["Albert"] = { latitude = 50.001802, longitude = 2.650922, display_name = "Albert"},
    ["Berck"] = { latitude = 50.405258, longitude = 1.571162, display_name = "Berck"},
    ["Dieppe"] = { latitude = 49.924618, longitude = 1.079144, display_name = "Dieppe"},
    ["Bruay-la-Buissiere"] = { latitude = 50.482196, longitude = 2.546192, display_name = "Bruay-la-Buissiere"},
    ["Auchel"] = { latitude = 50.503749, longitude = 2.468206, display_name = "Auchel"},
    ["Gamaches"] = { latitude = 49.985523, longitude = 1.555718, display_name = "Gamaches"},
    ["Avesnes-le-Comte"] = { latitude = 50.276202, longitude = 2.527700, display_name = "Avesnes-le-Comte"},
    ["Blangy-sur-Bresle"] = { latitude = 49.932376, longitude = 1.629683, display_name = "Blangy-sur-Bresle"},
    ["Flesselles"] = { latitude = 50.004900, longitude = 2.263360, display_name = "Flesselles"},
    ["Hesdin"] = { latitude = 50.373000, longitude = 2.036614, display_name = "Hesdin"},
    ["Doullens"] = { latitude = 50.157209, longitude = 2.341053, display_name = "Doullens"},
    ["Saint-Valery-sur-Somme"] = { latitude = 50.188701, longitude = 1.627915, display_name = "Saint-Valery-sur-Somme"},
    ["Haillicourt"] = { latitude = 50.475100, longitude = 2.577930, display_name = "Haillicourt"},
    ["Beauval"] = { latitude = 50.106225, longitude = 2.331653, display_name = "Beauval"},
    ["Mers-les-Bains"] = { latitude = 50.065632, longitude = 1.388970, display_name = "Mers-les-Bains"},
    ["Feuquieres-en-Vimeu"] = { latitude = 50.060937, longitude = 1.609549, display_name = "Feuquieres-en-Vimeu"},
    ["Barlin"] = { latitude = 50.456836, longitude = 2.617477, display_name = "Barlin"},
    ["Fressenneville"] = { latitude = 50.068204, longitude = 1.575208, display_name = "Fressenneville"},
    ["Ault"] = { latitude = 50.101564, longitude = 1.447301, display_name = "Ault"},
    ["Hersin-Coupigny"] = { latitude = 50.447236, longitude = 2.648495, display_name = "Hersin-Coupigny"},
    ["Merlimont"] = { latitude = 50.455808, longitude = 1.613436, display_name = "Merlimont"},
    ["Cayeux-sur-Mer"] = { latitude = 50.179200, longitude = 1.493384, display_name = "Cayeux-sur-Mer"},
    ["Saint-Ouen"] = { latitude = 50.036830, longitude = 2.122374, display_name = "Saint-Ouen"},
    ["Frevent"] = { latitude = 50.278213, longitude = 2.292979, display_name = "Frevent"},
    ["Verquin"] = { latitude = 50.501142, longitude = 2.641114, display_name = "Verquin"},
    ["Auxi-le-Chateau"] = { latitude = 50.231491, longitude = 2.117761, display_name = "Auxi-le-Chateau"},
    ["Friville-Escarbotin"] = { latitude = 50.086995, longitude = 1.545222, display_name = "Friville-Escarbotin"},
    ["Divion"] = { latitude = 50.469400, longitude = 2.507220, display_name = "Divion"},
    ["Le Touquet-Paris-Plage"] = { latitude = 50.521120, longitude = 1.590932, display_name = "Le Touquet-Paris-Plage"},
    ["Saint-Pol-sur-Ternoise"] = { latitude = 50.381211, longitude = 2.336105, display_name = "Saint-Pol-sur-Ternoise"},
    ["Lapugnoy"] = { latitude = 50.520400, longitude = 2.534200, display_name = "Lapugnoy"},
    ["Flixecourt"] = { latitude = 50.011710, longitude = 2.082582, display_name = "Flixecourt"},
    ["Rang-du-Fliers"] = { latitude = 50.416897, longitude = 1.643935, display_name = "Rang-du-Fliers"},
    ["Criel-sur-Mer"] = { latitude = 50.016093, longitude = 1.313944, display_name = "Criel-sur-Mer"},
    ["Eu"] = { latitude = 50.049170, longitude = 1.417574, display_name = "Eu"},
    ["Ailly-sur-Somme"] = { latitude = 49.928085, longitude = 2.198100, display_name = "Ailly-sur-Somme"},
    ["Labourse"] = { latitude = 50.497298, longitude = 2.679865, display_name = "Labourse"},
    ["Montreuil"] = { latitude = 50.463892, longitude = 1.763113, display_name = "Montreuil"},
    ["Marles-les-Mines"] = { latitude = 50.501949, longitude = 2.506041, display_name = "Marles-les-Mines"},
    ["Cauchy-a-la-Tour"] = { latitude = 50.503104, longitude = 2.452112, display_name = "Cauchy-a-la-Tour"},
    ["Bouvigny-Boyeffles"] = { latitude = 50.421195, longitude = 2.672681, display_name = "Bouvigny-Boyeffles"},
    ["Fruges"] = { latitude = 50.515039, longitude = 2.134929, display_name = "Fruges"},
    ["Sains-en-Gohelle"] = { latitude = 50.447328, longitude = 2.683388, display_name = "Sains-en-Gohelle"},
    ["Airaines"] = { latitude = 49.966270, longitude = 1.941064, display_name = "Airaines"},
    ["Wailly-Beaucamp"] = { latitude = 50.410866, longitude = 1.726233, display_name = "Wailly-Beaucamp"},
    ["Azincourt"] = { latitude = 50.463383, longitude = 2.128827, display_name = "Azincourt"},
    ["Tortefontaine"] = { latitude = 50.322728, longitude = 1.923292, display_name = "Tortefontaine"},
    ["Saulchoy"] = { latitude = 50.349999, longitude = 1.850295, display_name = "Saulchoy"},
    ["Douriez"] = { latitude = 50.332920, longitude = 1.877159, display_name = "Douriez"},
    ["Maintenay"] = { latitude = 50.366386, longitude = 1.813077, display_name = "Maintenay"},
    ["Roussent"] = { latitude = 50.368082, longitude = 1.777043, display_name = "Roussent"},
    ["Nempont-Saint-Firmin"] = { latitude = 50.355394, longitude = 1.732144, display_name = "Nempont-Saint-Firmin"},
    ["Gouy-Saint-Andre"] = { latitude = 50.373606, longitude = 1.900334, display_name = "Gouy-Saint-Andre"},
    ["Mouriez"] = { latitude = 50.340945, longitude = 1.947197, display_name = "Mouriez"},
    ["Bertangles"] = { latitude = 49.970940, longitude = 2.299291, display_name = "Bertangles"},
    ["Villers-Bocage"] = { latitude = 49.995675, longitude = 2.314198, display_name = "Villers-Bocage"},
    ["Molliens-au-Bois"] = { latitude = 49.989616, longitude = 2.385043, display_name = "Molliens-au-Bois"},
    ["Montigny-sur-l'Hallue"] = { latitude = 49.980230, longitude = 2.443205, display_name = "Montigny-sur-l'Hallue"},
    ["Behencourt"] = { latitude = 49.974472, longitude = 2.451315, display_name = "Behencourt"},
    ["Pont-Noyelles"] = { latitude = 49.939781, longitude = 2.440894, display_name = "Pont-Noyelles"},
    ["Querrieu"] = { latitude = 49.939113, longitude = 2.431445, display_name = "Querrieu"},
    ["Enocq"] = { latitude = 50.498729, longitude = 1.703481, display_name = "Enocq"},
    ["Lucheux"] = { latitude = 50.196945, longitude = 2.411534, display_name = "Lucheux"},
    ["Cambligneul"] = { latitude = 50.379859, longitude = 2.615773, display_name = "Cambligneul"},
    ["Lahoussoye"] = { latitude = 49.952856, longitude = 2.483167, display_name = "Lahoussoye"},
    ["Bonnay"] = { latitude = 49.934650, longitude = 2.512065, display_name = "Bonnay"},
    ["Picquigny"] = { latitude = 49.944326, longitude = 2.141807, display_name = "Picquigny"},
    ["Hallencourt"] = { latitude = 49.992824, longitude = 1.876855, display_name = "Hallencourt"},
    ["Lisbourg"] = { latitude = 50.507259, longitude = 2.216503, display_name = "Lisbourg"},
    ["Verchin"] = { latitude = 50.494047, longitude = 2.184971, display_name = "Verchin"},
    ["Lugy"] = { latitude = 50.523288, longitude = 2.173914, display_name = "Lugy"},
    ["Heuchin"] = { latitude = 50.474619, longitude = 2.269143, display_name = "Heuchin"},
    ["Bergueneuse"] = { latitude = 50.468606, longitude = 2.253501, display_name = "Bergueneuse"},
    ["Anvin"] = { latitude = 50.446435, longitude = 2.252243, display_name = "Anvin"},
    ["Monchy-Cayeux"] = { latitude = 50.436459, longitude = 2.278342, display_name = "Monchy-Cayeux"},
    ["Wavrans-sur-Ternoise"] = { latitude = 50.414381, longitude = 2.299602, display_name = "Wavrans-sur-Ternoise"},
    ["Hernicourt"] = { latitude = 50.407907, longitude = 2.304976, display_name = "Hernicourt"},
    ["Gauchin-Verloingt"] = { latitude = 50.395392, longitude = 2.312410, display_name = "Gauchin-Verloingt"},
    ["Fiefs"] = { latitude = 50.503443, longitude = 2.328885, display_name = "Fiefs"},
    ["Tangry"] = { latitude = 50.465104, longitude = 2.354806, display_name = "Tangry"},
    ["Valhuon"] = { latitude = 50.435419, longitude = 2.375445, display_name = "Valhuon"},
    ["Brias"] = { latitude = 50.409802, longitude = 2.378303, display_name = "Brias"},
    ["Pernes"] = { latitude = 50.484100, longitude = 2.410092, display_name = "Pernes"},
    ["Fontaine-les-Boulans"] = { latitude = 50.498360, longitude = 2.275293, display_name = "Fontaine-les-Boulans"},
    ["Naours"] = { latitude = 50.034144, longitude = 2.274822, display_name = "Naours"},
    ["Ramecourt"] = { latitude = 50.370249, longitude = 2.314578, display_name = "Ramecourt"},
    ["Le Parcq"] = { latitude = 50.379561, longitude = 2.099157, display_name = "Le Parcq"},
    ["Huby-Saint-Leu"] = { latitude = 50.381317, longitude = 2.037182, display_name = "Huby-Saint-Leu"},
    ["Marconne"] = { latitude = 50.370368, longitude = 2.045287, display_name = "Marconne"},
    ["Regnauville"] = { latitude = 50.313621, longitude = 2.013962, display_name = "Regnauville"},
    ["Le Boisle"] = { latitude = 50.271777, longitude = 1.985295, display_name = "Le Boisle"},
    ["Froyelles"] = { latitude = 50.226877, longitude = 1.929308, display_name = "Froyelles"},
    ["Drucat"] = { latitude = 50.141850, longitude = 1.868445, display_name = "Drucat"},
    ["Grand-Laviers"] = { latitude = 50.128684, longitude = 1.785705, display_name = "Grand-Laviers"},
    ["Ville-le-Marclet"] = { latitude = 50.022943, longitude = 2.088392, display_name = "Ville-le-Marclet"},
    ["Saint-Leger-les-Domart"] = { latitude = 50.053579, longitude = 2.139729, display_name = "Saint-Leger-les-Domart"},
    ["Domart-en-Ponthieu"] = { latitude = 50.074166, longitude = 2.126317, display_name = "Domart-en-Ponthieu"},
    ["La Calotterie"] = { latitude = 50.475205, longitude = 1.726325, display_name = "La Calotterie"},
    ["Dernancourt"] = { latitude = 49.973252, longitude = 2.631678, display_name = "Dernancourt"},
    ["Buire-sur-l'Ancre"] = { latitude = 49.965182, longitude = 2.592539, display_name = "Buire-sur-l'Ancre"},
    ["Ribemont-sur-Ancre"] = { latitude = 49.960156, longitude = 2.565479, display_name = "Ribemont-sur-Ancre"},
    ["Mericourt-l'Abbe"] = { latitude = 49.952297, longitude = 2.563810, display_name = "Mericourt-l'Abbe"},
    ["Ancourt"] = { latitude = 49.910934, longitude = 1.179063, display_name = "Ancourt"},
    ["Arrest"] = { latitude = 50.126994, longitude = 1.616619, display_name = "Arrest"},
    ["Rubempre"] = { latitude = 50.018360, longitude = 2.384620, display_name = "Rubempre"},
    ["Estreboeuf"] = { latitude = 50.156111, longitude = 1.615395, display_name = "Estreboeuf"},
    ["Boismont"] = { latitude = 50.153208, longitude = 1.684135, display_name = "Boismont"},
    ["Crecy-en-Ponthieu"] = { latitude = 50.252146, longitude = 1.884066, display_name = "Crecy-en-Ponthieu"},
    ["Nouvion"] = { latitude = 50.212477, longitude = 1.777915, display_name = "Nouvion"},
    ["Saint-Martin-en-Campagne"] = { latitude = 49.956823, longitude = 1.221892, display_name = "Saint-Martin-en-Campagne"},
    ["Grandcourt"] = { latitude = 49.913749, longitude = 1.491097, display_name = "Grandcourt"},
    ["Le Treport"] = { latitude = 50.059110, longitude = 1.382766, display_name = "Le Treport"},
    ["Le Crotoy"] = { latitude = 50.216569, longitude = 1.624047, display_name = "Le Crotoy"},
    ["Estree"] = { latitude = 50.499322, longitude = 1.791475, display_name = "Estree"},
    ["Montcavrel"] = { latitude = 50.514675, longitude = 1.810636, display_name = "Montcavrel"},
    ["Marles-sur-Canche"] = { latitude = 50.458151, longitude = 1.826960, display_name = "Marles-sur-Canche"},
    ["Saint-Josse"] = { latitude = 50.467477, longitude = 1.663984, display_name = "Saint-Josse"},
    ["Saint-Aubin"] = { latitude = 50.457369, longitude = 1.665185, display_name = "Saint-Aubin"},
    ["Sorrus"] = { latitude = 50.456844, longitude = 1.715139, display_name = "Sorrus"},
    ["Brimeux"] = { latitude = 50.445700, longitude = 1.834628, display_name = "Brimeux"},
    ["Campigneulles-les-Petites"] = { latitude = 50.444800, longitude = 1.733805, display_name = "Campigneulles-les-Petites"},
    ["Bouzincourt"] = { latitude = 50.026323, longitude = 2.610171, display_name = "Bouzincourt"},
    ["Vacqueriette-Erquieres"] = { latitude = 50.322689, longitude = 2.078418, display_name = "Vacqueriette-Erquieres"},
    ["Miannay"] = { latitude = 50.097549, longitude = 1.717694, display_name = "Miannay"},
    ["Valines"] = { latitude = 50.076011, longitude = 1.621392, display_name = "Valines"},
    ["Epagne-Epagnette"] = { latitude = 50.073673, longitude = 1.870325, display_name = "Epagne-Epagnette"},
    ["Eaucourt-sur-Somme"] = { latitude = 50.064496, longitude = 1.884941, display_name = "Eaucourt-sur-Somme"},
    ["Ailly-le-Haut-Clocher"] = { latitude = 50.076495, longitude = 1.992357, display_name = "Ailly-le-Haut-Clocher"},
    ["Pont-Remy"] = { latitude = 50.055774, longitude = 1.901735, display_name = "Pont-Remy"},
    ["Canaples"] = { latitude = 50.056802, longitude = 2.218586, display_name = "Canaples"},
    ["Havernas"] = { latitude = 50.036637, longitude = 2.233241, display_name = "Havernas"},
    ["Halloy-les-Pernois"] = { latitude = 50.050258, longitude = 2.201617, display_name = "Halloy-les-Pernois"},
    ["Berteaucourt-les-Dames"] = { latitude = 50.045899, longitude = 2.153166, display_name = "Berteaucourt-les-Dames"},
    ["Woignarue"] = { latitude = 50.109473, longitude = 1.494677, display_name = "Woignarue"},
    ["Ponthoile"] = { latitude = 50.215635, longitude = 1.711600, display_name = "Ponthoile"},
    ["Noyelles-sur-Mer"] = { latitude = 50.182840, longitude = 1.707460, display_name = "Noyelles-sur-Mer"},
    ["Cambron"] = { latitude = 50.111747, longitude = 1.769661, display_name = "Cambron"},
    ["Morlancourt"] = { latitude = 49.950469, longitude = 2.628832, display_name = "Morlancourt"},
    ["Etinehem-Mericourt"] = { latitude = 49.927788, longitude = 2.688331, display_name = "Etinehem-Mericourt"},
    ["Franvillers"] = { latitude = 49.966473, longitude = 2.507860, display_name = "Franvillers"},
    ["Ville-sur-Ancre"] = { latitude = 49.961707, longitude = 2.610144, display_name = "Ville-sur-Ancre"},
    ["Moyenneville"] = { latitude = 50.070794, longitude = 1.749657, display_name = "Moyenneville"},
    ["Yonval"] = { latitude = 50.089952, longitude = 1.789253, display_name = "Yonval"},
    ["Franleu"] = { latitude = 50.098909, longitude = 1.639044, display_name = "Franleu"},
    ["Acheux-en-Vimeu"] = { latitude = 50.064489, longitude = 1.675123, display_name = "Acheux-en-Vimeu"},
    ["Beaucourt-sur-l'Hallue"] = { latitude = 49.986218, longitude = 2.444484, display_name = "Beaucourt-sur-l'Hallue"},
    ["Varennes"] = { latitude = 50.049134, longitude = 2.534710, display_name = "Varennes"},
    ["Senlis-le-Sec"] = { latitude = 50.024504, longitude = 2.578660, display_name = "Senlis-le-Sec"},
    ["Bertrancourt"] = { latitude = 50.093358, longitude = 2.555917, display_name = "Bertrancourt"},
    ["Bus-les-Artois"] = { latitude = 50.104208, longitude = 2.541676, display_name = "Bus-les-Artois"},
    ["Authie"] = { latitude = 50.120757, longitude = 2.489984, display_name = "Authie"},
    ["Marieux"] = { latitude = 50.106205, longitude = 2.441478, display_name = "Marieux"},
    ["Louvencourt"] = { latitude = 50.094201, longitude = 2.499360, display_name = "Louvencourt"},
    ["Vauchelles-les-Authie"] = { latitude = 50.095571, longitude = 2.473944, display_name = "Vauchelles-les-Authie"},
    ["Raincheval"] = { latitude = 50.073792, longitude = 2.437531, display_name = "Raincheval"},
    ["Arqueves"] = { latitude = 50.071686, longitude = 2.469114, display_name = "Arqueves"},
    ["Acheux-en-Amienois"] = { latitude = 50.072899, longitude = 2.530620, display_name = "Acheux-en-Amienois"},
    ["Puchevillers"] = { latitude = 50.054352, longitude = 2.409105, display_name = "Puchevillers"},
    ["Beauquesne"] = { latitude = 50.084686, longitude = 2.394203, display_name = "Beauquesne"},
    ["Terramesnil"] = { latitude = 50.106253, longitude = 2.379393, display_name = "Terramesnil"},
    ["Humbercourt"] = { latitude = 50.210223, longitude = 2.454904, display_name = "Humbercourt"},
    ["Brevillers"] = { latitude = 50.215587, longitude = 2.376572, display_name = "Brevillers"},
    ["Grouches-Luchuel"] = { latitude = 50.179760, longitude = 2.381204, display_name = "Grouches-Luchuel"},
    ["Neuvillette"] = { latitude = 50.209127, longitude = 2.319330, display_name = "Neuvillette"},
    ["Bouquemaison"] = { latitude = 50.211581, longitude = 2.335495, display_name = "Bouquemaison"},
    ["Le Souich"] = { latitude = 50.222057, longitude = 2.367014, display_name = "Le Souich"},
    ["Allonville"] = { latitude = 49.939767, longitude = 2.364224, display_name = "Allonville"},
    ["Meaulte"] = { latitude = 49.983122, longitude = 2.664155, display_name = "Meaulte"},
    ["Saint-Sauveur"] = { latitude = 49.938093, longitude = 2.212011, display_name = "Saint-Sauveur"},
    ["Dreuil-les-Amiens"] = { latitude = 49.914115, longitude = 2.233500, display_name = "Dreuil-les-Amiens"},
    ["Quesnoy-sur-Airaines"] = { latitude = 49.956626, longitude = 1.990594, display_name = "Quesnoy-sur-Airaines"},
    ["Saint-Vaast-en-Chaussee"] = { latitude = 49.969551, longitude = 2.204214, display_name = "Saint-Vaast-en-Chaussee"},
    ["Vaux-en-Amienois"] = { latitude = 49.962606, longitude = 2.247945, display_name = "Vaux-en-Amienois"},
    ["Poulainville"] = { latitude = 49.947348, longitude = 2.311602, display_name = "Poulainville"},
    ["Coisy"] = { latitude = 49.959888, longitude = 2.326859, display_name = "Coisy"},
    ["Cardonnette"] = { latitude = 49.952245, longitude = 2.359868, display_name = "Cardonnette"},
    ["Rainneville"] = { latitude = 49.972235, longitude = 2.355317, display_name = "Rainneville"},
    ["Contay"] = { latitude = 50.005329, longitude = 2.478641, display_name = "Contay"},
    ["Warloy-Baillon"] = { latitude = 50.010790, longitude = 2.522951, display_name = "Warloy-Baillon"},
    ["Baizieux"] = { latitude = 49.992474, longitude = 2.518960, display_name = "Baizieux"},
    ["Henencourt"] = { latitude = 50.002171, longitude = 2.563231, display_name = "Henencourt"},
    ["Millencourt"] = { latitude = 50.000761, longitude = 2.587326, display_name = "Millencourt"},
    ["Mailly-Maillet"] = { latitude = 50.079767, longitude = 2.604249, display_name = "Mailly-Maillet"},
    ["Vaire-sous-Corbie"] = { latitude = 49.914717, longitude = 2.546964, display_name = "Vaire-sous-Corbie"},
    ["Heilly"] = { latitude = 49.953015, longitude = 2.536172, display_name = "Heilly"},
    ["Sailly-le-Sec"] = { latitude = 49.920759, longitude = 2.582747, display_name = "Sailly-le-Sec"},
    ["Sailly-Laurette"] = { latitude = 49.912221, longitude = 2.606709, display_name = "Sailly-Laurette"},
    ["Oisemont"] = { latitude = 49.956082, longitude = 1.764936, display_name = "Oisemont"},
    ["Allery"] = { latitude = 49.963050, longitude = 1.897986, display_name = "Allery"},
    ["Colline-Beaumont"] = { latitude = 50.339831, longitude = 1.680815, display_name = "Colline-Beaumont"},
    ["Fort-Mahon-Plage"] = { latitude = 50.341429, longitude = 1.568313, display_name = "Fort-Mahon-Plage"},
    ["Talmas"] = { latitude = 50.029580, longitude = 2.325057, display_name = "Talmas"},
    ["Neuilly-l'Hopital"] = { latitude = 50.170034, longitude = 1.878572, display_name = "Neuilly-l'Hopital"},
    ["Riencourt"] = { latitude = 49.922113, longitude = 2.050381, display_name = "Riencourt"},
    ["Cavillon"] = { latitude = 49.921717, longitude = 2.083588, display_name = "Cavillon"},
    ["Belloy-sur-Somme"] = { latitude = 49.966846, longitude = 2.135439, display_name = "Belloy-sur-Somme"},
    ["Warlus"] = { latitude = 50.275515, longitude = 2.668715, display_name = "Warlus"},
    ["Willencourt"] = { latitude = 50.238886, longitude = 2.091478, display_name = "Willencourt"},
    ["Acq"] = { latitude = 50.348457, longitude = 2.655962, display_name = "Acq"},
    ["Agnieres"] = { latitude = 50.355059, longitude = 2.607809, display_name = "Agnieres"},
    ["Aix-en-Issart"] = { latitude = 50.475171, longitude = 1.858967, display_name = "Aix-en-Issart"},
    ["Ambricourt"] = { latitude = 50.467973, longitude = 2.175626, display_name = "Ambricourt"},
    ["Ambrines"] = { latitude = 50.310247, longitude = 2.468694, display_name = "Ambrines"},
    ["Aubigny-en-Artois"] = { latitude = 50.351595, longitude = 2.589628, display_name = "Aubigny-en-Artois"},
    ["Aubin-Saint-Vaast"] = { latitude = 50.397660, longitude = 1.974479, display_name = "Aubin-Saint-Vaast"},
    ["Aubrometz"] = { latitude = 50.304194, longitude = 2.177230, display_name = "Aubrometz"},
    ["Bailleul-aux-Cornailles"] = { latitude = 50.371330, longitude = 2.443230, display_name = "Bailleul-aux-Cornailles"},
    ["Bajus"] = { latitude = 50.421067, longitude = 2.481147, display_name = "Bajus"},
    ["Le Titre"] = { latitude = 50.188785, longitude = 1.798457, display_name = "Le Titre"},
    ["Beaumetz-les-Loges"] = { latitude = 50.243641, longitude = 2.655521, display_name = "Beaumetz-les-Loges"},
    ["Berneville"] = { latitude = 50.265960, longitude = 2.670953, display_name = "Berneville"},
    ["Bethonsart"] = { latitude = 50.375325, longitude = 2.551191, display_name = "Bethonsart"},
    ["Beugin"] = { latitude = 50.442082, longitude = 2.514646, display_name = "Beugin"},
    ["Bienvillers-au-Bois"] = { latitude = 50.174926, longitude = 2.620271, display_name = "Bienvillers-au-Bois"},
    ["Boisjean"] = { latitude = 50.407192, longitude = 1.767314, display_name = "Boisjean"},
    ["Bouin-Plumoison"] = { latitude = 50.381981, longitude = 1.988686, display_name = "Bouin-Plumoison"},
    ["Buire-au-Bois"] = { latitude = 50.262317, longitude = 2.152020, display_name = "Buire-au-Bois"},
    ["Buire-le-Sec"] = { latitude = 50.381962, longitude = 1.832624, display_name = "Buire-le-Sec"},
    ["Crequy"] = { latitude = 50.494717, longitude = 2.046920, display_name = "Crequy"},
    ["Carency"] = { latitude = 50.378876, longitude = 2.703589, display_name = "Carency"},
    ["Campagne-les-Hesdin"] = { latitude = 50.398470, longitude = 1.878694, display_name = "Campagne-les-Hesdin"},
    ["Camblain-Chatelain"] = { latitude = 50.482288, longitude = 2.463744, display_name = "Camblain-Chatelain"},
    ["Camblain-l'Abbe"] = { latitude = 50.371375, longitude = 2.632892, display_name = "Camblain-l'Abbe"},
    ["Canlers"] = { latitude = 50.482216, longitude = 2.142202, display_name = "Canlers"},
    ["Capelle-les-Hesdin"] = { latitude = 50.341267, longitude = 1.998372, display_name = "Capelle-les-Hesdin"},
    ["Caucourt"] = { latitude = 50.399633, longitude = 2.570931, display_name = "Caucourt"},
    ["Chelers"] = { latitude = 50.375943, longitude = 2.484191, display_name = "Chelers"},
    ["Coullemont"] = { latitude = 50.214546, longitude = 2.471392, display_name = "Coullemont"},
    ["Couturelle"] = { latitude = 50.207486, longitude = 2.499051, display_name = "Couturelle"},
    ["Crepy"] = { latitude = 50.472526, longitude = 2.201140, display_name = "Crepy"},
    ["Drouvin-le-Marais"] = { latitude = 50.493415, longitude = 2.627614, display_name = "Drouvin-le-Marais"},
    ["Embry"] = { latitude = 50.492782, longitude = 1.967237, display_name = "Embry"},
    ["Estree-Cauchy"] = { latitude = 50.398375, longitude = 2.608935, display_name = "Estree-Cauchy"},
    ["Etrun"] = { latitude = 50.314529, longitude = 2.700925, display_name = "Etrun"},
    ["Wamin"] = { latitude = 50.413414, longitude = 2.058777, display_name = "Wamin"},
    ["Fressin"] = { latitude = 50.446563, longitude = 2.055354, display_name = "Fressin"},
    ["Ferfay"] = { latitude = 50.519583, longitude = 2.422769, display_name = "Ferfay"},
    ["Foncquevillers"] = { latitude = 50.148739, longitude = 2.631933, display_name = "Foncquevillers"},
    ["Fouquereuil"] = { latitude = 50.518441, longitude = 2.600400, display_name = "Fouquereuil"},
    ["Fouquieres-les-Bethune"] = { latitude = 50.514124, longitude = 2.611136, display_name = "Fouquieres-les-Bethune"},
    ["Frevillers"] = { latitude = 50.397655, longitude = 2.518940, display_name = "Frevillers"},
    ["Frevin-Capelle"] = { latitude = 50.350238, longitude = 2.638829, display_name = "Frevin-Capelle"},
    ["Gauchin-Legal"] = { latitude = 50.415413, longitude = 2.580783, display_name = "Gauchin-Legal"},
    ["Gosnay"] = { latitude = 50.508288, longitude = 2.585141, display_name = "Gosnay"},
    ["Gouy-Servins"] = { latitude = 50.402467, longitude = 2.649541, display_name = "Gouy-Servins"},
    ["Guisy"] = { latitude = 50.389043, longitude = 2.001358, display_name = "Guisy"},
    ["Hesmond"] = { latitude = 50.451619, longitude = 1.950778, display_name = "Hesmond"},
    ["Habarcq"] = { latitude = 50.305638, longitude = 2.611375, display_name = "Habarcq"},
    ["Hannescamps"] = { latitude = 50.166406, longitude = 2.638869, display_name = "Hannescamps"},
    ["Haute-Avesnes"] = { latitude = 50.328849, longitude = 2.639675, display_name = "Haute-Avesnes"},
    ["Hebuterne"] = { latitude = 50.125337, longitude = 2.636355, display_name = "Hebuterne"},
    ["Hermaville"] = { latitude = 50.323366, longitude = 2.583539, display_name = "Hermaville"},
    ["Hermin"] = { latitude = 50.418676, longitude = 2.558363, display_name = "Hermin"},
    ["Hesdigneul-les-Bethune"] = { latitude = 50.501317, longitude = 2.593630, display_name = "Hesdigneul-les-Bethune"},
    ["Houchin"] = { latitude = 50.482338, longitude = 2.619552, display_name = "Houchin"},
    ["Izel-les-Hameau"] = { latitude = 50.314623, longitude = 2.531383, display_name = "Izel-les-Hameau"},
    ["La Comte"] = { latitude = 50.427123, longitude = 2.499043, display_name = "La Comte"},
    ["La Loge"] = { latitude = 50.410128, longitude = 2.031768, display_name = "La Loge"},
    ["Labeuvriere"] = { latitude = 50.520376, longitude = 2.563110, display_name = "Labeuvriere"},
    ["Lebiez"] = { latitude = 50.469253, longitude = 1.982758, display_name = "Lebiez"},
    ["Lespinoy"] = { latitude = 50.426840, longitude = 1.878623, display_name = "Lespinoy"},
    ["Loison-sur-Crequoise"] = { latitude = 50.437403, longitude = 1.925334, display_name = "Loison-sur-Crequoise"},
    ["Lozinghem"] = { latitude = 50.517819, longitude = 2.499672, display_name = "Lozinghem"},
    ["Maresquel-Ecquemicourt"] = { latitude = 50.408102, longitude = 1.933883, display_name = "Maresquel-Ecquemicourt"},
    ["Magnicourt-en-Comte"] = { latitude = 50.403634, longitude = 2.486591, display_name = "Magnicourt-en-Comte"},
    ["Magnicourt-sur-Canche"] = { latitude = 50.303018, longitude = 2.409031, display_name = "Magnicourt-sur-Canche"},
    ["Maisnil-les-Ruitz"] = { latitude = 50.453376, longitude = 2.585746, display_name = "Maisnil-les-Ruitz"},
    ["Maizieres"] = { latitude = 50.323792, longitude = 2.447266, display_name = "Maizieres"},
    ["Manin"] = { latitude = 50.297024, longitude = 2.511387, display_name = "Manin"},
    ["Mont-Saint-Eloi"] = { latitude = 50.351429, longitude = 2.692501, display_name = "Mont-Saint-Eloi"},
    ["Marconnelle"] = { latitude = 50.373859, longitude = 2.018381, display_name = "Marconnelle"},
    ["Marant"] = { latitude = 50.461734, longitude = 1.835891, display_name = "Marant"},
    ["Mondicourt"] = { latitude = 50.173591, longitude = 2.462474, display_name = "Mondicourt"},
    ["Marenla"] = { latitude = 50.446101, longitude = 1.868635, display_name = "Marenla"},
    ["Mingoval"] = { latitude = 50.373024, longitude = 2.574754, display_name = "Mingoval"},
    ["Puisieux"] = { latitude = 50.116703, longitude = 2.693897, display_name = "Puisieux"},
    ["Ourton"] = { latitude = 50.455251, longitude = 2.477867, display_name = "Ourton"},
    ["Pas-en-Artois"] = { latitude = 50.154193, longitude = 2.490704, display_name = "Pas-en-Artois"},
    ["Penin"] = { latitude = 50.328137, longitude = 2.484400, display_name = "Penin"},
    ["Pommera"] = { latitude = 50.172195, longitude = 2.442457, display_name = "Pommera"},
    ["Pommier"] = { latitude = 50.184254, longitude = 2.599698, display_name = "Pommier"},
    ["Royon"] = { latitude = 50.472399, longitude = 1.992301, display_name = "Royon"},
    ["Villers-Chatel"] = { latitude = 50.376786, longitude = 2.585568, display_name = "Villers-Chatel"},
    ["Noeux-les-Auxi"] = { latitude = 50.235132, longitude = 2.174807, display_name = "Noeux-les-Auxi"},
    ["Rebreuve-Ranchicourt"] = { latitude = 50.437012, longitude = 2.558185, display_name = "Rebreuve-Ranchicourt"},
    ["Rimboval"] = { latitude = 50.508085, longitude = 1.985654, display_name = "Rimboval"},
    ["Ruisseauville"] = { latitude = 50.480788, longitude = 2.123324, display_name = "Ruisseauville"},
    ["Ruitz"] = { latitude = 50.466503, longitude = 2.588501, display_name = "Ruitz"},
    ["Sailly-Labourse"] = { latitude = 50.500806, longitude = 2.695613, display_name = "Sailly-Labourse"},
    ["Verquigneul"] = { latitude = 50.502031, longitude = 2.665051, display_name = "Verquigneul"},
    ["Vaudricourt"] = { latitude = 50.499351, longitude = 2.627253, display_name = "Vaudricourt"},
    ["Avesnes-en-Val"] = { latitude = 49.920193, longitude = 1.398231, display_name = "Avesnes-en-Val"},
    ["Sains-les-Fressin"] = { latitude = 50.466471, longitude = 2.040777, display_name = "Sains-les-Fressin"},
    ["Saint-Amand"] = { latitude = 50.163989, longitude = 2.557766, display_name = "Saint-Amand"},
    ["Saint-Denoeux"] = { latitude = 50.471621, longitude = 1.905215, display_name = "Saint-Denoeux"},
    ["Savy-Berlette"] = { latitude = 50.353870, longitude = 2.570194, display_name = "Savy-Berlette"},
    ["Sempy"] = { latitude = 50.492124, longitude = 1.876173, display_name = "Sempy"},
    ["Servins"] = { latitude = 50.409664, longitude = 2.642820, display_name = "Servins"},
    ["Sombrin"] = { latitude = 50.240107, longitude = 2.498782, display_name = "Sombrin"},
    ["Saulty"] = { latitude = 50.216647, longitude = 2.534072, display_name = "Saulty"},
    ["Souastre"] = { latitude = 50.152737, longitude = 2.564129, display_name = "Souastre"},
    ["Tilloy-les-Hermaville"] = { latitude = 50.327542, longitude = 2.555282, display_name = "Tilloy-les-Hermaville"},
    ["Tincques"] = { latitude = 50.358091, longitude = 2.493038, display_name = "Tincques"},
    ["Villers-au-Bois"] = { latitude = 50.373476, longitude = 2.671491, display_name = "Villers-au-Bois"},
    ["Villers-Brulin"] = { latitude = 50.368300, longitude = 2.539641, display_name = "Villers-Brulin"},
    ["Barly"] = { latitude = 50.251285, longitude = 2.547855, display_name = "Barly"},
    ["Beutin"] = { latitude = 50.491368, longitude = 1.723943, display_name = "Beutin"},
    ["Bouret-sur-Canche"] = { latitude = 50.267561, longitude = 2.320685, display_name = "Bouret-sur-Canche"},
    ["Fontaine-l'Etalon"] = { latitude = 50.304808, longitude = 2.062972, display_name = "Fontaine-l'Etalon"},
    ["Fortel-en-Artois"] = { latitude = 50.258340, longitude = 2.224950, display_name = "Fortel-en-Artois"},
    ["Humbercamps"] = { latitude = 50.184811, longitude = 2.574128, display_name = "Humbercamps"},
    ["Liencourt"] = { latitude = 50.271114, longitude = 2.454495, display_name = "Liencourt"},
    ["Quoeux-Haut-Mainil"] = { latitude = 50.299738, longitude = 2.109808, display_name = "Quoeux-Haut-Mainil"},
    ["Friaucourt"] = { latitude = 50.088504, longitude = 1.477432, display_name = "Friaucourt"},
    ["Allenay"] = { latitude = 50.090232, longitude = 1.494030, display_name = "Allenay"},
    ["Brutelles"] = { latitude = 50.140851, longitude = 1.522464, display_name = "Brutelles"},
    ["Vaudricourt"] = { latitude = 50.119989, longitude = 1.549379, display_name = "Vaudricourt"},
    ["Saint-Blimont"] = { latitude = 50.120352, longitude = 1.567875, display_name = "Saint-Blimont"},
    ["Nibas"] = { latitude = 50.099336, longitude = 1.588243, display_name = "Nibas"},
    ["Lancheres"] = { latitude = 50.156881, longitude = 1.550766, display_name = "Lancheres"},
    ["Pende"] = { latitude = 50.160419, longitude = 1.586523, display_name = "Pende"},
    ["Saint-Quentin-Lamotte-Croix-au-Bailly"] = { latitude = 50.073532, longitude = 1.452558, display_name = "Saint-Quentin-Lamotte-Croix-au-Bailly"},
    ["Mons-Boubert"] = { latitude = 50.128830, longitude = 1.662704, display_name = "Mons-Boubert"},
    ["Saigneville"] = { latitude = 50.136906, longitude = 1.712312, display_name = "Saigneville"},
    ["Ponts-et-Marais"] = { latitude = 50.040459, longitude = 1.442832, display_name = "Ponts-et-Marais"},
    ["Woincourt"] = { latitude = 50.064974, longitude = 1.537203, display_name = "Woincourt"},
    ["Dargnies"] = { latitude = 50.042273, longitude = 1.526040, display_name = "Dargnies"},
    ["Beauchamps"] = { latitude = 50.017920, longitude = 1.508522, display_name = "Beauchamps"},
    ["Embreville"] = { latitude = 50.029763, longitude = 1.542338, display_name = "Embreville"},
    ["Mareuil-Caubert"] = { latitude = 50.068518, longitude = 1.829495, display_name = "Mareuil-Caubert"},
    ["Huchenneville"] = { latitude = 50.050529, longitude = 1.798512, display_name = "Huchenneville"},
    ["Aigneville"] = { latitude = 50.033267, longitude = 1.618383, display_name = "Aigneville"},
    ["Bouillancourt-en-Sery"] = { latitude = 49.962078, longitude = 1.629067, display_name = "Bouillancourt-en-Sery"},
    ["Port-le-Grand"] = { latitude = 50.151333, longitude = 1.749128, display_name = "Port-le-Grand"},
    ["Favieres"] = { latitude = 50.237656, longitude = 1.663845, display_name = "Favieres"},
    ["Villers-sur-Authie"] = { latitude = 50.317121, longitude = 1.690718, display_name = "Villers-sur-Authie"},
    ["Forest-Montiers"] = { latitude = 50.245116, longitude = 1.741668, display_name = "Forest-Montiers"},
    ["Buigny-Saint-Maclou"] = { latitude = 50.155218, longitude = 1.813248, display_name = "Buigny-Saint-Maclou"},
    ["Vismes"] = { latitude = 50.011591, longitude = 1.672669, display_name = "Vismes"},
    ["Toeufles"] = { latitude = 50.066957, longitude = 1.715689, display_name = "Toeufles"},
    ["Behen"] = { latitude = 50.056974, longitude = 1.755319, display_name = "Behen"},
    ["Bealencourt"] = { latitude = 50.435337, longitude = 2.121538, display_name = "Bealencourt"},
    ["Fontaine-sur-Somme"] = { latitude = 50.029171, longitude = 1.941005, display_name = "Fontaine-sur-Somme"},
    ["Longpre-les-Corps-Saints"] = { latitude = 50.013403, longitude = 1.991743, display_name = "Longpre-les-Corps-Saints"},
    ["Bettencourt-Riviere"] = { latitude = 49.996197, longitude = 1.976653, display_name = "Bettencourt-Riviere"},
    ["Conde-Folie"] = { latitude = 50.010437, longitude = 2.021068, display_name = "Conde-Folie"},
    ["Hangest-sur-Somme"] = { latitude = 49.980232, longitude = 2.065232, display_name = "Hangest-sur-Somme"},
    ["L'Etoile"] = { latitude = 50.020280, longitude = 2.045751, display_name = "L'Etoile"},
    ["Huppy"] = { latitude = 50.024552, longitude = 1.764234, display_name = "Huppy"},
    ["Bray-les-Mareuil"] = { latitude = 50.054739, longitude = 1.855303, display_name = "Bray-les-Mareuil"},
    ["Erondelle"] = { latitude = 50.053317, longitude = 1.884265, display_name = "Erondelle"},
    ["Limeux"] = { latitude = 50.019278, longitude = 1.815791, display_name = "Limeux"},
    ["Frucourt"] = { latitude = 49.995513, longitude = 1.807079, display_name = "Frucourt"},
    ["Crouy-Saint-Pierre"] = { latitude = 49.969409, longitude = 2.086307, display_name = "Crouy-Saint-Pierre"},
    ["Frechencourt"] = { latitude = 49.965039, longitude = 2.441864, display_name = "Frechencourt"},
    ["Calonne-Ricouart"] = { latitude = 50.489409, longitude = 2.484482, display_name = "Calonne-Ricouart"},
    ["Stella-Plage"] = { latitude = 50.479940, longitude = 1.577138, display_name = "Stella-Plage"},
    ["Recques-sur-Course"] = { latitude = 50.521554, longitude = 1.785264, display_name = "Recques-sur-Course"},
    ["Fienvillers"] = { latitude = 50.117793, longitude = 2.228164, display_name = "Fienvillers"},
    ["Houdain"] = { latitude = 50.453813, longitude = 2.536784, display_name = "Houdain"},
    ["Pernois"] = { latitude = 50.051806, longitude = 2.181309, display_name = "Pernois"},
    ["Caours"] = { latitude = 50.129588, longitude = 1.881779, display_name = "Caours"},
    ["Saint-Riquier"] = { latitude = 50.135782, longitude = 1.946800, display_name = "Saint-Riquier"},
    ["Neufmoulin"] = { latitude = 50.128545, longitude = 1.908033, display_name = "Neufmoulin"},
    ["Millencourt-en-Ponthieu"] = { latitude = 50.151836, longitude = 1.901300, display_name = "Millencourt-en-Ponthieu"},
    ["Hautvillers-Ouville"] = { latitude = 50.172747, longitude = 1.812496, display_name = "Hautvillers-Ouville"},
    ["Forest-l'Abbaye"] = { latitude = 50.203667, longitude = 1.822246, display_name = "Forest-l'Abbaye"},
    ["Marcheville"] = { latitude = 50.221449, longitude = 1.902782, display_name = "Marcheville"},
    ["Bernay-en-Ponthieu"] = { latitude = 50.269179, longitude = 1.744496, display_name = "Bernay-en-Ponthieu"},
    ["Arry"] = { latitude = 50.278391, longitude = 1.720847, display_name = "Arry"},
    ["Regniere-Ecluse"] = { latitude = 50.279957, longitude = 1.769164, display_name = "Regniere-Ecluse"},
    ["Vron"] = { latitude = 50.314832, longitude = 1.755027, display_name = "Vron"},
    ["Nampont"] = { latitude = 50.347932, longitude = 1.745303, display_name = "Nampont"},
    ["Vironchaux"] = { latitude = 50.285888, longitude = 1.828922, display_name = "Vironchaux"},
    ["Vercourt"] = { latitude = 50.300467, longitude = 1.700853, display_name = "Vercourt"},
    ["Machiel"] = { latitude = 50.269881, longitude = 1.822936, display_name = "Machiel"},
    ["Machy"] = { latitude = 50.271234, longitude = 1.799760, display_name = "Machy"},
    ["Sachin"] = { latitude = 50.486649, longitude = 2.375360, display_name = "Sachin"},
    ["Boyaval"] = { latitude = 50.474299, longitude = 2.304088, display_name = "Boyaval"},
    ["Tollent"] = { latitude = 50.277620, longitude = 2.013841, display_name = "Tollent"},
    ["Siracourt"] = { latitude = 50.372126, longitude = 2.270515, display_name = "Siracourt"},
    ["Wanquetin"] = { latitude = 50.276266, longitude = 2.612965, display_name = "Wanquetin"},
    ["Simencourt"] = { latitude = 50.257176, longitude = 2.643434, display_name = "Simencourt"},
    ["Wail"] = { latitude = 50.343675, longitude = 2.126523, display_name = "Wail"},
    ["Villers-l'Hopital"] = { latitude = 50.228001, longitude = 2.213403, display_name = "Villers-l'Hopital"},
    ["Beaucourt-sur-l'Ancre"] = { latitude = 50.079855, longitude = 2.687121, display_name = "Beaucourt-sur-l'Ancre"},
    ["Basseux"] = { latitude = 50.225691, longitude = 2.644203, display_name = "Basseux"},
    ["Bailleulval"] = { latitude = 50.221240, longitude = 2.634088, display_name = "Bailleulval"},
    ["Riviere"] = { latitude = 50.233728, longitude = 2.685614, display_name = "Riviere"},
    ["Ransart"] = { latitude = 50.209100, longitude = 2.687046, display_name = "Ransart"},
    ["Bailleulmont"] = { latitude = 50.215951, longitude = 2.612239, display_name = "Bailleulmont"},
    ["La Cauchie"] = { latitude = 50.201207, longitude = 2.582186, display_name = "La Cauchie"},
    ["Monchy-au-Bois"] = { latitude = 50.179778, longitude = 2.657847, display_name = "Monchy-au-Bois"},
    ["Gapennes"] = { latitude = 50.182745, longitude = 1.952581, display_name = "Gapennes"},
    ["Buigny-l'Abbe"] = { latitude = 50.097853, longitude = 1.938007, display_name = "Buigny-l'Abbe"},
    ["Bresle"] = { latitude = 49.983572, longitude = 2.557272, display_name = "Bresle"},
    ["Fosseux"] = { latitude = 50.256212, longitude = 2.562805, display_name = "Fosseux"},
    ["Grand-Rullecourt"] = { latitude = 50.255160, longitude = 2.473003, display_name = "Grand-Rullecourt"},
    ["Bavincourt"] = { latitude = 50.225523, longitude = 2.567702, display_name = "Bavincourt"},
    ["Gouy-en-Artois"] = { latitude = 50.247612, longitude = 2.592830, display_name = "Gouy-en-Artois"},
    ["La Herliere"] = { latitude = 50.207756, longitude = 2.559321, display_name = "La Herliere"},
    ["Neuville-sous-Montreuil"] = { latitude = 50.475693, longitude = 1.772682, display_name = "Neuville-sous-Montreuil"},
    ["Ponches-Estruval"] = { latitude = 50.310024, longitude = 1.893326, display_name = "Ponches-Estruval"},
    ["Le Ponchel"] = { latitude = 50.257722, longitude = 2.071385, display_name = "Le Ponchel"},
    ["La Chaussee-Tirancourt"] = { latitude = 49.952940, longitude = 2.148340, display_name = "La Chaussee-Tirancourt"},
    ["Ligescourt"] = { latitude = 50.288349, longitude = 1.876473, display_name = "Ligescourt"},
    ["Canettemont"] = { latitude = 50.278814, longitude = 2.364928, display_name = "Canettemont"},
    ["Montenescourt"] = { latitude = 50.293878, longitude = 2.622887, display_name = "Montenescourt"},
    ["Saint-Georges"] = { latitude = 50.358395, longitude = 2.087431, display_name = "Saint-Georges"},
    ["Berles-au-Bois"] = { latitude = 50.199002, longitude = 2.627526, display_name = "Berles-au-Bois"},
    ["Bettencourt-Saint-Ouen"] = { latitude = 50.024972, longitude = 2.110299, display_name = "Bettencourt-Saint-Ouen"},
    ["Lepine"] = { latitude = 50.385461, longitude = 1.735686, display_name = "Lepine"},
    ["Floringhem"] = { latitude = 50.496938, longitude = 2.425733, display_name = "Floringhem"},
    ["Buneville"] = { latitude = 50.324403, longitude = 2.357109, display_name = "Buneville"},
    ["Laleu"] = { latitude = 49.941220, longitude = 1.932334, display_name = "Laleu"},
    ["Agenvillers"] = { latitude = 50.176756, longitude = 1.917494, display_name = "Agenvillers"},
    ["Boufflers"] = { latitude = 50.261571, longitude = 2.020652, display_name = "Boufflers"},
    ["Brailly-Cornehotte"] = { latitude = 50.217280, longitude = 1.959455, display_name = "Brailly-Cornehotte"},
    ["Domvast"] = { latitude = 50.198286, longitude = 1.921579, display_name = "Domvast"},
    ["Estrees-les-Crecy"] = { latitude = 50.253385, longitude = 1.927624, display_name = "Estrees-les-Crecy"},
    ["Fontaine-sur-Maye"] = { latitude = 50.235651, longitude = 1.925212, display_name = "Fontaine-sur-Maye"},
    ["Gueschart"] = { latitude = 50.238987, longitude = 2.011477, display_name = "Gueschart"},
    ["Lamotte-Buleux"] = { latitude = 50.188737, longitude = 1.827387, display_name = "Lamotte-Buleux"},
    ["Noyelles-en-Chaussee"] = { latitude = 50.208602, longitude = 1.979836, display_name = "Noyelles-en-Chaussee"},
    ["Agenville"] = { latitude = 50.165070, longitude = 2.102450, display_name = "Agenville"},
    ["Autheux"] = { latitude = 50.142580, longitude = 2.228620, display_name = "Autheux"},
    ["Barly"] = { latitude = 50.202120, longitude = 2.272490, display_name = "Barly"},
    ["Bavelincourt"] = { latitude = 49.986440, longitude = 2.454680, display_name = "Bavelincourt"},
    ["Beaumetz"] = { latitude = 50.140950, longitude = 2.119650, display_name = "Beaumetz"},
    ["Bellancourt"] = { latitude = 50.090260, longitude = 1.909777, display_name = "Bellancourt"},
    ["Bernaville"] = { latitude = 50.132351, longitude = 2.164393, display_name = "Bernaville"},
    ["Bernatre"] = { latitude = 50.197320, longitude = 2.090690, display_name = "Bernatre"},
    ["Bouchon"] = { latitude = 50.035275, longitude = 2.028775, display_name = "Bouchon"},
    ["Bourdon"] = { latitude = 49.987030, longitude = 2.074940, display_name = "Bourdon"},
    ["Brucamps"] = { latitude = 50.072220, longitude = 2.056640, display_name = "Brucamps"},
    ["Bussus-Bussuel"] = { latitude = 50.109490, longitude = 1.998690, display_name = "Bussus-Bussuel"},
    ["Canchy"] = { latitude = 50.185670, longitude = 1.876900, display_name = "Canchy"},
    ["Conteville"] = { latitude = 50.177440, longitude = 2.074270, display_name = "Conteville"},
    ["Coulonvillers"] = { latitude = 50.141880, longitude = 2.006440, display_name = "Coulonvillers"},
    ["Cramont"] = { latitude = 50.148130, longitude = 2.053520, display_name = "Cramont"},
    ["Domleger-Longvillers"] = { latitude = 50.159560, longitude = 2.085780, display_name = "Domleger-Longvillers"},
    ["Domqueur"] = { latitude = 50.114380, longitude = 2.048880, display_name = "Domqueur"},
    ["Ergnies"] = { latitude = 50.085540, longitude = 2.036850, display_name = "Ergnies"},
    ["Francieres"] = { latitude = 50.072004, longitude = 1.940737, display_name = "Francieres"},
    ["Franqueville"] = { latitude = 50.094150, longitude = 2.106500, display_name = "Franqueville"},
    ["Fransu"] = { latitude = 50.109320, longitude = 2.092270, display_name = "Fransu"},
    ["Frohen-sur-Authie"] = { latitude = 50.202322, longitude = 2.206451, display_name = "Frohen-sur-Authie"},
    ["Gorenflos"] = { latitude = 50.095791, longitude = 2.049428, display_name = "Gorenflos"},
    ["Heucourt-Croquoison"] = { latitude = 49.930668, longitude = 1.881624, display_name = "Heucourt-Croquoison"},
    ["Heuzecourt"] = { latitude = 50.172710, longitude = 2.166640, display_name = "Heuzecourt"},
    ["Hiermont"] = { latitude = 50.194510, longitude = 2.075350, display_name = "Hiermont"},
    ["Le Meillard"] = { latitude = 50.170104, longitude = 2.194871, display_name = "Le Meillard"},
    ["Le Mesge"] = { latitude = 49.944630, longitude = 2.052050, display_name = "Le Mesge"},
    ["Maison-Ponthieu"] = { latitude = 50.206830, longitude = 2.042950, display_name = "Maison-Ponthieu"},
    ["Maison-Roland"] = { latitude = 50.127893, longitude = 2.020570, display_name = "Maison-Roland"},
    ["Maizicourt"] = { latitude = 50.195750, longitude = 2.121500, display_name = "Maizicourt"},
    ["Mesnil-Domqueur"] = { latitude = 50.135190, longitude = 2.069450, display_name = "Mesnil-Domqueur"},
    ["Montigny-les-Jongleurs"] = { latitude = 50.180560, longitude = 2.132950, display_name = "Montigny-les-Jongleurs"},
    ["Mouflers"] = { latitude = 50.047010, longitude = 2.048740, display_name = "Mouflers"},
    ["Metigny"] = { latitude = 49.944500, longitude = 1.926760, display_name = "Metigny"},
    ["Mezerolles"] = { latitude = 50.186630, longitude = 2.234950, display_name = "Mezerolles"},
    ["Neuilly-le-Dien"] = { latitude = 50.223000, longitude = 2.042640, display_name = "Neuilly-le-Dien"},
    ["Occoches"] = { latitude = 50.174360, longitude = 2.269295, display_name = "Occoches"},
    ["Oneux"] = { latitude = 50.145194, longitude = 1.970786, display_name = "Oneux"},
    ["Outrebois"] = { latitude = 50.174160, longitude = 2.251920, display_name = "Outrebois"},
    ["Prouville"] = { latitude = 50.147280, longitude = 2.125130, display_name = "Prouville"},
    ["Ribeaucourt"] = { latitude = 50.116690, longitude = 2.118670, display_name = "Ribeaucourt"},
    ["Saint-Acheul"] = { latitude = 50.190650, longitude = 2.163550, display_name = "Saint-Acheul"},
    ["Saint-Gratien"] = { latitude = 49.965740, longitude = 2.408160, display_name = "Saint-Gratien"},
    ["Soues"] = { latitude = 49.956320, longitude = 2.053220, display_name = "Soues"},
    ["Surcamps"] = { latitude = 50.068760, longitude = 2.073450, display_name = "Surcamps"},
    ["Vauchelles-les-Quesnoy"] = { latitude = 50.102607, longitude = 1.888834, display_name = "Vauchelles-les-Quesnoy"},
    ["Villers-sous-Ailly"] = { latitude = 50.061130, longitude = 2.016390, display_name = "Villers-sous-Ailly"},
    ["Vitz-sur-Authie"] = { latitude = 50.252906, longitude = 2.064327, display_name = "Vitz-sur-Authie"},
    ["Yaucourt-Bussus"] = { latitude = 50.103020, longitude = 1.976480, display_name = "Yaucourt-Bussus"},
    ["Yvrench"] = { latitude = 50.177790, longitude = 2.004340, display_name = "Yvrench"},
    ["Yvrencheux"] = { latitude = 50.181246, longitude = 1.992521, display_name = "Yvrencheux"},
    ["Yzeux"] = { latitude = 49.974700, longitude = 2.108600, display_name = "Yzeux"},
    ["Longroy"] = { latitude = 49.988960, longitude = 1.535680, display_name = "Longroy"},
    ["Rieux"] = { latitude = 49.934680, longitude = 1.580390, display_name = "Rieux"},
    ["Agnez-les-Duisans"] = { latitude = 50.307060, longitude = 2.658735, display_name = "Agnez-les-Duisans"},
    ["Alette"] = { latitude = 50.517430, longitude = 1.827260, display_name = "Alette"},
    ["Attin"] = { latitude = 50.487875, longitude = 1.743784, display_name = "Attin"},
    ["Auchy-les-Hesdin"] = { latitude = 50.398823, longitude = 2.101955, display_name = "Auchy-les-Hesdin"},
    ["Aumerval"] = { latitude = 50.506482, longitude = 2.399188, display_name = "Aumerval"},
    ["Averdoingt"] = { latitude = 50.343970, longitude = 2.441610, display_name = "Averdoingt"},
    ["Bailleul-les-Pernes"] = { latitude = 50.510004, longitude = 2.387600, display_name = "Bailleul-les-Pernes"},
    ["Beaufort-Blavincourt"] = { latitude = 50.278670, longitude = 2.496980, display_name = "Beaufort-Blavincourt"},
    ["Beaumerie-Saint-Martin"] = { latitude = 50.455200, longitude = 1.797770, display_name = "Beaumerie-Saint-Martin"},
    ["Beauvois"] = { latitude = 50.374280, longitude = 2.233510, display_name = "Beauvois"},
    ["Bermicourt"] = { latitude = 50.408797, longitude = 2.230856, display_name = "Bermicourt"},
    ["Blangerval-Blangermont"] = { latitude = 50.323080, longitude = 2.230080, display_name = "Blangerval-Blangermont"},
    ["Blangy-sur-Ternoise"] = { latitude = 50.421450, longitude = 2.169560, display_name = "Blangy-sur-Ternoise"},
    ["Blingel"] = { latitude = 50.407830, longitude = 2.147310, display_name = "Blingel"},
    ["Bonnieres"] = { latitude = 50.244380, longitude = 2.259194, display_name = "Bonnieres"},
    ["Boubers-sur-Canche"] = { latitude = 50.291293, longitude = 2.236901, display_name = "Boubers-sur-Canche"},
    ["Bours"] = { latitude = 50.453854, longitude = 2.410986, display_name = "Bours"},
    ["Campigneulles-les-Grandes"] = { latitude = 50.435490, longitude = 1.712490, display_name = "Campigneulles-les-Grandes"},
    ["Cavron-Saint-Martin"] = { latitude = 50.417635, longitude = 1.999383, display_name = "Cavron-Saint-Martin"},
    ["Cheriennes"] = { latitude = 50.313630, longitude = 2.035310, display_name = "Cheriennes"},
    ["Clenleu"] = { latitude = 50.522310, longitude = 1.869390, display_name = "Clenleu"},
    ["Contes"] = { latitude = 50.409136, longitude = 1.961080, display_name = "Contes"},
    ["Conteville-en-Ternois"] = { latitude = 50.432745, longitude = 2.323704, display_name = "Conteville-en-Ternois"},
    ["Croisette"] = { latitude = 50.352900, longitude = 2.260500, display_name = "Croisette"},
    ["Croix-en-Ternois"] = { latitude = 50.383689, longitude = 2.280064, display_name = "Croix-en-Ternois"},
    ["Denier"] = { latitude = 50.287307, longitude = 2.443385, display_name = "Denier"},
    ["Duisans"] = { latitude = 50.305979, longitude = 2.688110, display_name = "Duisans"},
    ["Eps"] = { latitude = 50.454550, longitude = 2.295560, display_name = "Eps"},
    ["Estree-Wamin"] = { latitude = 50.269353, longitude = 2.394498, display_name = "Estree-Wamin"},
    ["Estreelles"] = { latitude = 50.498150, longitude = 1.782670, display_name = "Estreelles"},
    ["Flers"] = { latitude = 50.320530, longitude = 2.252550, display_name = "Flers"},
    ["Fleury"] = { latitude = 50.421990, longitude = 2.253860, display_name = "Fleury"},
    ["Fontaine-les-Hermans"] = { latitude = 50.526059, longitude = 2.349442, display_name = "Fontaine-les-Hermans"},
    ["Foufflin-Ricametz"] = { latitude = 50.350106, longitude = 2.385308, display_name = "Foufflin-Ricametz"},
    ["Framecourt"] = { latitude = 50.330239, longitude = 2.304849, display_name = "Framecourt"},
    ["Fresnoy"] = { latitude = 50.367300, longitude = 2.128340, display_name = "Fresnoy"},
    ["Galametz"] = { latitude = 50.327860, longitude = 2.137430, display_name = "Galametz"},
    ["Gaudiempre"] = { latitude = 50.177793, longitude = 2.530880, display_name = "Gaudiempre"},
    ["Givenchy-le-Noble"] = { latitude = 50.301509, longitude = 2.497085, display_name = "Givenchy-le-Noble"},
    ["Gouves"] = { latitude = 50.298885, longitude = 2.635911, display_name = "Gouves"},
    ["Gouy-en-Ternois"] = { latitude = 50.319846, longitude = 2.411787, display_name = "Gouy-en-Ternois"},
    ["Grigny"] = { latitude = 50.385624, longitude = 2.065831, display_name = "Grigny"},
    ["Guigny"] = { latitude = 50.329467, longitude = 1.998550, display_name = "Guigny"},
    ["Guinecourt"] = { latitude = 50.348190, longitude = 2.225490, display_name = "Guinecourt"},
    ["Herlin-le-Sec"] = { latitude = 50.354811, longitude = 2.330732, display_name = "Herlin-le-Sec"},
    ["Herlincourt"] = { latitude = 50.344072, longitude = 2.302256, display_name = "Herlincourt"},
    ["Hestrus"] = { latitude = 50.447560, longitude = 2.329400, display_name = "Hestrus"},
    ["Houvin-Houvigneul"] = { latitude = 50.297612, longitude = 2.382772, display_name = "Houvin-Houvigneul"},
    ["Huclier"] = { latitude = 50.429750, longitude = 2.356340, display_name = "Huclier"},
    ["Humbert"] = { latitude = 50.503790, longitude = 1.906400, display_name = "Humbert"},
    ["Humeroeuille"] = { latitude = 50.404779, longitude = 2.212303, display_name = "Humeroeuille"},
    ["Humieres"] = { latitude = 50.387224, longitude = 2.203771, display_name = "Humieres"},
    ["Hericourt"] = { latitude = 50.344780, longitude = 2.253600, display_name = "Hericourt"},
    ["Incourt"] = { latitude = 50.390810, longitude = 2.151810, display_name = "Incourt"},
    ["Ivergny"] = { latitude = 50.238690, longitude = 2.392370, display_name = "Ivergny"},
    ["La Thieuloye"] = { latitude = 50.413245, longitude = 2.434301, display_name = "La Thieuloye"},
    ["Lattre-Saint-Quentin"] = { latitude = 50.288187, longitude = 2.579949, display_name = "Lattre-Saint-Quentin"},
    ["Le Quesnoy-en-Artois"] = { latitude = 50.333480, longitude = 2.047560, display_name = "Le Quesnoy-en-Artois"},
    ["Lignereuil"] = { latitude = 50.290257, longitude = 2.472974, display_name = "Lignereuil"},
    ["Ligny-Saint-Flochel"] = { latitude = 50.358524, longitude = 2.428236, display_name = "Ligny-Saint-Flochel"},
    ["Ligny-sur-Canche"] = { latitude = 50.285305, longitude = 2.256618, display_name = "Ligny-sur-Canche"},
    ["Linzeux"] = { latitude = 50.340901, longitude = 2.204534, display_name = "Linzeux"},
    ["Maisnil"] = { latitude = 50.345400, longitude = 2.364430, display_name = "Maisnil"},
    ["Maisoncelle"] = { latitude = 50.447300, longitude = 2.142420, display_name = "Maisoncelle"},
    ["Marest"] = { latitude = 50.466914, longitude = 2.412207, display_name = "Marest"},
    ["Marquay"] = { latitude = 50.382133, longitude = 2.421446, display_name = "Marquay"},
    ["Moncheaux-les-Frevent"] = { latitude = 50.315119, longitude = 2.366697, display_name = "Moncheaux-les-Frevent"},
    ["Monchel-sur-Canche"] = { latitude = 50.302352, longitude = 2.206383, display_name = "Monchel-sur-Canche"},
    ["Monchiet"] = { latitude = 50.241219, longitude = 2.627880, display_name = "Monchiet"},
    ["Monchy-Breton"] = { latitude = 50.400701, longitude = 2.440284, display_name = "Monchy-Breton"},
    ["Monts-en-Ternois"] = { latitude = 50.321300, longitude = 2.384886, display_name = "Monts-en-Ternois"},
    ["Neulette"] = { latitude = 50.381981, longitude = 2.166513, display_name = "Neulette"},
    ["Neuville-au-Cornet"] = { latitude = 50.334717, longitude = 2.369580, display_name = "Neuville-au-Cornet"},
    ["Noyelles-les-Humieres"] = { latitude = 50.373000, longitude = 2.174570, display_name = "Noyelles-les-Humieres"},
    ["Noyellette"] = { latitude = 50.299034, longitude = 2.595646, display_name = "Noyellette"},
    ["Nuncq-Hautecote"] = { latitude = 50.305812, longitude = 2.287741, display_name = "Nuncq-Hautecote"},
    ["Nedon"] = { latitude = 50.524960, longitude = 2.369200, display_name = "Nedon"},
    ["Nedonchel"] = { latitude = 50.524010, longitude = 2.357950, display_name = "Nedonchel"},
    ["Orville"] = { latitude = 50.134376, longitude = 2.409865, display_name = "Orville"},
    ["Ostreville"] = { latitude = 50.395774, longitude = 2.394440, display_name = "Ostreville"},
    ["Pierremont"] = { latitude = 50.401179, longitude = 2.259532, display_name = "Pierremont"},
    ["Predefin"] = { latitude = 50.502780, longitude = 2.254070, display_name = "Predefin"},
    ["Quilen"] = { latitude = 50.529100, longitude = 1.926980, display_name = "Quilen"},
    ["Raye-sur-Authie"] = { latitude = 50.297650, longitude = 1.947310, display_name = "Raye-sur-Authie"},
    ["Rebreuve-sur-Canche"] = { latitude = 50.264927, longitude = 2.340054, display_name = "Rebreuve-sur-Canche"},
    ["Rebreuviette"] = { latitude = 50.262936, longitude = 2.361126, display_name = "Rebreuviette"},
    ["Rollancourt"] = { latitude = 50.407650, longitude = 2.122020, display_name = "Rollancourt"},
    ["Roellecourt"] = { latitude = 50.366485, longitude = 2.389433, display_name = "Roellecourt"},
    ["Sains-les-Pernes"] = { latitude = 50.477320, longitude = 2.355730, display_name = "Sains-les-Pernes"},
    ["Saint-Michel-sous-Bois"] = { latitude = 50.513880, longitude = 1.931520, display_name = "Saint-Michel-sous-Bois"},
    ["Saint-Michel-sur-Ternoise"] = { latitude = 50.376360, longitude = 2.357629, display_name = "Saint-Michel-sur-Ternoise"},
    ["Sars-le-Bois"] = { latitude = 50.294530, longitude = 2.428140, display_name = "Sars-le-Bois"},
    ["Sibiville"] = { latitude = 50.298490, longitude = 2.322970, display_name = "Sibiville"},
    ["Sus-Saint-Leger"] = { latitude = 50.238951, longitude = 2.432944, display_name = "Sus-Saint-Leger"},
    ["Sericourt"] = { latitude = 50.294063, longitude = 2.314864, display_name = "Sericourt"},
    ["Teneur"] = { latitude = 50.450490, longitude = 2.218290, display_name = "Teneur"},
    ["Ternas"] = { latitude = 50.341970, longitude = 2.396740, display_name = "Ternas"},
    ["Tilly-Capelle"] = { latitude = 50.443180, longitude = 2.197320, display_name = "Tilly-Capelle"},
    ["Tramecourt"] = { latitude = 50.463510, longitude = 2.150580, display_name = "Tramecourt"},
    ["Troisvaux"] = { latitude = 50.401834, longitude = 2.343038, display_name = "Troisvaux"},
    ["Vaulx"] = { latitude = 50.267362, longitude = 2.096494, display_name = "Vaulx"},
    ["Wambercourt"] = { latitude = 50.428460, longitude = 2.023280, display_name = "Wambercourt"},
    ["Warluzel"] = { latitude = 50.228915, longitude = 2.470100, display_name = "Warluzel"},
    ["Willeman"] = { latitude = 50.353056, longitude = 2.156880, display_name = "Willeman"},
    ["Eclimeux"] = { latitude = 50.398789, longitude = 2.178689, display_name = "Eclimeux"},
    ["Ecoivres"] = { latitude = 50.322530, longitude = 2.287590, display_name = "Ecoivres"},
    ["Ecuires"] = { latitude = 50.444689, longitude = 1.763966, display_name = "Ecuires"},
    ["Equirre"] = { latitude = 50.472375, longitude = 2.236755, display_name = "Equirre"},
    ["Erin"] = { latitude = 50.438760, longitude = 2.208630, display_name = "Erin"},
    ["Oeuf-en-Ternois"] = { latitude = 50.358938, longitude = 2.211600, display_name = "Oeuf-en-Ternois"},
    ["Groffliers"] = { latitude = 50.382729, longitude = 1.622814, display_name = "Groffliers"},
    ["Tubersent"] = { latitude = 50.519779, longitude = 1.704451, display_name = "Tubersent"},
    ["Maresville"] = { latitude = 50.526554, longitude = 1.731015, display_name = "Maresville"},
    ["Brexent-Enocq"] = { latitude = 50.509592, longitude = 1.728742, display_name = "Brexent-Enocq"},
    ["La Madelaine-sous-Montreuil"] = { latitude = 50.468627, longitude = 1.749413, display_name = "La Madelaine-sous-Montreuil"},
    ["Airon-Saint-Vaast"] = { latitude = 50.431377, longitude = 1.669146, display_name = "Airon-Saint-Vaast"},
    ["Airon-Notre-Dame"] = { latitude = 50.436507, longitude = 1.655822, display_name = "Airon-Notre-Dame"},
    ["Verton"] = { latitude = 50.401553, longitude = 1.650798, display_name = "Verton"},
    ["Caumont"] = { latitude = 50.289437, longitude = 2.029945, display_name = "Caumont"},
    ["Fillievres"] = { latitude = 50.319387, longitude = 2.158198, display_name = "Fillievres"},
    ["Conchil-le-Temple"] = { latitude = 50.370928, longitude = 1.665754, display_name = "Conchil-le-Temple"},
    ["Tigny-Noyelle"] = { latitude = 50.351400, longitude = 1.717952, display_name = "Tigny-Noyelle"},
    ["Waben"] = { latitude = 50.380072, longitude = 1.653436, display_name = "Waben"},
    ["Coupelle-Vieille"] = { latitude = 50.524957, longitude = 2.099054, display_name = "Coupelle-Vieille"},
    ["Saint-Remy-au-Bois"] = { latitude = 50.368111, longitude = 1.873131, display_name = "Saint-Remy-au-Bois"},
    ["Avondance"] = { latitude = 50.476049, longitude = 2.098952, display_name = "Avondance"},
    ["Boubers-les-Hesmond"] = { latitude = 50.475020, longitude = 1.949731, display_name = "Boubers-les-Hesmond"},
    ["Coupelle-Neuve"] = { latitude = 50.500689, longitude = 2.119716, display_name = "Coupelle-Neuve"},
    ["Offin"] = { latitude = 50.445176, longitude = 1.942315, display_name = "Offin"},
    ["Torcy"] = { latitude = 50.483063, longitude = 2.023020, display_name = "Torcy"},
    ["Sainte-Austreberthe"] = { latitude = 50.363513, longitude = 2.046351, display_name = "Sainte-Austreberthe"},
    ["Beauvoir-Wavans"] = { latitude = 50.218474, longitude = 2.163085, display_name = "Beauvoir-Wavans"},
    ["Conchy-sur-Canche"] = { latitude = 50.300792, longitude = 2.195983, display_name = "Conchy-sur-Canche"},
    ["Gennes-Ivergny"] = { latitude = 50.264791, longitude = 2.047602, display_name = "Gennes-Ivergny"},
    ["Rougefay"] = { latitude = 50.272935, longitude = 2.171032, display_name = "Rougefay"},
    ["Vacquerie-le-Boucq"] = { latitude = 50.269647, longitude = 2.218496, display_name = "Vacquerie-le-Boucq"},
    ["Vieil-Hesdin"] = { latitude = 50.356193, longitude = 2.098040, display_name = "Vieil-Hesdin"},
    ["Boffles"] = { latitude = 50.253988, longitude = 2.202852, display_name = "Boffles"},
    ["Canteleux"] = { latitude = 50.216291, longitude = 2.306973, display_name = "Canteleux"},
    ["Pressy"] = { latitude = 50.475604, longitude = 2.397533, display_name = "Pressy"},
    ["Dieval"] = { latitude = 50.434914, longitude = 2.448034, display_name = "Dieval"},
    ["Berles-Monchel"] = { latitude = 50.345477, longitude = 2.537601, display_name = "Berles-Monchel"},
    ["Berlencourt-le-Cauroy"] = { latitude = 50.279325, longitude = 2.423454, display_name = "Berlencourt-le-Cauroy"},
    ["Noyelle-Vion"] = { latitude = 50.294571, longitude = 2.547255, display_name = "Noyelle-Vion"},
    ["Villers-Sir-Simon"] = { latitude = 50.317131, longitude = 2.491699, display_name = "Villers-Sir-Simon"},
    ["Warlincourt-les-Pas"] = { latitude = 50.174255, longitude = 2.505313, display_name = "Warlincourt-les-Pas"},
    ["Sailly-au-Bois"] = { latitude = 50.119868, longitude = 2.595678, display_name = "Sailly-au-Bois"},
    ["Hem-Hardinval"] = { latitude = 50.163187, longitude = 2.303552, display_name = "Hem-Hardinval"},
    ["Vauchelles-les-Domart"] = { latitude = 50.055224, longitude = 2.057334, display_name = "Vauchelles-les-Domart"},
    ["Herissart"] = { latitude = 50.027715, longitude = 2.415546, display_name = "Herissart"},
    ["Dompierre-sur-Authie"] = { latitude = 50.303624, longitude = 1.916875, display_name = "Dompierre-sur-Authie"},
    ["Incheville"] = { latitude = 50.015656, longitude = 1.497900, display_name = "Incheville"},
    ["Saint-Pierre-en-Val"] = { latitude = 50.021065, longitude = 1.446089, display_name = "Saint-Pierre-en-Val"},
    ["Fourdrinoy"] = { latitude = 49.917409, longitude = 2.107841, display_name = "Fourdrinoy"},
    ["Hauteville"] = { latitude = 50.273637, longitude = 2.573024, display_name = "Hauteville"},
    ["Acquet"] = { latitude = 50.226910, longitude = 2.056536, display_name = "Acquet"},
    ["Labroye"] = { latitude = 50.278221, longitude = 1.989908, display_name = "Labroye"},
    ["Vignacourt"] = { latitude = 50.011839, longitude = 2.196777, display_name = "Vignacourt"},
    ["Lealvillers"] = { latitude = 50.065428, longitude = 2.509457, display_name = "Lealvillers"},
    ["Fieffes-Montrelet"] = { latitude = 50.083938, longitude = 2.231165, display_name = "Fieffes-Montrelet"},
    ["Beaurainville"] = { latitude = 50.424843, longitude = 1.900946, display_name = "Beaurainville"},
    ["Heudelimont"] = { latitude = 50.011780, longitude = 1.375816, display_name = "Heudelimont"},
    ["Valencendre"] = { latitude = 50.485801, longitude = 1.693873, display_name = "Valencendre"},
    ["Boisbergues"] = { latitude = 50.156183, longitude = 2.229787, display_name = "Boisbergues"},
    ["Auchonvillers"] = { latitude = 50.081345, longitude = 2.630008, display_name = "Auchonvillers"},
    ["Rue"] = { latitude = 50.272874, longitude = 1.667305, display_name = "Rue"},
    ["Amiens"] = { latitude = 49.899546, longitude = 2.295089, display_name = "Amiens"},
    ["Corbie"] = { latitude = 49.908415, longitude = 2.510957, display_name = "Corbie"},
    ["Arras"] = { latitude = 50.288691, longitude = 2.776158, display_name = "Arras"},
    ["Douai"] = { latitude = 50.367849, longitude = 3.089882, display_name = "Douai"},
    ["Lens"] = { latitude = 50.427170, longitude = 2.832500, display_name = "Lens"},
}

veafNamedPoints._citiesSyria = 
{
    ["Racetrack for Camel Racing"] = { latitude = 34.403706, longitude = 38.198771, display_name = ("Racetrack for Camel Racing")},
    ["Marbat El-Hassan Reservoir"] = { latitude = 34.667083, longitude = 38.224691, display_name = ("Marbat El-Hassan Reservoir")},
    ["Sharqiyah Mine"] = { latitude = 34.199641, longitude = 38.014742, display_name = ("Sharqiyah Mine")},
    ["Solonchak Sabhat al-Jabbul"] = { latitude = 36.044571, longitude = 37.521323, display_name = ("Solonchak Sabhat al-Jabbul")},
    ["Adana"] = { latitude = 37.009025, longitude = 35.305756, display_name = ("Adana")},
    ["Aleppo"] = { latitude = 36.206786, longitude = 37.142391, display_name = ("Aleppo")},
    ["Raqqa"] = { latitude = 35.960290, longitude = 39.015335, display_name = ("Raqqa")},
    ["Latakia"] = { latitude = 35.525744, longitude = 35.785411, display_name = ("Latakia")},
    ["Hama"] = { latitude = 35.147288, longitude = 36.757011, display_name = ("Hama")},
    ["Homs"] = { latitude = 34.731897, longitude = 36.711724, display_name = ("Homs")},
    ["Palmyra"] = { latitude = 34.565481, longitude = 38.284229, display_name = ("Palmyra")},
    ["Tartus"] = { latitude = 34.893779, longitude = 35.892245, display_name = ("Tartus")},
    ["Tripoli"] = { latitude = 34.435775, longitude = 35.837320, display_name = ("Tripoli")},
    ["Beirut"] = { latitude = 33.861187, longitude = 35.526848, display_name = ("Beirut")},
    ["Damascus"] = { latitude = 33.518073, longitude = 36.296386, display_name = ("Damascus")},
    ["Haifa"] = { latitude = 32.813432, longitude = 34.987236, display_name = ("Haifa")},
    ["Idlib"] = { latitude = 35.930952, longitude = 36.633923, display_name = ("Idlib")},
    ["Qudssaya"] = { latitude = 33.536740, longitude = 36.234351, display_name = ("Qudssaya")},
    ["Qudssaya Suburb"] = { latitude = 33.540064, longitude = 36.192270, display_name = ("Qudssaya Suburb")},
    ["At Tall"] = { latitude = 33.600323, longitude = 36.315657, display_name = ("At Tall")},
    ["Alassad"] = { latitude = 33.581558, longitude = 36.358940, display_name = ("Alassad")},
    ["Duma"] = { latitude = 33.571938, longitude = 36.405781, display_name = ("Duma")},
    ["Harasta"] = { latitude = 33.559036, longitude = 36.366581, display_name = ("Harasta")},
    ["Arbil"] = { latitude = 33.539070, longitude = 36.366804, display_name = ("Arbil")},
    ["Madryara"] = { latitude = 33.544681, longitude = 36.395586, display_name = ("Madryara")},
    ["Ad Dumayr"] = { latitude = 33.643665, longitude = 36.691055, display_name = ("Ad Dumayr")},
    ["Al Qutayfah"] = { latitude = 33.741700, longitude = 36.594686, display_name = ("Al Qutayfah")},
    ["Kafr Batna"] = { latitude = 33.514919, longitude = 36.384188, display_name = ("Kafr Batna")},
    ["Ein Tamra"] = { latitude = 33.518276, longitude = 36.352033, display_name = ("Ein Tamra")},
    ["Al Mleha"] = { latitude = 33.484569, longitude = 36.373028, display_name = ("Al Mleha")},
    ["Babbila"] = { latitude = 33.477200, longitude = 36.336357, display_name = ("Babbila")},
    ["El Hajar Al Aswad"] = { latitude = 33.468749, longitude = 36.309625, display_name = ("El Hajar Al Aswad")},
    ["Set Zaynab"] = { latitude = 33.448837, longitude = 36.338214, display_name = ("Set Zaynab")},
    ["Shebaa"] = { latitude = 33.449565, longitude = 36.398202, display_name = ("Shebaa")},
    ["Al Ghuzlaniyah"] = { latitude = 33.398953, longitude = 36.454461, display_name = ("Al Ghuzlaniyah")},
    ["Jdaydet Alkhas"] = { latitude = 33.405680, longitude = 36.544628, display_name = ("Jdaydet Alkhas")},
    ["Harran al'Awamid"] = { latitude = 33.447575, longitude = 36.561596, display_name = ("Harran al'Awamid")},
    ["Otaybah"] = { latitude = 33.483517, longitude = 36.610154, display_name = ("Otaybah")},
    ["Hayjanah"] = { latitude = 33.358906, longitude = 36.544664, display_name = ("Hayjanah")},
    ["Al Baytariyah"] = { latitude = 33.315697, longitude = 36.543629, display_name = ("Al Baytariyah")},
    ["Buraq"] = { latitude = 33.185661, longitude = 36.479501, display_name = ("Buraq")},
    ["Hazm"] = { latitude = 33.132337, longitude = 36.524319, display_name = ("Hazm")},
    ["Qura Al-Assad"] = { latitude = 33.556956, longitude = 36.135584, display_name = ("Qura Al-Assad")},
    ["Al-Dimass"] = { latitude = 33.588011, longitude = 36.091209, display_name = ("Al-Dimass")},
    ["Al Moadamyeh"] = { latitude = 33.463478, longitude = 36.187321, display_name = ("Al Moadamyeh")},
    ["Darayya"] = { latitude = 33.460294, longitude = 36.235558, display_name = ("Darayya")},
    ["Sahnaya"] = { latitude = 33.432227, longitude = 36.237736, display_name = ("Sahnaya")},
    ["Jdaydet Artooz"] = { latitude = 33.438519, longitude = 36.159494, display_name = ("Jdaydet Artooz")},
    ["Zakyah"] = { latitude = 33.337249, longitude = 36.165773, display_name = ("Zakyah")},
    ["As Sanamayn"] = { latitude = 33.077101, longitude = 36.185811, display_name = ("As Sanamayn")},
    ["Jasim"] = { latitude = 32.996681, longitude = 36.064041, display_name = ("Jasim")},
    ["Ankhul"] = { latitude = 33.017328, longitude = 36.130427, display_name = ("Ankhul")},
    ["Kafr Shams"] = { latitude = 33.122908, longitude = 36.115409, display_name = ("Kafr Shams")},
    ["Aqrabac"] = { latitude = 33.111487, longitude = 36.001655, display_name = ("Aqrabac")},
    ["Al Harah"] = { latitude = 33.057838, longitude = 36.006797, display_name = ("Al Harah")},
    ["Cotlu"] = { latitude = 36.874264, longitude = 35.487177, display_name = ("Cotlu")},
    ["Ceyhan"] = { latitude = 37.032204, longitude = 35.820925, display_name = ("Ceyhan")},
    ["Karsi"] = { latitude = 36.761068, longitude = 36.223837, display_name = ("Karsi")},
    ["Sariseki"] = { latitude = 36.629934, longitude = 36.220243, display_name = ("Sariseki")},
    ["Iskenderun"] = { latitude = 36.570207, longitude = 36.147166, display_name = ("Iskenderun")},
    ["Kirikhan"] = { latitude = 36.501251, longitude = 36.361665, display_name = ("Kirikhan")},
    ["Akincilar"] = { latitude = 36.388407, longitude = 36.231198, display_name = ("Akincilar")},
    ["Kumlu"] = { latitude = 36.365700, longitude = 36.465543, display_name = ("Kumlu")},
    ["Reyhanli"] = { latitude = 36.263975, longitude = 36.567831, display_name = ("Reyhanli")},
    ["Antakya"] = { latitude = 36.211221, longitude = 36.157590, display_name = ("Antakya")},
    ["Samandag"] = { latitude = 36.081880, longitude = 35.981085, display_name = ("Samandag")},
    ["Maarrat Misrin"] = { latitude = 36.013735, longitude = 36.678771, display_name = ("Maarrat Misrin")},
    ["Sarmin"] = { latitude = 35.906478, longitude = 36.725726, display_name = ("Sarmin")},
    ["Saraqib"] = { latitude = 35.861085, longitude = 36.808029, display_name = ("Saraqib")},
    ["Arihah"] = { latitude = 35.819453, longitude = 36.616768, display_name = ("Arihah")},
    ["Al Rami"] = { latitude = 35.804366, longitude = 36.487599, display_name = ("Al Rami")},
    ["Jisr Ash-Shughur"] = { latitude = 35.814383, longitude = 36.318373, display_name = ("Jisr Ash-Shughur")},
    ["Khan Assubul"] = { latitude = 35.751556, longitude = 36.758686, display_name = ("Khan Assubul")},
    ["Abu Ad Dhuhur"] = { latitude = 35.749513, longitude = 37.049436, display_name = ("Abu Ad Dhuhur")},
    ["Al Ghadfah"] = { latitude = 35.677496, longitude = 36.801608, display_name = ("Al Ghadfah")},
    ["Maarat al-Numan"] = { latitude = 35.641889, longitude = 36.675356, display_name = ("Maarat al-Numan")},
    ["Kafr Nabi"] = { latitude = 35.614837, longitude = 36.568447, display_name = ("Kafr Nabi")},
    ["Tell Arn"] = { latitude = 36.124066, longitude = 37.333972, display_name = ("Tell Arn")},
    ["Al-Safirah"] = { latitude = 36.069189, longitude = 37.377062, display_name = ("Al-Safirah")},
    ["Al Bab"] = { latitude = 36.366231, longitude = 37.512118, display_name = ("Al Bab")},
    ["Manbij"] = { latitude = 36.525941, longitude = 37.960087, display_name = ("Manbij")},
    ["Maskanah"] = { latitude = 35.964663, longitude = 38.042601, display_name = ("Maskanah")},
    ["Al Tabqah"] = { latitude = 35.827092, longitude = 38.541302, display_name = ("Al Tabqah")},
    ["Sqoubin"] = { latitude = 35.558660, longitude = 35.829224, display_name = ("Sqoubin")},
    ["Sett Markho"] = { latitude = 35.589482, longitude = 35.851695, display_name = ("Sett Markho")},
    ["Al Hannadi"] = { latitude = 35.480731, longitude = 35.927036, display_name = ("Al Hannadi")},
    ["Jablah"] = { latitude = 35.363965, longitude = 35.927291, display_name = ("Jablah")},
    ["Baniyas"] = { latitude = 35.189238, longitude = 35.953516, display_name = ("Baniyas")},
    ["Ein Elkorum"] = { latitude = 35.368802, longitude = 36.409242, display_name = ("Ein Elkorum")},
    ["Muhradah"] = { latitude = 35.249643, longitude = 36.572717, display_name = ("Muhradah")},
    ["Halfaya"] = { latitude = 35.260712, longitude = 36.606798, display_name = ("Halfaya")},
    ["Ar Rastan"] = { latitude = 34.923488, longitude = 36.732503, display_name = ("Ar Rastan")},
    ["Hawash"] = { latitude = 34.762026, longitude = 36.322719, display_name = ("Hawash")},
    ["Zaidal"] = { latitude = 34.718672, longitude = 36.773686, display_name = ("Zaidal")},
    ["Fairuzah"] = { latitude = 34.702436, longitude = 36.757134, display_name = ("Fairuzah")},
    ["Al Qusayr"] = { latitude = 34.512113, longitude = 36.587781, display_name = ("Al Qusayr")},
    ["Khirbe"] = { latitude = 34.583839, longitude = 36.017331, display_name = ("Khirbe")},
    ["Al Aabde"] = { latitude = 34.511339, longitude = 35.962268, display_name = ("Al Aabde")},
    ["Chekka"] = { latitude = 34.323663, longitude = 35.731135, display_name = ("Chekka")},
    ["Hamat"] = { latitude = 34.285622, longitude = 35.692717, display_name = ("Hamat")},
    ["Batroun"] = { latitude = 34.252986, longitude = 35.666329, display_name = ("Batroun")},
    ["Maqne"] = { latitude = 34.076334, longitude = 36.206335, display_name = ("Maqne")},
    ["Baalbek"] = { latitude = 34.005549, longitude = 36.204317, display_name = ("Baalbek")},
    ["Jounieh"] = { latitude = 33.979290, longitude = 35.633664, display_name = ("Jounieh")},
    ["Rayak"] = { latitude = 33.853182, longitude = 36.021607, display_name = ("Rayak")},
    ["Serghaya"] = { latitude = 33.811850, longitude = 36.161077, display_name = ("Serghaya")},
    ["Al Zabadani"] = { latitude = 33.726458, longitude = 36.101596, display_name = ("Al Zabadani")},
    ["Madaya"] = { latitude = 33.682698, longitude = 36.095064, display_name = ("Madaya")},
    ["Zahle"] = { latitude = 33.842402, longitude = 35.925711, display_name = ("Zahle")},
    ["Taalabaya"] = { latitude = 33.814138, longitude = 35.873182, display_name = ("Taalabaya")},
    ["Bar Elias"] = { latitude = 33.774479, longitude = 35.900918, display_name = ("Bar Elias")},
    ["Anjar"] = { latitude = 33.727388, longitude = 35.931892, display_name = ("Anjar")},
    ["Majdel Anjar"] = { latitude = 33.707532, longitude = 35.907553, display_name = ("Majdel Anjar")},
    ["Ghazze"] = { latitude = 33.668655, longitude = 35.831698, display_name = ("Ghazze")},
    ["Joub Jannine"] = { latitude = 33.627189, longitude = 35.783124, display_name = ("Joub Jannine")},
    ["Qaraoun"] = { latitude = 33.567601, longitude = 35.721222, display_name = ("Qaraoun")},
    ["Khalde"] = { latitude = 33.777551, longitude = 35.475219, display_name = ("Khalde")},
    ["Haret Chbib"] = { latitude = 33.740920, longitude = 35.457533, display_name = ("Haret Chbib")},
    ["Chim"] = { latitude = 33.620974, longitude = 35.488638, display_name = ("Chim")},
    ["Saida"] = { latitude = 33.563094, longitude = 35.377028, display_name = ("Saida")},
    ["Nabatieh"] = { latitude = 33.381200, longitude = 35.479974, display_name = ("Nabatieh")},
    ["Qatana"] = { latitude = 33.438131, longitude = 36.079687, display_name = ("Qatana")},
    ["Khan Alsheh"] = { latitude = 33.373053, longitude = 36.113067, display_name = ("Khan Alsheh")},
    ["Kanaker"] = { latitude = 33.268693, longitude = 36.094640, display_name = ("Kanaker")},
    ["Jabah"] = { latitude = 33.164877, longitude = 35.927233, display_name = ("Jabah")},
    ["Khan Arnabeh"] = { latitude = 33.182506, longitude = 35.890276, display_name = ("Khan Arnabeh")},
    ["Naba Alsakher"] = { latitude = 33.088327, longitude = 35.947298, display_name = ("Naba Alsakher")},
    ["Ghabagheb"] = { latitude = 33.182552, longitude = 36.224414, display_name = ("Ghabagheb")},
    ["Jabab"] = { latitude = 33.113405, longitude = 36.264712, display_name = ("Jabab")},
    ["As Sawara"] = { latitude = 33.028305, longitude = 36.578694, display_name = ("As Sawara")},
    ["Shahba"] = { latitude = 32.858367, longitude = 36.632505, display_name = ("Shahba")},
    ["Muadamyat Al Qalamon"] = { latitude = 33.741316, longitude = 36.640933, display_name = ("Muadamyat Al Qalamon")},
    ["Al Kafr"] = { latitude = 32.632244, longitude = 36.648252, display_name = ("Al Kafr")},
    ["El Karak"] = { latitude = 32.685853, longitude = 36.353277, display_name = ("El Karak")},
    ["Eastern Garyiah"] = { latitude = 32.677965, longitude = 36.260940, display_name = ("Eastern Garyiah")},
    ["Western Garyiah"] = { latitude = 32.687917, longitude = 36.225685, display_name = ("Western Garyiah")},
    ["Khirbet Ghazaleh"] = { latitude = 32.737882, longitude = 36.201831, display_name = ("Khirbet Ghazaleh")},
    ["Busra"] = { latitude = 32.517997, longitude = 36.481645, display_name = ("Busra")},
    ["Dibin"] = { latitude = 32.439463, longitude = 36.567784, display_name = ("Dibin")},
    ["Miarbah"] = { latitude = 32.544797, longitude = 36.428341, display_name = ("Miarbah")},
    ["Ghasam"] = { latitude = 32.549096, longitude = 36.374564, display_name = ("Ghasam")},
    ["Al Jeezah"] = { latitude = 32.565680, longitude = 36.316322, display_name = ("Al Jeezah")},
    ["El Taebah"] = { latitude = 32.564137, longitude = 36.246336, display_name = ("El Taebah")},
    ["Ramtha"] = { latitude = 32.565921, longitude = 36.008365, display_name = ("Ramtha")},
    ["Et Turra"] = { latitude = 32.641456, longitude = 35.990626, display_name = ("Et Turra")},
    ["Irbid"] = { latitude = 32.557292, longitude = 35.856121, display_name = ("Irbid")},
    ["Tiberias"] = { latitude = 32.790725, longitude = 35.526414, display_name = ("Tiberias")},
    ["Nazareth"] = { latitude = 32.704739, longitude = 35.306182, display_name = ("Nazareth")},
    ["Daliyat al-Karmel"] = { latitude = 32.692722, longitude = 35.050793, display_name = ("Daliyat al-Karmel")},
    ["Umm al-Fahm"] = { latitude = 32.522954, longitude = 35.151370, display_name = ("Umm al-Fahm")},
    ["Afula"] = { latitude = 32.610048, longitude = 35.289515, display_name = ("Afula")},
    ["Ein Harod"] = { latitude = 32.556165, longitude = 35.394209, display_name = ("Ein Harod")},
    ["Beit Shean"] = { latitude = 32.492844, longitude = 35.501891, display_name = ("Beit Shean")},
    ["Iksal"] = { latitude = 32.684349, longitude = 35.325562, display_name = ("Iksal")},
    ["Hatzor HaGlilit"] = { latitude = 32.980146, longitude = 35.545722, display_name = ("Hatzor HaGlilit")},
    ["Migdal HaEmek"] = { latitude = 32.678191, longitude = 35.241797, display_name = ("Migdal HaEmek")},
    ["Nahalal"] = { latitude = 32.690694, longitude = 35.198958, display_name = ("Nahalal")},
    ["Ramat Yishai"] = { latitude = 32.705166, longitude = 35.166819, display_name = ("Ramat Yishai")},
    ["Kfar Yehoshua"] = { latitude = 32.681006, longitude = 35.153116, display_name = ("Kfar Yehoshua")},
    ["Kiryat Tivon"] = { latitude = 32.717158, longitude = 35.126145, display_name = ("Kiryat Tivon")},
    ["Yokneam Illit"] = { latitude = 32.653497, longitude = 35.102342, display_name = ("Yokneam Illit")},
    ["Kafr Qara"] = { latitude = 32.504314, longitude = 35.064774, display_name = ("Kafr Qara")},
    ["Pardes Hanna-Karkur"] = { latitude = 32.473446, longitude = 34.969511, display_name = ("Pardes Hanna-Karkur")},
    ["Hadera"] = { latitude = 32.428035, longitude = 34.919863, display_name = ("Hadera")},
    ["Isfiya"] = { latitude = 32.719767, longitude = 35.062365, display_name = ("Isfiya")},
    ["Ein Hod"] = { latitude = 32.698984, longitude = 34.987603, display_name = ("Ein Hod")},
    ["Atlit"] = { latitude = 32.688863, longitude = 34.943009, display_name = ("Atlit")},
    ["Kiryat Motzkin"] = { latitude = 32.834316, longitude = 35.084219, display_name = ("Kiryat Motzkin")},
    ["Kiryat Yam"] = { latitude = 32.842731, longitude = 35.069248, display_name = ("Kiryat Yam")},
    ["Kiryat Ata"] = { latitude = 32.809126, longitude = 35.115174, display_name = ("Kiryat Ata")},
    ["Shefar-Amr"] = { latitude = 32.804129, longitude = 35.172103, display_name = ("Shefar-Amr")},
    ["Ibilin"] = { latitude = 32.822190, longitude = 35.192066, display_name = ("Ibilin")},
    ["Kafr Manda"] = { latitude = 32.812136, longitude = 35.262557, display_name = ("Kafr Manda")},
    ["Kabul"] = { latitude = 32.869646, longitude = 35.213104, display_name = ("Kabul")},
    ["Tamra"] = { latitude = 32.854794, longitude = 35.194876, display_name = ("Tamra")},
    ["Karmiel"] = { latitude = 32.907124, longitude = 35.286717, display_name = ("Karmiel")},
    ["Deir al-Asad"] = { latitude = 32.929701, longitude = 35.275590, display_name = ("Deir al-Asad")},
    ["Jadeidi Makr"] = { latitude = 32.927754, longitude = 35.148612, display_name = ("Jadeidi Makr")},
    ["Yarka"] = { latitude = 32.955283, longitude = 35.211219, display_name = ("Yarka")},
    ["Kafr Yasif"] = { latitude = 32.957481, longitude = 35.166921, display_name = ("Kafr Yasif")},
    ["Acre"] = { latitude = 32.929521, longitude = 35.078140, display_name = ("Acre")},
    ["Nahariyya"] = { latitude = 33.008845, longitude = 35.100274, display_name = ("Nahariyya")},
    ["Bent Jbail"] = { latitude = 33.125874, longitude = 35.440673, display_name = ("Bent Jbail")},
    ["Qiryat Shemona"] = { latitude = 33.209375, longitude = 35.570715, display_name = ("Qiryat Shemona")},
    ["Tyre"] = { latitude = 33.275537, longitude = 35.215861, display_name = ("Tyre")},
    ["SAA 312th Brigade%"] = { latitude = 34.588207, longitude = 36.942638, display_name = ("SAA 312th Brigade%")},
    ["SAA 171th Brigade%"] = { latitude = 34.699599, longitude = 36.969931, display_name = ("SAA 171th Brigade%")},
    ["Netafim military base"] = { latitude = 32.810372, longitude = 35.420586, display_name = ("Netafim military base")},
    ["SyADF 24th Brigade"] = { latitude = 33.336071, longitude = 36.418180, display_name = ("SyADF 24th Brigade")},
    ["SAA 158th Brigade"] = { latitude = 33.401255, longitude = 36.272973, display_name = ("SAA 158th Brigade")},
    ["SAA 100th Regiment"] = { latitude = 33.380777, longitude = 36.213524, display_name = ("SAA 100th Regiment")},
    ["SAA 65th Brigade"] = { latitude = 33.766740, longitude = 36.547502, display_name = ("SAA 65th Brigade")},
    ["SAA 550th Brigade"] = { latitude = 34.614960, longitude = 38.254372, display_name = ("SAA 550th Brigade")},
    ["Chemical Weapons Storage Area"] = { latitude = 34.627713, longitude = 38.249290, display_name = ("Chemical Weapons Storage Area")},
    ["SAA 165th Brigade"] = { latitude = 33.283835, longitude = 36.255758, display_name = ("SAA 165th Brigade")},
    ["Army Vehicle Training Ground"] = { latitude = 33.365284, longitude = 36.322571, display_name = ("Army Vehicle Training Ground")},
    ["SAA 1st Armoured Division"] = { latitude = 33.352760, longitude = 36.296204, display_name = ("SAA 1st Armoured Division")},
    ["SyADF 150th Regiment"] = { latitude = 33.251611, longitude = 36.269702, display_name = ("SyADF 150th Regiment")},
    ["SAA 121th Brigade"] = { latitude = 33.278919, longitude = 36.082934, display_name = ("SAA 121th Brigade")},
    ["Army Vehicle Training Ground"] = { latitude = 33.433665, longitude = 36.124922, display_name = ("Army Vehicle Training Ground")},
    ["Artillery Base"] = { latitude = 33.457854, longitude = 36.128248, display_name = ("Artillery Base")},
    ["SAA 155th Artillery Regiment"] = { latitude = 33.702711, longitude = 36.526507, display_name = ("SAA 155th Artillery Regiment")},
    ["SAA 128th Brigade"] = { latitude = 33.971493, longitude = 36.898243, display_name = ("SAA 128th Brigade")},
    ["Al Safira Military Base"] = { latitude = 36.046375, longitude = 37.334725, display_name = ("Al Safira Military Base")},
    ["Military Research Base"] = { latitude = 35.983058, longitude = 37.403046, display_name = ("Military Research Base")},
    ["Durayhim Military Base"] = { latitude = 35.790074, longitude = 37.728073, display_name = ("Durayhim Military Base")},
    ["SAA 156th Brigade"] = { latitude = 33.667855, longitude = 36.735378, display_name = ("SAA 156th Brigade")},
    ["Air Defence Academy"] = { latitude = 34.612370, longitude = 36.750060, display_name = ("Air Defence Academy")},
    ["SAA 4th Armoured Division"] = { latitude = 33.506039, longitude = 36.201757, display_name = ("SAA 4th Armoured Division")},
    ["Army Vehicle Training Ground"] = { latitude = 33.737184, longitude = 36.809278, display_name = ("Army Vehicle Training Ground")},
    ["SAA 20th Brigade"] = { latitude = 33.773650, longitude = 36.718297, display_name = ("SAA 20th Brigade")},
    ["Army Vehicle Training Ground"] = { latitude = 33.710700, longitude = 36.665598, display_name = ("Army Vehicle Training Ground")},
    ["SyADF 159th Regiment"] = { latitude = 33.177072, longitude = 36.577899, display_name = ("SyADF 159th Regiment")},
    ["SAA 89th Brigade"] = { latitude = 33.146138, longitude = 36.284818, display_name = ("SAA 89th Brigade")},
    ["SAA 7th Division"] = { latitude = 33.456229, longitude = 36.083628, display_name = ("SAA 7th Division")},
    ["Army Training Ground"] = { latitude = 33.405091, longitude = 36.319119, display_name = ("Army Training Ground")},
}

veafNamedPoints._citiesMarianasIslands =
{
    ["San Jose"] = { latitude = 14.965154, longitude = 145.628466, display_name = ("San Jose")},
    ["Dandan"] = { latitude = 15.133948, longitude = 145.735471, display_name = ("Dandan")},
    ["Koblerville"] = { latitude = 15.122667, longitude = 145.703809, display_name = ("Koblerville")},
    ["San Vicente"] = { latitude = 15.154882, longitude = 145.739831, display_name = ("San Vicente")},
    ["Chalan Kiya"] = { latitude = 15.165205, longitude = 145.720938, display_name = ("Chalan Kiya")},
    ["San Jose"] = { latitude = 15.167064, longitude = 145.710962, display_name = ("San Jose")},
    ["Garapan"] = { latitude = 15.206861, longitude = 145.721351, display_name = ("Garapan")},
    ["Papago"] = { latitude = 15.179258, longitude = 145.752613, display_name = ("Papago")},
    ["Kagman I"] = { latitude = 15.181232, longitude = 145.773957, display_name = ("Kagman I")},
    ["Tanapag"] = { latitude = 15.234217, longitude = 145.747291, display_name = ("Tanapag")},
    ["Puerto Rico"] = { latitude = 15.223099, longitude = 145.735812, display_name = ("Puerto Rico")},
    ["San Roque"] = { latitude = 15.248449, longitude = 145.776229, display_name = ("San Roque")},
    ["Songsong"] = { latitude = 14.139157, longitude = 145.139697, display_name = ("Songsong")},
    ["Tuchok"] = { latitude = 14.136584, longitude = 145.154358, display_name = ("Tuchok")},
    ["Sinapalo"] = { latitude = 14.166285, longitude = 145.233980, display_name = ("Sinapalo")},
    ["Inarajan"] = { latitude = 13.274048, longitude = 144.746742, display_name = ("Inarajan")},
    ["Pigua"] = { latitude = 13.270406, longitude = 144.671605, display_name = ("Pigua")},
    ["Camp Dealy"] = { latitude = 13.353269, longitude = 144.766200, display_name = ("Camp Dealy")},
    ["Yona"] = { latitude = 13.410619, longitude = 144.775485, display_name = ("Yona")},
    ["Chalan Pago"] = { latitude = 13.442564, longitude = 144.768438, display_name = ("Chalan Pago")},
    ["Afami"] = { latitude = 13.456341, longitude = 144.762765, display_name = ("Afami")},
    ["Hagatna Heights"] = { latitude = 13.464819, longitude = 144.750032, display_name = ("Hagatna Heights")},
    ["Hagatna"] = { latitude = 13.475871, longitude = 144.750834, display_name = ("Hagatna")},
    ["Anigua"] = { latitude = 13.477348, longitude = 144.737995, display_name = ("Anigua")},
    ["Piti"] = { latitude = 13.463047, longitude = 144.692784, display_name = ("Piti")},
    ["North Tipalo"] = { latitude = 13.420384, longitude = 144.644657, display_name = ("North Tipalo")},
    ["Lockwood Terrace"] = { latitude = 13.432602, longitude = 144.649979, display_name = ("Lockwood Terrace")},
    ["Apra Heights"] = { latitude = 13.402864, longitude = 144.684931, display_name = ("Apra Heights")},
    ["Agat"] = { latitude = 13.382019, longitude = 144.659212, display_name = ("Agat")},
    ["Ladai"] = { latitude = 13.365377, longitude = 144.657204, display_name = ("Ladai")},
    ["Umatac"] = { latitude = 13.298629, longitude = 144.665590, display_name = ("Umatac")},
    ["Talofofo"] = { latitude = 13.354023, longitude = 144.756934, display_name = ("Talofofo")},
    ["Mapas"] = { latitude = 13.473612, longitude = 144.774622, display_name = ("Mapas")},
    ["Barrigada"] = { latitude = 13.465346, longitude = 144.797922, display_name = ("Barrigada")},
    ["Asbeco"] = { latitude = 13.466340, longitude = 144.837012, display_name = ("Asbeco")},
    ["Macheche"] = { latitude = 13.503018, longitude = 144.833579, display_name = ("Macheche")},
    ["Corten Torres"] = { latitude = 13.441240, longitude = 144.805211, display_name = ("Corten Torres")},
    ["Adacao"] = { latitude = 13.490890, longitude = 144.852597, display_name = ("Adacao")},
    ["Apurguan"] = { latitude = 13.484178, longitude = 144.775571, display_name = ("Apurguan")},
    ["Tumon"] = { latitude = 13.505412, longitude = 144.812314, display_name = ("Tumon")},
    ["Diidedo"] = { latitude = 13.526543, longitude = 144.845629, display_name = ("Diidedo")},
    ["Ipapao"] = { latitude = 13.524362, longitude = 144.858491, display_name = ("Ipapao")},
    ["Gayinero"] = { latitude = 13.528355, longitude = 144.900124, display_name = ("Gayinero")},
    ["Lupog"] = { latitude = 13.548040, longitude = 144.913590, display_name = ("Lupog")},
    ["Yigo"] = { latitude = 13.539023, longitude = 144.884101, display_name = ("Yigo")},
    [" Astumbo"] = { latitude = 13.554951, longitude = 144.842729, display_name = (" Astumbo")},
    ["San Antonio"] = { latitude = 15.133342, longitude = 145.696097, display_name = ("San Antonio")},
    ["Susupe"] = { latitude = 15.152792, longitude = 145.706059, display_name = ("Susupe")},
    ["Matansa"] = { latitude = 15.251512, longitude = 145.783177, display_name = ("Matansa")},
    ["Kagman II"] = { latitude = 15.176568, longitude = 145.778018, display_name = ("Kagman II")},
    ["Kagman III"] = { latitude = 15.169898, longitude = 145.779899, display_name = ("Kagman III")},
    ["Puetto"] = { latitude = 14.968777, longitude = 145.631754, display_name = ("Puetto")},
    ["Peca"] = { latitude = 13.280913, longitude = 144.752703, display_name = ("Peca")},
    ["Malojloj"] = { latitude = 13.308378, longitude = 144.761342, display_name = ("Malojloj")},
    ["Tres Reyes"] = { latitude = 13.404876, longitude = 144.775087, display_name = ("Tres Reyes")},
    ["Asmisen"] = { latitude = 13.400030, longitude = 144.773871, display_name = ("Asmisen")},
    ["Toto"] = { latitude = 13.466524, longitude = 144.784103, display_name = ("Toto")},
    ["Aspengo"] = { latitude = 13.464835, longitude = 144.798828, display_name = ("Aspengo")},
    ["Asan"] = { latitude = 13.472373, longitude = 144.718320, display_name = ("Asan")},
    ["Tepungan"] = { latitude = 13.467035, longitude = 144.699033, display_name = ("Tepungan")},
    ["Santa Rita"] = { latitude = 13.393936, longitude = 144.662313, display_name = ("Santa Rita")},
    ["Cerain"] = { latitude = 13.387143, longitude = 144.673645, display_name = ("Cerain")},
    ["Rota"] = { latitude = 14.161794, longitude = 145.217911, display_name = ("Rota")},
    ["Saipan"] = { latitude = 15.197839, longitude = 145.748413, display_name = ("Saipan")},
    ["Tinian"] = { latitude = 15.025455, longitude = 145.625337, display_name = ("Tinian")},
    ["Guam"] = { latitude = 13.506871, longitude = 144.824237, display_name = ("Guam")},
    ["Faralon de Medinilla"] = { latitude = 16.019837, longitude = 146.059475, display_name = ("Faralon de Medinilla")},
    ["Aguijan"] = { latitude = 14.852365, longitude = 145.558622, display_name = ("Aguijan")}
}

veafNamedPoints._citiesFalklands =
{
    ["Adventure Harbour"] = { latitude = -52.141347, longitude = -59.128372, display_name = ("Adventure Harbour")},
    ["Adventure Sound"] = { latitude = -52.098386, longitude = -59.026794, display_name = ("Adventure Sound")},
    ["Ajax Bay"] = { latitude = -51.552937, longitude = -59.058947, display_name = ("Ajax Bay")},
    ["Albemarle Harbour"] = { latitude = -52.128746, longitude = -60.555181, display_name = ("Albemarle Harbour")},
    ["Albemarle Sealing Station"] = { latitude = -52.191718, longitude = -60.541186, display_name = ("Albemarle Sealing Station")},
    ["Albemarle Station Farm"] = { latitude = -52.192037, longitude = -60.541448, display_name = ("Albemarle Station Farm")},
    ["Anchor Inlet"] = { latitude = -52.163469, longitude = -60.675087, display_name = ("Anchor Inlet")},
    ["Anthony Creek"] = { latitude = -51.991294, longitude = -60.704667, display_name = ("Anthony Creek")},
    ["Arch Road"] = { latitude = -52.217481, longitude = -60.481882, display_name = ("Arch Road")},
    ["Archer Cove"] = { latitude = -51.900713, longitude = -58.621588, display_name = ("Archer Cove")},
    ["Arrow Harbour"] = { latitude = -51.870955, longitude = -58.938896, display_name = ("Arrow Harbour")},
    ["Arrow Point"] = { latitude = -51.658696, longitude = -57.787496, display_name = ("Arrow Point")},
    ["Arrow Point"] = { latitude = -51.390883, longitude = -60.309993, display_name = ("Arrow Point")},
    ["Bagwell Point"] = { latitude = -52.199496, longitude = -59.037158, display_name = ("Bagwell Point")},
    ["Bald Road"] = { latitude = -51.788896, longitude = -60.934811, display_name = ("Bald Road")},
    ["Barrow Harbour"] = { latitude = -52.137979, longitude = -59.122324, display_name = ("Barrow Harbour")},
    ["Barthe Creek"] = { latitude = -51.569221, longitude = -58.128761, display_name = ("Barthe Creek")},
    ["Bay of Harbours"] = { latitude = -52.201295, longitude = -59.306774, display_name = ("Bay of Harbours")},
    ["Bay Point"] = { latitude = -51.322665, longitude = -59.779232, display_name = ("Bay Point")},
    ["Beach Point"] = { latitude = -51.754544, longitude = -57.967429, display_name = ("Beach Point")},
    ["Beacon Point"] = { latitude = -51.779966, longitude = -60.976640, display_name = ("Beacon Point")},
    ["Beatrice Cove"] = { latitude = -51.652524, longitude = -57.750449, display_name = ("Beatrice Cove")},
    ["Beaver Bay"] = { latitude = -51.822272, longitude = -61.268050, display_name = ("Beaver Bay")},
    ["Beaver Farm"] = { latitude = -51.853341, longitude = -61.253730, display_name = ("Beaver Farm")},
    ["Beaver Harbour"] = { latitude = -51.828630, longitude = -61.232042, display_name = ("Beaver Harbour")},
    ["Bense Harbour"] = { latitude = -51.491511, longitude = -60.502610, display_name = ("Bense Harbour")},
    ["Berkeley Sound"] = { latitude = -51.567041, longitude = -57.920952, display_name = ("Berkeley Sound")},
    ["Black Bog Hill"] = { latitude = -51.350456, longitude = -60.707095, display_name = ("Black Bog Hill")},
    ["Black Point"] = { latitude = -51.332928, longitude = -58.378547, display_name = ("Black Point")},
    ["Black Point"] = { latitude = -52.182544, longitude = -59.644732, display_name = ("Black Point")},
    ["Black Point"] = { latitude = -51.636176, longitude = -57.741265, display_name = ("Black Point")},
    ["Black Rock"] = { latitude = -51.782947, longitude = -58.667048, display_name = ("Black Rock")},
    ["Blanco Bay"] = { latitude = -51.667205, longitude = -57.801819, display_name = ("Blanco Bay")},
    ["Bleaker Farm"] = { latitude = -52.207270, longitude = -58.851203, display_name = ("Bleaker Farm")},
    ["Blue Beach Farm"] = { latitude = -51.573737, longitude = -59.035232, display_name = ("Blue Beach Farm")},
    ["Bluff Cove"] = { latitude = -51.744749, longitude = -58.163363, display_name = ("Bluff Cove")},
    ["Bluff Cove Farm"] = { latitude = -51.747866, longitude = -58.180226, display_name = ("Bluff Cove Farm")},
    ["Bluff Creek"] = { latitude = -51.884591, longitude = -58.738339, display_name = ("Bluff Creek")},
    ["Bluff Head"] = { latitude = -52.082930, longitude = -58.866933, display_name = ("Bluff Head")},
    ["Bluff Point"] = { latitude = -51.346054, longitude = -60.242318, display_name = ("Bluff Point")},
    ["Boat Point"] = { latitude = -51.846095, longitude = -58.215147, display_name = ("Boat Point")},
    ["Bodie Inlet"] = { latitude = -51.845592, longitude = -59.089907, display_name = ("Bodie Inlet")},
    ["Bold Cove"] = { latitude = -51.592340, longitude = -59.449896, display_name = ("Bold Cove")},
    ["Bold Point"] = { latitude = -51.345160, longitude = -59.922759, display_name = ("Bold Point")},
    ["Bold Point"] = { latitude = -52.179212, longitude = -59.406037, display_name = ("Bold Point")},
    ["Bold Point"] = { latitude = -51.631464, longitude = -59.464877, display_name = ("Bold Point")},
    ["Bold Point"] = { latitude = -51.689927, longitude = -61.217581, display_name = ("Bold Point")},
    ["Bold Point"] = { latitude = -51.454087, longitude = -58.455141, display_name = ("Bold Point")},
    ["Bold Point"] = { latitude = -51.672801, longitude = -60.367404, display_name = ("Bold Point")},
    ["Bold Point"] = { latitude = -51.770471, longitude = -58.064755, display_name = ("Bold Point")},
    ["Bonners Bay"] = { latitude = -51.578570, longitude = -59.031708, display_name = ("Bonners Bay")},
    ["Bougainville Creek"] = { latitude = -51.531834, longitude = -58.102505, display_name = ("Bougainville Creek")},
    ["Boulder Point"] = { latitude = -51.876981, longitude = -61.240990, display_name = ("Boulder Point")},
    ["Boundary Farm"] = { latitude = -51.501099, longitude = -60.167983, display_name = ("Boundary Farm")},
    ["Bouy Beach"] = { latitude = -51.621968, longitude = -60.438983, display_name = ("Bouy Beach")},
    ["Boxwood Gulch"] = { latitude = -51.606642, longitude = -60.457182, display_name = ("Boxwood Gulch")},
    ["Boxwood Point"] = { latitude = -51.438688, longitude = -60.598869, display_name = ("Boxwood Point")},
    ["Boxwood Point"] = { latitude = -51.347510, longitude = -60.679375, display_name = ("Boxwood Point")},
    ["Brasse Mar"] = { latitude = -51.582116, longitude = -58.201780, display_name = ("Brasse Mar")},
    ["Brenton Loch"] = { latitude = -51.788436, longitude = -59.011480, display_name = ("Brenton Loch")},
    ["Brett Harbour"] = { latitude = -51.370588, longitude = -60.157485, display_name = ("Brett Harbour")},
    ["Brookfield Farm"] = { latitude = -51.547650, longitude = -58.202158, display_name = ("Brookfield Farm")},
    ["Brown Harbour"] = { latitude = -51.819877, longitude = -60.311422, display_name = ("Brown Harbour")},
    ["Brown Point"] = { latitude = -51.624484, longitude = -60.255375, display_name = ("Brown Point")},
    ["Bull Cove"] = { latitude = -52.341695, longitude = -59.331846, display_name = ("Bull Cove")},
    ["Bull Point"] = { latitude = -52.331261, longitude = -59.305723, display_name = ("Bull Point")},
    ["Bull Point"] = { latitude = -52.116830, longitude = -59.075182, display_name = ("Bull Point")},
    ["Bull Point"] = { latitude = -51.820633, longitude = -58.299768, display_name = ("Bull Point")},
    ["Bull Road"] = { latitude = -52.313220, longitude = -59.350524, display_name = ("Bull Road")},
    ["Burnt Harbour"] = { latitude = -51.408084, longitude = -60.132833, display_name = ("Burnt Harbour")},
    ["Button Bay"] = { latitude = -51.897906, longitude = -58.642911, display_name = ("Button Bay")},
    ["Byron Sound"] = { latitude = -51.312728, longitude = -60.635335, display_name = ("Byron Sound")},
    ["Byron Sound"] = { latitude = -51.434668, longitude = -60.278292, display_name = ("Byron Sound")},
    ["Calf Creek"] = { latitude = -51.953000, longitude = -58.798946, display_name = ("Calf Creek")},
    ["Calm Head"] = { latitude = -52.115282, longitude = -60.927773, display_name = ("Calm Head")},
    ["Camilla Creek"] = { latitude = -51.773596, longitude = -58.958377, display_name = ("Camilla Creek")},
    ["Campa Menta Bay"] = { latitude = -51.381192, longitude = -58.136919, display_name = ("Campa Menta Bay")},
    ["Canard Cove"] = { latitude = -51.546029, longitude = -58.148442, display_name = ("Canard Cove")},
    ["Caneja Creek"] = { latitude = -51.528266, longitude = -58.276632, display_name = ("Caneja Creek")},
    ["Caneja Creek"] = { latitude = -51.852958, longitude = -58.268201, display_name = ("Caneja Creek")},
    ["Cape Bougainville"] = { latitude = -51.297778, longitude = -58.461667, display_name = ("Cape Bougainville")},
    ["Cape Carysfort"] = { latitude = -51.414145, longitude = -57.857702, display_name = ("Cape Carysfort")},
    ["Cape Dolphin"] = { latitude = -51.233056, longitude = -58.965556, display_name = ("Cape Dolphin")},
    ["Cape Dolphin Farm"] = { latitude = -51.340202, longitude = -58.841209, display_name = ("Cape Dolphin Farm")},
    ["Cape Frehel"] = { latitude = -51.383332, longitude = -58.232592, display_name = ("Cape Frehel")},
    ["Cape Meredith"] = { latitude = -52.256569, longitude = -60.647004, display_name = ("Cape Meredith")},
    ["Cape Orford"] = { latitude = -52.012676, longitude = -61.070166, display_name = ("Cape Orford")},
    ["Cape Pembroke"] = { latitude = -51.682622, longitude = -57.719467, display_name = ("Cape Pembroke")},
    ["Cape Percival"] = { latitude = -51.829890, longitude = -61.342871, display_name = ("Cape Percival")},
    ["Cape Split"] = { latitude = -51.808953, longitude = -61.321392, display_name = ("Cape Split")},
    ["Cape Tamar"] = { latitude = -51.279334, longitude = -59.496009, display_name = ("Cape Tamar")},
    ["Cape Terrible"] = { latitude = -51.328255, longitude = -60.729069, display_name = ("Cape Terrible")},
    ["Carcass Bay"] = { latitude = -51.969211, longitude = -59.910014, display_name = ("Carcass Bay")},
    ["Carcass Farm"] = { latitude = -51.290598, longitude = -60.555728, display_name = ("Carcass Farm")},
    ["Careening Cove"] = { latitude = -51.511908, longitude = -58.968086, display_name = ("Careening Cove")},
    ["Carew Harbour"] = { latitude = -52.002942, longitude = -60.675095, display_name = ("Carew Harbour")},
    ["Carruther Point"] = { latitude = -51.356703, longitude = -59.949984, display_name = ("Carruther Point")},
    ["Cat Cove"] = { latitude = -51.339848, longitude = -60.692468, display_name = ("Cat Cove")},
    ["Cattle Point"] = { latitude = -51.797170, longitude = -58.117697, display_name = ("Cattle Point")},
    ["Cattle Point"] = { latitude = -52.180656, longitude = -59.316613, display_name = ("Cattle Point")},
    ["Chabot Creek"] = { latitude = -51.499486, longitude = -58.052144, display_name = ("Chabot Creek")},
    ["Chabot Point"] = { latitude = -51.521119, longitude = -58.045393, display_name = ("Chabot Point")},
    ["Chaffers Gullet"] = { latitude = -52.128539, longitude = -60.381927, display_name = ("Chaffers Gullet")},
    ["Chancho Point"] = { latitude = -51.497271, longitude = -59.127860, display_name = ("Chancho Point")},
    ["Channel Point"] = { latitude = -51.408586, longitude = -60.080028, display_name = ("Channel Point")},
    ["Charles Point"] = { latitude = -51.657462, longitude = -57.773559, display_name = ("Charles Point")},
    ["Charles Point"] = { latitude = -51.654294, longitude = -57.774345, display_name = ("Charles Point")},
    ["Chatham Harbour"] = { latitude = -51.844408, longitude = -60.968499, display_name = ("Chatham Harbour")},
    ["Choiseul Sound"] = { latitude = -51.919920, longitude = -58.667164, display_name = ("Choiseul Sound")},
    ["Christina Bay"] = { latitude = -51.689036, longitude = -57.725301, display_name = ("Christina Bay")},
    ["Christmas Harbour"] = { latitude = -51.676497, longitude = -60.175563, display_name = ("Christmas Harbour")},
    ["Cinnamon Valley"] = { latitude = -51.615123, longitude = -60.439326, display_name = ("Cinnamon Valley")},
    ["Cliff Point"] = { latitude = -51.304943, longitude = -60.087286, display_name = ("Cliff Point")},
    ["Cliff Point"] = { latitude = -52.009645, longitude = -58.521316, display_name = ("Cliff Point")},
    ["Committee Bay"] = { latitude = -51.329808, longitude = -59.928579, display_name = ("Committee Bay")},
    ["Concordia Bay"] = { latitude = -51.308316, longitude = -58.603670, display_name = ("Concordia Bay")},
    ["Cow Bay"] = { latitude = -51.434360, longitude = -57.860742, display_name = ("Cow Bay")},
    ["Cow Point"] = { latitude = -52.222534, longitude = -59.222787, display_name = ("Cow Point")},
    ["Cracker Point"] = { latitude = -51.623180, longitude = -60.446547, display_name = ("Cracker Point")},
    ["Creek Point"] = { latitude = -51.391317, longitude = -59.861594, display_name = ("Creek Point")},
    ["Crooked Inlet"] = { latitude = -51.547676, longitude = -60.317216, display_name = ("Crooked Inlet")},
    ["Crooked Inlet Farm"] = { latitude = -51.584687, longitude = -60.211575, display_name = ("Crooked Inlet Farm")},
    ["Cutter Cove"] = { latitude = -51.756514, longitude = -59.251089, display_name = ("Cutter Cove")},
    ["Cygnet Harbour"] = { latitude = -51.889092, longitude = -59.485505, display_name = ("Cygnet Harbour")},
    ["Dangerous Point"] = { latitude = -52.004271, longitude = -58.350184, display_name = ("Dangerous Point")},
    ["Danson Harbour"] = { latitude = -52.105342, longitude = -59.640060, display_name = ("Danson Harbour")},
    ["Darwin Harbour"] = { latitude = -51.819754, longitude = -58.949755, display_name = ("Darwin Harbour")},
    ["Deaths Head"] = { latitude = -51.412424, longitude = -60.646141, display_name = ("Deaths Head")},
    ["Dick Point"] = { latitude = -51.798325, longitude = -60.526310, display_name = ("Dick Point")},
    ["Dick Point"] = { latitude = -51.805960, longitude = -60.538446, display_name = ("Dick Point")},
    ["Doctor Point"] = { latitude = -51.662827, longitude = -57.818266, display_name = ("Doctor Point")},
    ["Doctors Head"] = { latitude = -51.517838, longitude = -59.060551, display_name = ("Doctors Head")},
    ["Dog Point"] = { latitude = -51.917862, longitude = -58.887792, display_name = ("Dog Point")},
    ["Don Carlos Bay"] = { latitude = -51.635786, longitude = -57.751898, display_name = ("Don Carlos Bay")},
    ["Double Creek"] = { latitude = -52.007008, longitude = -60.585358, display_name = ("Double Creek")},
    ["Driftwood Point"] = { latitude = -52.257556, longitude = -59.024863, display_name = ("Driftwood Point")},
    ["Duclos Point"] = { latitude = -51.534758, longitude = -58.023005, display_name = ("Duclos Point")},
    ["Dunbar Creek"] = { latitude = -51.408398, longitude = -60.449023, display_name = ("Dunbar Creek")},
    ["Dunbar Farm"] = { latitude = -51.412911, longitude = -60.453126, display_name = ("Dunbar Farm")},
    ["Dunnose Head"] = { latitude = -51.680273, longitude = -60.644741, display_name = ("Dunnose Head")},
    ["Dunnose Head Farm"] = { latitude = -51.756294, longitude = -60.419300, display_name = ("Dunnose Head Farm")},
    ["Duperrey Harbour"] = { latitude = -51.570575, longitude = -58.110766, display_name = ("Duperrey Harbour")},
    ["Dyke Point"] = { latitude = -51.930136, longitude = -61.077890, display_name = ("Dyke Point")},
    ["Eagle Point"] = { latitude = -51.539609, longitude = -57.769933, display_name = ("Eagle Point")},
    ["East Bay"] = { latitude = -51.762900, longitude = -60.244217, display_name = ("East Bay")},
    ["East Branch"] = { latitude = -51.577950, longitude = -60.283935, display_name = ("East Branch")},
    ["East Cove"] = { latitude = -51.906194, longitude = -58.434863, display_name = ("East Cove")},
    ["East Head"] = { latitude = -51.998721, longitude = -60.012603, display_name = ("East Head")},
    ["East Point"] = { latitude = -51.627038, longitude = -60.417544, display_name = ("East Point")},
    ["East Point"] = { latitude = -51.336376, longitude = -60.048802, display_name = ("East Point")},
    ["East Road"] = { latitude = -51.792227, longitude = -58.089330, display_name = ("East Road")},
    ["Eddy Point"] = { latitude = -51.757431, longitude = -61.278680, display_name = ("Eddy Point")},
    ["Egg Harbour"] = { latitude = -51.838668, longitude = -59.384837, display_name = ("Egg Harbour")},
    ["Elephant Bay"] = { latitude = -52.338976, longitude = -59.770069, display_name = ("Elephant Bay")},
    ["Elephant Bay"] = { latitude = -51.295372, longitude = -59.569987, display_name = ("Elephant Bay")},
    ["Elephant Beach Farm"] = { latitude = -51.386471, longitude = -58.761120, display_name = ("Elephant Beach Farm")},
    ["Elephant Cove"] = { latitude = -51.801556, longitude = -60.919018, display_name = ("Elephant Cove")},
    ["Elephant Island"] = { latitude = -51.876758, longitude = -58.291741, display_name = ("Elephant Island")},
    ["Elephant Point"] = { latitude = -51.264560, longitude = -60.321271, display_name = ("Elephant Point")},
    ["Elephant Point"] = { latitude = -51.884396, longitude = -58.302410, display_name = ("Elephant Point")},
    ["Eliza Cove"] = { latitude = -51.733162, longitude = -57.832224, display_name = ("Eliza Cove")},
    ["Enderby Point"] = { latitude = -52.027125, longitude = -58.550929, display_name = ("Enderby Point")},
    ["Engineer Point"] = { latitude = -51.682155, longitude = -57.832434, display_name = ("Engineer Point")},
    ["Entrance Cove"] = { latitude = -51.958204, longitude = -60.582047, display_name = ("Entrance Cove")},
    ["Estancia Farm"] = { latitude = -51.656414, longitude = -58.158132, display_name = ("Estancia Farm")},
    ["Evelyn Station Farm"] = { latitude = -51.557936, longitude = -58.438389, display_name = ("Evelyn Station Farm")},
    ["Fairy Cove"] = { latitude = -51.682310, longitude = -57.881263, display_name = ("Fairy Cove")},
    ["Fall Point"] = { latitude = -51.355221, longitude = -59.930109, display_name = ("Fall Point")},
    ["False Creek"] = { latitude = -51.947274, longitude = -58.720797, display_name = ("False Creek")},
    ["Fanning Harbour"] = { latitude = -51.476264, longitude = -59.087048, display_name = ("Fanning Harbour")},
    ["Fanning Head"] = { latitude = -51.468689, longitude = -59.133956, display_name = ("Fanning Head")},
    ["Fanny Road"] = { latitude = -52.228709, longitude = -59.355979, display_name = ("Fanny Road")},
    ["Fegen Inlet"] = { latitude = -52.069906, longitude = -60.877425, display_name = ("Fegen Inlet")},
    ["Fern Valley Creek"] = { latitude = -51.559577, longitude = -59.032180, display_name = ("Fern Valley Creek")},
    ["Findlay Creek"] = { latitude = -51.865220, longitude = -59.007858, display_name = ("Findlay Creek")},
    ["Findlay Harbour"] = { latitude = -51.991712, longitude = -59.610271, display_name = ("Findlay Harbour")},
    ["First Sand Grass Bay"] = { latitude = -51.410798, longitude = -58.287214, display_name = ("First Sand Grass Bay")},
    ["Fish Creek"] = { latitude = -52.033380, longitude = -60.266662, display_name = ("Fish Creek")},
    ["Fish Creek"] = { latitude = -51.851089, longitude = -61.238351, display_name = ("Fish Creek")},
    ["Fitz Cove"] = { latitude = -51.757362, longitude = -58.184152, display_name = ("Fitz Cove")},
    ["Fitzroy Basin"] = { latitude = -51.748116, longitude = -58.163295, display_name = ("Fitzroy Basin")},
    ["Fitzroy Creek"] = { latitude = -51.801795, longitude = -58.233783, display_name = ("Fitzroy Creek")},
    ["Flores Harbour"] = { latitude = -52.221937, longitude = -59.589586, display_name = ("Flores Harbour")},
    ["Foot Point"] = { latitude = -51.486594, longitude = -60.156605, display_name = ("Foot Point")},
    ["Foul Bay"] = { latitude = -51.356882, longitude = -58.907793, display_name = ("Foul Bay")},
    ["Fox Bay"] = { latitude = -51.980544, longitude = -60.054567, display_name = ("Fox Bay")},
    ["Fox Harbour"] = { latitude = -52.090174, longitude = -59.113212, display_name = ("Fox Harbour")},
    ["Fox Point"] = { latitude = -51.921434, longitude = -58.404849, display_name = ("Fox Point")},
    ["Fox Point West"] = { latitude = -51.484590, longitude = -60.066399, display_name = ("Fox Point West")},
    ["French Harbour"] = { latitude = -51.851182, longitude = -61.102910, display_name = ("French Harbour")},
    ["Garden Point"] = { latitude = -51.765499, longitude = -58.199429, display_name = ("Garden Point")},
    ["Garibaldi Bay"] = { latitude = -51.923011, longitude = -60.884712, display_name = ("Garibaldi Bay")},
    ["Garibaldi Point"] = { latitude = -51.917258, longitude = -60.872606, display_name = ("Garibaldi Point")},
    ["Gascoigne Point"] = { latitude = -51.336667, longitude = -59.920724, display_name = ("Gascoigne Point")},
    ["George Island Farm"] = { latitude = -52.354310, longitude = -59.750933, display_name = ("George Island Farm")},
    ["Gibraltar Reef"] = { latitude = -51.297752, longitude = -60.841599, display_name = ("Gibraltar Reef")},
    ["Gibraltar Station Farm"] = { latitude = -51.435519, longitude = -58.372301, display_name = ("Gibraltar Station Farm")},
    ["Gladys Cove"] = { latitude = -51.964385, longitude = -60.767355, display_name = ("Gladys Cove")},
    ["Glen Point"] = { latitude = -51.344044, longitude = -59.988903, display_name = ("Glen Point")},
    ["Goat Point"] = { latitude = -51.419880, longitude = -58.295414, display_name = ("Goat Point")},
    ["Golding Bay"] = { latitude = -51.337682, longitude = -59.800150, display_name = ("Golding Bay")},
    ["Golding Island Farm"] = { latitude = -51.365733, longitude = -59.688851, display_name = ("Golding Island Farm")},
    ["Goose Green Farm"] = { latitude = -51.827197, longitude = -58.973479, display_name = ("Goose Green Farm")},
    ["Gosling Creek"] = { latitude = -51.995770, longitude = -58.483548, display_name = ("Gosling Creek")},
    ["Grantham Sound"] = { latitude = -51.662760, longitude = -59.094429, display_name = ("Grantham Sound")},
    ["Grave Cove"] = { latitude = -51.363813, longitude = -60.632306, display_name = ("Grave Cove")},
    ["Grave Point"] = { latitude = -51.534778, longitude = -58.022050, display_name = ("Grave Point")},
    ["Green Rincon"] = { latitude = -51.259715, longitude = -59.730316, display_name = ("Green Rincon")},
    ["Greenfield Farm"] = { latitude = -51.566702, longitude = -58.913984, display_name = ("Greenfield Farm")},
    ["Gull Harbour"] = { latitude = -51.899290, longitude = -60.891627, display_name = ("Gull Harbour")},
    ["Gull Point"] = { latitude = -51.902582, longitude = -60.871708, display_name = ("Gull Point")},
    ["Gull Point"] = { latitude = -51.305412, longitude = -60.041420, display_name = ("Gull Point")},
    ["Gypsy Cove"] = { latitude = -51.675343, longitude = -57.807646, display_name = ("Gypsy Cove")},
    ["Hadassa Bay"] = { latitude = -51.679617, longitude = -57.822095, display_name = ("Hadassa Bay")},
    ["Halfway Cove"] = { latitude = -51.747143, longitude = -60.409237, display_name = ("Halfway Cove")},
    ["Halfway Cove"] = { latitude = -52.213418, longitude = -59.688356, display_name = ("Halfway Cove")},
    ["Hamblin Cove"] = { latitude = -51.665914, longitude = -57.829628, display_name = ("Hamblin Cove")},
    ["Hamond Cove"] = { latitude = -51.970619, longitude = -58.619442, display_name = ("Hamond Cove")},
    ["Head of the Bay Creek"] = { latitude = -51.595324, longitude = -59.036449, display_name = ("Head of the Bay Creek")},
    ["Head of the Bay Farm"] = { latitude = -51.601000, longitude = -59.028733, display_name = ("Head of the Bay Farm")},
    ["Hearnden Water"] = { latitude = -51.666680, longitude = -57.870111, display_name = ("Hearnden Water")},
    ["Hells Kitchen"] = { latitude = -51.649646, longitude = -57.771212, display_name = ("Hells Kitchen")},
    ["High Bluff Cape"] = { latitude = -52.077307, longitude = -61.019633, display_name = ("High Bluff Cape")},
    ["Hill Cove"] = { latitude = -51.499126, longitude = -60.100880, display_name = ("Hill Cove")},
    ["Hill Gap"] = { latitude = -51.808568, longitude = -59.753351, display_name = ("Hill Gap")},
    ["Home Farm"] = { latitude = -51.459496, longitude = -58.608190, display_name = ("Home Farm")},
    ["Hookers Point"] = { latitude = -51.699380, longitude = -57.776114, display_name = ("Hookers Point")},
    ["Hope Cottage Farm"] = { latitude = -51.476205, longitude = -58.627879, display_name = ("Hope Cottage Farm")},
    ["Hope Harbour"] = { latitude = -51.354444, longitude = -60.635519, display_name = ("Hope Harbour")},
    ["Hope Place"] = { latitude = -51.845482, longitude = -59.018050, display_name = ("Hope Place")},
    ["Hope Point"] = { latitude = -51.340919, longitude = -60.663009, display_name = ("Hope Point")},
    ["Hope Reef"] = { latitude = -51.188422, longitude = -60.748644, display_name = ("Hope Reef")},
    ["Horse Point"] = { latitude = -51.720542, longitude = -57.825481, display_name = ("Horse Point")},
    ["Horseshoe Bay"] = { latitude = -51.501874, longitude = -58.295734, display_name = ("Horseshoe Bay")},
    ["Horseshoe Bay Farm"] = { latitude = -51.500600, longitude = -58.282186, display_name = ("Horseshoe Bay Farm")},
    ["Hospital Point"] = { latitude = -51.506539, longitude = -59.040613, display_name = ("Hospital Point")},
    ["Hoste Inlet"] = { latitude = -52.122396, longitude = -60.719805, display_name = ("Hoste Inlet")},
    ["House Bay"] = { latitude = -51.615731, longitude = -60.430427, display_name = ("House Bay")},
    ["House Cove"] = { latitude = -51.979270, longitude = -60.834174, display_name = ("House Cove")},
    ["Hummock Point"] = { latitude = -51.369627, longitude = -59.661486, display_name = ("Hummock Point")},
    ["Hut Point"] = { latitude = -51.391855, longitude = -58.281720, display_name = ("Hut Point")},
    ["Inlet Point"] = { latitude = -52.032347, longitude = -60.896101, display_name = ("Inlet Point")},
    ["Island Creek"] = { latitude = -51.966375, longitude = -58.690896, display_name = ("Island Creek")},
    ["Island Harbour"] = { latitude = -51.826817, longitude = -58.328615, display_name = ("Island Harbour")},
    ["Isthmus Cove"] = { latitude = -51.702373, longitude = -60.318353, display_name = ("Isthmus Cove")},
    ["Jenesta Point"] = { latitude = -51.263972, longitude = -59.604113, display_name = ("Jenesta Point")},
    ["Jersey Harbour"] = { latitude = -51.460572, longitude = -59.281497, display_name = ("Jersey Harbour")},
    ["Jersey Point"] = { latitude = -51.437514, longitude = -59.203734, display_name = ("Jersey Point")},
    ["John Point"] = { latitude = -51.878341, longitude = -58.810311, display_name = ("John Point")},
    ["Johnson Harbour"] = { latitude = -51.518490, longitude = -58.014982, display_name = ("Johnson Harbour")},
    ["Justice Inlet"] = { latitude = -51.317746, longitude = -59.933861, display_name = ("Justice Inlet")},
    ["Kelp Bay"] = { latitude = -51.997446, longitude = -58.502669, display_name = ("Kelp Bay")},
    ["Kelp Creek"] = { latitude = -51.878802, longitude = -61.035726, display_name = ("Kelp Creek")},
    ["Kelp Harbour"] = { latitude = -51.783958, longitude = -59.314842, display_name = ("Kelp Harbour")},
    ["Kelp Point"] = { latitude = -51.968219, longitude = -60.039339, display_name = ("Kelp Point")},
    ["Kelp Point"] = { latitude = -51.860206, longitude = -58.214683, display_name = ("Kelp Point")},
    ["Keppel Sound"] = { latitude = -51.275503, longitude = -59.914230, display_name = ("Keppel Sound")},
    ["Kidney Cove"] = { latitude = -51.629979, longitude = -57.751222, display_name = ("Kidney Cove")},
    ["King George Bay"] = { latitude = -51.639195, longitude = -60.487091, display_name = ("King George Bay")},
    ["Kingsford Valley Farm"] = { latitude = -51.575244, longitude = -59.030547, display_name = ("Kingsford Valley Farm")},
    ["Kits Creek"] = { latitude = -52.181281, longitude = -60.646999, display_name = ("Kits Creek")},
    ["Knob Point"] = { latitude = -51.990574, longitude = -58.604448, display_name = ("Knob Point")},
    ["Lafonia"] = { latitude = -51.963629, longitude = -59.269508, display_name = ("Lafonia")},
    ["Laguna Algas"] = { latitude = -52.338435, longitude = -59.374844, display_name = ("Laguna Algas")},
    ["Lake Point"] = { latitude = -51.723313, longitude = -57.855957, display_name = ("Lake Point")},
    ["Lamarche Point"] = { latitude = -51.529779, longitude = -57.977386, display_name = ("Lamarche Point")},
    ["Lancaster Point"] = { latitude = -51.320139, longitude = -59.916086, display_name = ("Lancaster Point")},
    ["Landsend Bluff"] = { latitude = -51.692622, longitude = -61.332394, display_name = ("Landsend Bluff")},
    ["Lebon Creek"] = { latitude = -51.567057, longitude = -58.115262, display_name = ("Lebon Creek")},
    ["Leicester Creek"] = { latitude = -51.945932, longitude = -60.319490, display_name = ("Leicester Creek")},
    ["Leicester Creek Farm"] = { latitude = -51.894767, longitude = -60.282105, display_name = ("Leicester Creek Farm")},
    ["Letterbox Point"] = { latitude = -51.388649, longitude = -60.045455, display_name = ("Letterbox Point")},
    ["Limpet Creek"] = { latitude = -51.349780, longitude = -58.605237, display_name = ("Limpet Creek")},
    ["Lion Point"] = { latitude = -51.332123, longitude = -60.710610, display_name = ("Lion Point")},
    ["Lion Point"] = { latitude = -51.366640, longitude = -59.833243, display_name = ("Lion Point")},
    ["Lion Point"] = { latitude = -51.300788, longitude = -58.582513, display_name = ("Lion Point")},
    ["Lion Point"] = { latitude = -51.418767, longitude = -60.409144, display_name = ("Lion Point")},
    ["Lively Farm"] = { latitude = -51.994056, longitude = -58.464810, display_name = ("Lively Farm")},
    ["Long Creek"] = { latitude = -52.013724, longitude = -58.519495, display_name = ("Long Creek")},
    ["Long Island Farm"] = { latitude = -51.576691, longitude = -58.072443, display_name = ("Long Island Farm")},
    ["Loop Head"] = { latitude = -51.763743, longitude = -60.898901, display_name = ("Loop Head")},
    ["Lorenzo Farm"] = { latitude = -51.458481, longitude = -58.610466, display_name = ("Lorenzo Farm")},
    ["Low Bay"] = { latitude = -52.070327, longitude = -58.799429, display_name = ("Low Bay")},
    ["Low Bay"] = { latitude = -52.087312, longitude = -58.924913, display_name = ("Low Bay")},
    ["Low Point"] = { latitude = -51.953245, longitude = -58.699222, display_name = ("Low Point")},
    ["Low Point"] = { latitude = -51.682924, longitude = -60.135142, display_name = ("Low Point")},
    ["Low Point"] = { latitude = -52.080310, longitude = -59.100845, display_name = ("Low Point")},
    ["Lucas Bay"] = { latitude = -52.161806, longitude = -60.428802, display_name = ("Lucas Bay")},
    ["Lucas Point"] = { latitude = -52.170535, longitude = -60.371744, display_name = ("Lucas Point")},
    ["MacBride Head"] = { latitude = -51.363595, longitude = -57.947688, display_name = ("MacBride Head")},
    ["Mackinnon Creek"] = { latitude = -51.888239, longitude = -58.642407, display_name = ("Mackinnon Creek")},
    ["Magellan Cove"] = { latitude = -51.507884, longitude = -58.000527, display_name = ("Magellan Cove")},
    ["Magellan Point"] = { latitude = -51.508729, longitude = -58.017204, display_name = ("Magellan Point")},
    ["Main Point"] = { latitude = -51.423793, longitude = -59.733845, display_name = ("Main Point")},
    ["Main Point Farm"] = { latitude = -51.435452, longitude = -59.910888, display_name = ("Main Point Farm")},
    ["Malo Creek"] = { latitude = -51.988144, longitude = -60.200014, display_name = ("Malo Creek")},
    ["Mare Harbour"] = { latitude = -51.899019, longitude = -58.499322, display_name = ("Mare Harbour")},
    ["Mare Rincon"] = { latitude = -51.788602, longitude = -60.556644, display_name = ("Mare Rincon")},
    ["Mark Point"] = { latitude = -51.889140, longitude = -60.873914, display_name = ("Mark Point")},
    ["Marville Bay"] = { latitude = -51.381511, longitude = -58.190567, display_name = ("Marville Bay")},
    ["McGill Shoals"] = { latitude = -51.215896, longitude = -60.548830, display_name = ("McGill Shoals")},
    ["Mengeary Point"] = { latitude = -51.644166, longitude = -57.729877, display_name = ("Mengeary Point")},
    ["Middle Bay"] = { latitude = -51.414759, longitude = -59.037867, display_name = ("Middle Bay")},
    ["Middle Creek"] = { latitude = -51.523077, longitude = -58.273340, display_name = ("Middle Creek")},
    ["Middle Point"] = { latitude = -51.386296, longitude = -59.004149, display_name = ("Middle Point")},
    ["Miles Creek"] = { latitude = -51.926883, longitude = -58.855010, display_name = ("Miles Creek")},
    ["Misery Valley"] = { latitude = -51.370012, longitude = -60.701308, display_name = ("Misery Valley")},
    ["Moffit Bay"] = { latitude = -52.229469, longitude = -59.045205, display_name = ("Moffit Bay")},
    ["Moffitt Harbour"] = { latitude = -52.086561, longitude = -59.608255, display_name = ("Moffitt Harbour")},
    ["Monkey Point"] = { latitude = -51.539112, longitude = -57.956153, display_name = ("Monkey Point")},
    ["Moss Side Farm"] = { latitude = -51.480737, longitude = -58.921770, display_name = ("Moss Side Farm")},
    ["Mound Point"] = { latitude = -51.911615, longitude = -60.498308, display_name = ("Mound Point")},
    ["Mound Point"] = { latitude = -51.965423, longitude = -60.838165, display_name = ("Mound Point")},
    ["Mount Kent Farm"] = { latitude = -51.558594, longitude = -58.133369, display_name = ("Mount Kent Farm")},
    ["Muddy Creek"] = { latitude = -51.404714, longitude = -58.550541, display_name = ("Muddy Creek")},
    ["Murrell Farm"] = { latitude = -51.649688, longitude = -57.915065, display_name = ("Murrell Farm")},
    ["Narrow Creek"] = { latitude = -51.984336, longitude = -60.630289, display_name = ("Narrow Creek")},
    ["New Haven"] = { latitude = -51.728040, longitude = -59.213422, display_name = ("New Haven")},
    ["New Haven"] = { latitude = -51.730937, longitude = -59.206936, display_name = ("New Haven")},
    ["New Year Cove"] = { latitude = -51.941506, longitude = -60.902967, display_name = ("New Year Cove")},
    ["North Arm"] = { latitude = -52.132566, longitude = -59.366813, display_name = ("North Arm")},
    ["North Arm Farm"] = { latitude = -52.123686, longitude = -59.367667, display_name = ("North Arm Farm")},
    ["North Basin"] = { latitude = -51.739801, longitude = -58.059073, display_name = ("North Basin")},
    ["North Bluff"] = { latitude = -51.676951, longitude = -61.263334, display_name = ("North Bluff")},
    ["North Creek"] = { latitude = -51.504976, longitude = -58.298178, display_name = ("North Creek")},
    ["North East Creek"] = { latitude = -51.536379, longitude = -58.228826, display_name = ("North East Creek")},
    ["North East Point"] = { latitude = -51.788059, longitude = -58.110879, display_name = ("North East Point")},
    ["North Harbour"] = { latitude = -51.704812, longitude = -61.239800, display_name = ("North Harbour")},
    ["North Head"] = { latitude = -52.013191, longitude = -60.236063, display_name = ("North Head")},
    ["North Point"] = { latitude = -51.284847, longitude = -59.959585, display_name = ("North Point")},
    ["North Point"] = { latitude = -52.133682, longitude = -58.826962, display_name = ("North Point")},
    ["North West Bay"] = { latitude = -51.726630, longitude = -60.351934, display_name = ("North West Bay")},
    ["North West Point"] = { latitude = -51.246296, longitude = -60.600007, display_name = ("North West Point")},
    ["North West Rincon"] = { latitude = -51.584008, longitude = -59.174866, display_name = ("North West Rincon")},
    ["Northwest Bay"] = { latitude = -51.607022, longitude = -60.446616, display_name = ("Northwest Bay")},
    ["Norton Inlet"] = { latitude = -51.848392, longitude = -58.852891, display_name = ("Norton Inlet")},
    ["Old House Creek"] = { latitude = -51.532889, longitude = -59.054260, display_name = ("Old House Creek")},
    ["Ordnance Point"] = { latitude = -51.682707, longitude = -57.826334, display_name = ("Ordnance Point")},
    ["Pea Point"] = { latitude = -52.184780, longitude = -60.721951, display_name = ("Pea Point")},
    ["Peat Bog Point"] = { latitude = -52.359314, longitude = -59.811794, display_name = ("Peat Bog Point")},
    ["Pebble Cove"] = { latitude = -51.258457, longitude = -59.826415, display_name = ("Pebble Cove")},
    ["Pebble Sound"] = { latitude = -51.357472, longitude = -59.512424, display_name = ("Pebble Sound")},
    ["Pebbly Bay"] = { latitude = -52.211735, longitude = -58.847901, display_name = ("Pebbly Bay")},
    ["Penarrow Point"] = { latitude = -51.390205, longitude = -60.312211, display_name = ("Penarrow Point")},
    ["Penarrow Point"] = { latitude = -51.684214, longitude = -57.866423, display_name = ("Penarrow Point")},
    ["Penguin Cove"] = { latitude = -51.961060, longitude = -60.933952, display_name = ("Penguin Cove")},
    ["Penguin Point"] = { latitude = -52.017793, longitude = -58.445828, display_name = ("Penguin Point")},
    ["Penguin Point"] = { latitude = -51.378005, longitude = -60.643930, display_name = ("Penguin Point")},
    ["Penguin Point"] = { latitude = -51.925935, longitude = -60.645309, display_name = ("Penguin Point")},
    ["Petrel City"] = { latitude = -51.610911, longitude = -60.455966, display_name = ("Petrel City")},
    ["Phillips Bay"] = { latitude = -52.169471, longitude = -59.762535, display_name = ("Phillips Bay")},
    ["Phillips Point"] = { latitude = -51.412471, longitude = -58.531530, display_name = ("Phillips Point")},
    ["Phillips Point"] = { latitude = -52.141146, longitude = -59.779412, display_name = ("Phillips Point")},
    ["Phillips Point"] = { latitude = -51.717188, longitude = -57.815852, display_name = ("Phillips Point")},
    ["Pickthorne Point"] = { latitude = -51.491082, longitude = -60.482486, display_name = ("Pickthorne Point")},
    ["Pillar Bluff"] = { latitude = -51.929733, longitude = -61.096022, display_name = ("Pillar Bluff")},
    ["Pillar Cove"] = { latitude = -51.919742, longitude = -61.080251, display_name = ("Pillar Cove")},
    ["Pitt Creek"] = { latitude = -51.835184, longitude = -61.057570, display_name = ("Pitt Creek")},
    ["Platt Point"] = { latitude = -51.485101, longitude = -58.371542, display_name = ("Platt Point")},
    ["Platt Point Rincon"] = { latitude = -51.483051, longitude = -58.362437, display_name = ("Platt Point Rincon")},
    ["Plaza Creek"] = { latitude = -51.389593, longitude = -58.503270, display_name = ("Plaza Creek")},
    ["Pleasant Point"] = { latitude = -51.816171, longitude = -58.166513, display_name = ("Pleasant Point")},
    ["Pleasant Road"] = { latitude = -51.834253, longitude = -58.220286, display_name = ("Pleasant Road")},
    ["Point Frances"] = { latitude = -51.306076, longitude = -59.926413, display_name = ("Point Frances")},
    ["Point Frio"] = { latitude = -51.469817, longitude = -58.438331, display_name = ("Point Frio")},
    ["Poke Point"] = { latitude = -51.603985, longitude = -59.395480, display_name = ("Poke Point")},
    ["Poker Point"] = { latitude = -51.572567, longitude = -59.065837, display_name = ("Poker Point")},
    ["Pond Bay"] = { latitude = -51.874822, longitude = -60.455360, display_name = ("Pond Bay")},
    ["Porpoise Cove"] = { latitude = -51.963995, longitude = -60.740833, display_name = ("Porpoise Cove")},
    ["Porpoise Point"] = { latitude = -52.349135, longitude = -59.310699, display_name = ("Porpoise Point")},
    ["Port Albemarle"] = { latitude = -52.189141, longitude = -60.433045, display_name = ("Port Albemarle")},
    ["Port Edgar"] = { latitude = -52.007587, longitude = -60.258315, display_name = ("Port Edgar")},
    ["Port Edgar Farm"] = { latitude = -52.049719, longitude = -60.280796, display_name = ("Port Edgar Farm")},
    ["Port Egmont"] = { latitude = -51.357740, longitude = -60.079165, display_name = ("Port Egmont")},
    ["Port Egmont"] = { latitude = -51.358531, longitude = -60.028546, display_name = ("Port Egmont")},
    ["Port Fitzroy"] = { latitude = -51.776292, longitude = -58.122988, display_name = ("Port Fitzroy")},
    ["Port Harriet"] = { latitude = -51.731387, longitude = -57.930307, display_name = ("Port Harriet")},
    ["Port Howard"] = { latitude = -51.641454, longitude = -59.549389, display_name = ("Port Howard")},
    ["Port Howard Farm"] = { latitude = -51.611011, longitude = -59.520974, display_name = ("Port Howard Farm")},
    ["Port King"] = { latitude = -51.911053, longitude = -59.538088, display_name = ("Port King")},
    ["Port Louis Farm"] = { latitude = -51.529193, longitude = -58.128300, display_name = ("Port Louis Farm")},
    ["Port Louis Harbour"] = { latitude = -51.530323, longitude = -58.121733, display_name = ("Port Louis Harbour")},
    ["Port North"] = { latitude = -51.471765, longitude = -60.449009, display_name = ("Port North")},
    ["Port North Farm"] = { latitude = -51.487490, longitude = -60.354387, display_name = ("Port North Farm")},
    ["Port Philomel"] = { latitude = -51.733673, longitude = -60.280781, display_name = ("Port Philomel")},
    ["Port Pleasant"] = { latitude = -51.776405, longitude = -58.178043, display_name = ("Port Pleasant")},
    ["Port Purvis"] = { latitude = -51.432715, longitude = -59.461441, display_name = ("Port Purvis")},
    ["Port Richards"] = { latitude = -51.957966, longitude = -60.470295, display_name = ("Port Richards")},
    ["Port Salvador"] = { latitude = -51.519052, longitude = -58.386283, display_name = ("Port Salvador")},
    ["Port Stephens"] = { latitude = -52.126928, longitude = -60.792761, display_name = ("Port Stephens")},
    ["Port Stephens Farm"] = { latitude = -52.097294, longitude = -60.831988, display_name = ("Port Stephens Farm")},
    ["Port Sussex"] = { latitude = -51.659800, longitude = -59.008681, display_name = ("Port Sussex")},
    ["Port Sussex Farm"] = { latitude = -51.655203, longitude = -59.015105, display_name = ("Port Sussex Farm")},
    ["Port William"] = { latitude = -51.665188, longitude = -57.765262, display_name = ("Port William")},
    ["Porvenir Gulch"] = { latitude = -51.616643, longitude = -60.434527, display_name = ("Porvenir Gulch")},
    ["Praltos Point"] = { latitude = -51.723094, longitude = -59.197469, display_name = ("Praltos Point")},
    ["Prong Point"] = { latitude = -52.096086, longitude = -58.419714, display_name = ("Prong Point")},
    ["Punta Ave Marina"] = { latitude = -51.401776, longitude = -59.496803, display_name = ("Punta Ave Marina")},
    ["Punta Redonda"] = { latitude = -51.752796, longitude = -60.361038, display_name = ("Punta Redonda")},
    ["Punta Turano"] = { latitude = -51.519542, longitude = -59.337199, display_name = ("Punta Turano")},
    ["Punte Triste"] = { latitude = -52.139807, longitude = -58.658876, display_name = ("Punte Triste")},
    ["Purvis Point"] = { latitude = -51.402686, longitude = -59.467578, display_name = ("Purvis Point")},
    ["Pyramid Cove"] = { latitude = -52.009196, longitude = -58.600495, display_name = ("Pyramid Cove")},
    ["Pyramid Point"] = { latitude = -52.013822, longitude = -58.592663, display_name = ("Pyramid Point")},
    ["Quaker Harbour"] = { latitude = -51.812733, longitude = -61.079292, display_name = ("Quaker Harbour")},
    ["Queen Charlotte Bay"] = { latitude = -51.846903, longitude = -60.698646, display_name = ("Queen Charlotte Bay")},
    ["Queen Point"] = { latitude = -51.931631, longitude = -60.585415, display_name = ("Queen Point")},
    ["Rabbit Cove"] = { latitude = -51.666001, longitude = -57.781735, display_name = ("Rabbit Cove")},
    ["Rabbit Cove"] = { latitude = -51.428835, longitude = -59.643831, display_name = ("Rabbit Cove")},
    ["Rabbit Point"] = { latitude = -51.301898, longitude = -59.712549, display_name = ("Rabbit Point")},
    ["Rabbit Rincon"] = { latitude = -51.379738, longitude = -58.062168, display_name = ("Rabbit Rincon")},
    ["Race Point"] = { latitude = -51.983479, longitude = -60.995536, display_name = ("Race Point")},
    ["Race Point"] = { latitude = -51.413260, longitude = -59.097487, display_name = ("Race Point")},
    ["Race Point Farm"] = { latitude = -51.502265, longitude = -58.995508, display_name = ("Race Point Farm")},
    ["Rain Cove"] = { latitude = -51.966659, longitude = -58.646178, display_name = ("Rain Cove")},
    ["Rame Head"] = { latitude = -51.413434, longitude = -60.240043, display_name = ("Rame Head")},
    ["Rapid Point"] = { latitude = -51.382959, longitude = -60.005486, display_name = ("Rapid Point")},
    ["Red Hill Point"] = { latitude = -51.554681, longitude = -59.075482, display_name = ("Red Hill Point")},
    ["Red Point"] = { latitude = -51.381679, longitude = -60.082017, display_name = ("Red Point")},
    ["Reef Point"] = { latitude = -52.094952, longitude = -58.460020, display_name = ("Reef Point")},
    ["Reef Point"] = { latitude = -51.316063, longitude = -59.905550, display_name = ("Reef Point")},
    ["Rincon de Saino"] = { latitude = -51.615658, longitude = -58.255112, display_name = ("Rincon de Saino")},
    ["Rincon Grande Estate Farm"] = { latitude = -51.461580, longitude = -58.309318, display_name = ("Rincon Grande Estate Farm")},
    ["River Harbour"] = { latitude = -51.440768, longitude = -59.723396, display_name = ("River Harbour")},
    ["Riverside Farm"] = { latitude = -51.751296, longitude = -58.353514, display_name = ("Riverside Farm")},
    ["Riverside Farm"] = { latitude = -51.752827, longitude = -58.464821, display_name = ("Riverside Farm")},
    ["Riverview Farm"] = { latitude = -51.630201, longitude = -58.320929, display_name = ("Riverview Farm")},
    ["Robinson Point"] = { latitude = -51.286762, longitude = -59.988474, display_name = ("Robinson Point")},
    ["Rock Harbour"] = { latitude = -51.383419, longitude = -59.797468, display_name = ("Rock Harbour")},
    ["Rocky Inlet"] = { latitude = -51.696315, longitude = -60.060110, display_name = ("Rocky Inlet")},
    ["Rodney Bluff"] = { latitude = -52.074534, longitude = -61.018839, display_name = ("Rodney Bluff")},
    ["Rodney Cove"] = { latitude = -52.060192, longitude = -61.005482, display_name = ("Rodney Cove")},
    ["Ronda House"] = { latitude = -51.369944, longitude = -58.359965, display_name = ("Ronda House")},
    ["Rookery Bay"] = { latitude = -51.705132, longitude = -57.796626, display_name = ("Rookery Bay")},
    ["Round Point"] = { latitude = -51.761193, longitude = -60.323406, display_name = ("Round Point")},
    ["Ruggles Bay"] = { latitude = -52.062724, longitude = -59.657641, display_name = ("Ruggles Bay")},
    ["Sal Point"] = { latitude = -52.058210, longitude = -58.528891, display_name = ("Sal Point")},
    ["Saladero Farm"] = { latitude = -51.722688, longitude = -59.061739, display_name = ("Saladero Farm")},
    ["Salvador Water"] = { latitude = -51.456596, longitude = -58.415279, display_name = ("Salvador Water")},
    ["San Carlos Water"] = { latitude = -51.497988, longitude = -59.079257, display_name = ("San Carlos Water")},
    ["Sand Bay"] = { latitude = -51.728844, longitude = -57.948579, display_name = ("Sand Bay")},
    ["Sand Bay"] = { latitude = -51.552580, longitude = -57.990131, display_name = ("Sand Bay")},
    ["Sand Paddock"] = { latitude = -51.334160, longitude = -60.720062, display_name = ("Sand Paddock")},
    ["Sandy Bay"] = { latitude = -52.187524, longitude = -58.837839, display_name = ("Sandy Bay")},
    ["Sandy Cove"] = { latitude = -52.053058, longitude = -59.144211, display_name = ("Sandy Cove")},
    ["Saturday Point"] = { latitude = -52.029294, longitude = -59.099526, display_name = ("Saturday Point")},
    ["Sea Lion Cove"] = { latitude = -51.627591, longitude = -60.419411, display_name = ("Sea Lion Cove")},
    ["Sea Lion Point"] = { latitude = -51.355033, longitude = -58.342606, display_name = ("Sea Lion Point")},
    ["Seablite Point"] = { latitude = -51.604880, longitude = -60.458946, display_name = ("Seablite Point")},
    ["Seal Bay"] = { latitude = -51.378751, longitude = -58.011522, display_name = ("Seal Bay")},
    ["Seal Cove"] = { latitude = -52.028603, longitude = -58.659652, display_name = ("Seal Cove")},
    ["Seal Point"] = { latitude = -51.737879, longitude = -57.845177, display_name = ("Seal Point")},
    ["Seal Point"] = { latitude = -52.102030, longitude = -59.070403, display_name = ("Seal Point")},
    ["Sealer Cove"] = { latitude = -51.802352, longitude = -60.865985, display_name = ("Sealer Cove")},
    ["Sealer Cove"] = { latitude = -51.364252, longitude = -60.077974, display_name = ("Sealer Cove")},
    ["Seaview Creek"] = { latitude = -51.583900, longitude = -59.076281, display_name = ("Seaview Creek")},
    ["Seaview Point"] = { latitude = -51.580231, longitude = -59.059531, display_name = ("Seaview Point")},
    ["Second Sand Grass Bay"] = { latitude = -51.399848, longitude = -58.280358, display_name = ("Second Sand Grass Bay")},
    ["Semaphore Point"] = { latitude = -52.166661, longitude = -59.659719, display_name = ("Semaphore Point")},
    ["Settlement Creek"] = { latitude = -52.056713, longitude = -60.270653, display_name = ("Settlement Creek")},
    ["Shag Cove"] = { latitude = -51.738039, longitude = -59.644865, display_name = ("Shag Cove")},
    ["Shag Harbour"] = { latitude = -51.736809, longitude = -59.618897, display_name = ("Shag Harbour")},
    ["Shag Point"] = { latitude = -52.222292, longitude = -59.672306, display_name = ("Shag Point")},
    ["Shallop Point"] = { latitude = -51.700088, longitude = -60.093706, display_name = ("Shallop Point")},
    ["Shallow Bay"] = { latitude = -51.404147, longitude = -59.990658, display_name = ("Shallow Bay")},
    ["Shallow Bay"] = { latitude = -51.675373, longitude = -60.103057, display_name = ("Shallow Bay")},
    ["Shallow Bay Farm"] = { latitude = -51.423212, longitude = -59.996788, display_name = ("Shallow Bay Farm")},
    ["Shallow Bluff"] = { latitude = -51.774074, longitude = -60.590136, display_name = ("Shallow Bluff")},
    ["Shallow Cove"] = { latitude = -51.780765, longitude = -58.220458, display_name = ("Shallow Cove")},
    ["Shallow Harbour"] = { latitude = -51.998549, longitude = -58.433919, display_name = ("Shallow Harbour")},
    ["Shallow Harbour"] = { latitude = -51.777055, longitude = -60.508747, display_name = ("Shallow Harbour")},
    ["Shallow Harbour Farm"] = { latitude = -51.746787, longitude = -60.523139, display_name = ("Shallow Harbour Farm")},
    ["Sheffield Farm"] = { latitude = -51.656998, longitude = -60.189851, display_name = ("Sheffield Farm")},
    ["Shell Bay"] = { latitude = -52.074099, longitude = -58.955000, display_name = ("Shell Bay")},
    ["Shell Point"] = { latitude = -52.096211, longitude = -58.987538, display_name = ("Shell Point")},
    ["Ship Harbour"] = { latitude = -51.711248, longitude = -61.269775, display_name = ("Ship Harbour")},
    ["Ship Harbour"] = { latitude = -51.329505, longitude = -59.468029, display_name = ("Ship Harbour")},
    ["Ship Point"] = { latitude = -52.100205, longitude = -59.093109, display_name = ("Ship Point")},
    ["Shivery Creek"] = { latitude = -52.156968, longitude = -60.711010, display_name = ("Shivery Creek")},
    ["Skull Bay"] = { latitude = -51.885657, longitude = -61.127629, display_name = ("Skull Bay")},
    ["Smokey Point"] = { latitude = -51.609499, longitude = -60.429868, display_name = ("Smokey Point")},
    ["Smylie Creek"] = { latitude = -51.431899, longitude = -58.967657, display_name = ("Smylie Creek")},
    ["Snug Cove"] = { latitude = -52.186721, longitude = -59.421272, display_name = ("Snug Cove")},
    ["Sound Point"] = { latitude = -51.438062, longitude = -60.098868, display_name = ("Sound Point")},
    ["South East Point"] = { latitude = -51.796862, longitude = -58.117493, display_name = ("South East Point")},
    ["South Harbour"] = { latitude = -51.734138, longitude = -61.287317, display_name = ("South Harbour")},
    ["South Harbour"] = { latitude = -52.021379, longitude = -60.863314, display_name = ("South Harbour")},
    ["South Harbour Farm"] = { latitude = -52.005681, longitude = -60.745499, display_name = ("South Harbour Farm")},
    ["South Head"] = { latitude = -52.018077, longitude = -60.246792, display_name = ("South Head")},
    ["Southwest Bay"] = { latitude = -51.621380, longitude = -60.443805, display_name = ("Southwest Bay")},
    ["Sparrow Cove"] = { latitude = -51.650953, longitude = -57.808256, display_name = ("Sparrow Cove")},
    ["Spring Point Farm"] = { latitude = -51.830975, longitude = -60.465875, display_name = ("Spring Point Farm")},
    ["Squib Point"] = { latitude = -51.834199, longitude = -58.932243, display_name = ("Squib Point")},
    ["Stanley Harbour"] = { latitude = -51.686850, longitude = -57.837498, display_name = ("Stanley Harbour")},
    ["Starfish Creek"] = { latitude = -52.016717, longitude = -60.266876, display_name = ("Starfish Creek")},
    ["States Cove"] = { latitude = -51.835617, longitude = -60.901174, display_name = ("States Cove")},
    ["Steep Point"] = { latitude = -51.716846, longitude = -60.286209, display_name = ("Steep Point")},
    ["Steeple Jason East"] = { latitude = -51.048500, longitude = -61.190185, display_name = ("Steeple Jason East")},
    ["Steeple Jason West"] = { latitude = -51.024536, longitude = -61.235246, display_name = ("Steeple Jason West")},
    ["Stevelly Bay"] = { latitude = -51.443777, longitude = -60.464029, display_name = ("Stevelly Bay")},
    ["Stinker Point"] = { latitude = -51.821081, longitude = -61.198139, display_name = ("Stinker Point")},
    ["Stoney Ridge Farm"] = { latitude = -52.015289, longitude = -60.489749, display_name = ("Stoney Ridge Farm")},
    ["Strike Off Point"] = { latitude = -51.589443, longitude = -57.975369, display_name = ("Strike Off Point")},
    ["Sulivan Harbour"] = { latitude = -52.046051, longitude = -59.176741, display_name = ("Sulivan Harbour")},
    ["Surf Bay"] = { latitude = -51.695176, longitude = -57.774713, display_name = ("Surf Bay")},
    ["Swan Inlet Farm"] = { latitude = -51.828249, longitude = -58.621617, display_name = ("Swan Inlet Farm")},
    ["Swan Point"] = { latitude = -51.765078, longitude = -60.865615, display_name = ("Swan Point")},
    ["Symonds Harbour"] = { latitude = -51.832967, longitude = -60.352449, display_name = ("Symonds Harbour")},
    ["Table Cove"] = { latitude = -51.809716, longitude = -61.245239, display_name = ("Table Cove")},
    ["Table Point"] = { latitude = -51.797256, longitude = -61.231860, display_name = ("Table Point")},
    ["Tamar Point"] = { latitude = -51.343849, longitude = -59.400786, display_name = ("Tamar Point")},
    ["Tank Point"] = { latitude = -52.293205, longitude = -59.743459, display_name = ("Tank Point")},
    ["Tea Point"] = { latitude = -52.350651, longitude = -59.658444, display_name = ("Tea Point")},
    ["Teal Creek"] = { latitude = -51.819781, longitude = -58.924652, display_name = ("Teal Creek")},
    ["Teal River Farm"] = { latitude = -51.656837, longitude = -60.077527, display_name = ("Teal River Farm")},
    ["Tern Point"] = { latitude = -51.954475, longitude = -60.845847, display_name = ("Tern Point")},
    ["Terra Motas Point"] = { latitude = -51.653839, longitude = -59.045895, display_name = ("Terra Motas Point")},
    ["The Canache"] = { latitude = -51.696076, longitude = -57.790580, display_name = ("The Canache")},
    ["The Careenage"] = { latitude = -51.535925, longitude = -58.108857, display_name = ("The Careenage")},
    ["The Moro"] = { latitude = -51.426845, longitude = -58.524650, display_name = ("The Moro")},
    ["The Narrows"] = { latitude = -51.682131, longitude = -57.829232, display_name = ("The Narrows")},
    ["The Waterfall"] = { latitude = -51.368482, longitude = -60.687188, display_name = ("The Waterfall")},
    ["Town Point"] = { latitude = -51.658161, longitude = -60.241948, display_name = ("Town Point")},
    ["Tussac Point"] = { latitude = -51.682907, longitude = -57.817987, display_name = ("Tussac Point")},
    ["Tussac Point"] = { latitude = -51.871245, longitude = -60.473567, display_name = ("Tussac Point")},
    ["Tussac Point"] = { latitude = -52.347217, longitude = -59.377799, display_name = ("Tussac Point")},
    ["Uranie Bay"] = { latitude = -51.580497, longitude = -58.037639, display_name = ("Uranie Bay")},
    ["Useless Bay"] = { latitude = -51.831826, longitude = -60.938501, display_name = ("Useless Bay")},
    ["Useless Water"] = { latitude = -52.015760, longitude = -58.438264, display_name = ("Useless Water")},
    ["Victoria Harbour"] = { latitude = -51.931896, longitude = -58.829985, display_name = ("Victoria Harbour")},
    ["Volunteer Lagoon"] = { latitude = -51.503374, longitude = -57.862759, display_name = ("Volunteer Lagoon")},
    ["Volunteer Point"] = { latitude = -51.512270, longitude = -57.741694, display_name = ("Volunteer Point")},
    ["Watering Cove"] = { latitude = -51.940323, longitude = -60.430641, display_name = ("Watering Cove")},
    ["Weddell Farm"] = { latitude = -51.893838, longitude = -60.910683, display_name = ("Weddell Farm")},
    ["Weddell Point"] = { latitude = -51.903560, longitude = -61.135711, display_name = ("Weddell Point")},
    ["Weir Creek"] = { latitude = -51.662754, longitude = -57.868638, display_name = ("Weir Creek")},
    ["West Arm"] = { latitude = -52.191772, longitude = -60.526042, display_name = ("West Arm")},
    ["West Bluff"] = { latitude = -51.378002, longitude = -60.704999, display_name = ("West Bluff")},
    ["West Cove"] = { latitude = -51.801623, longitude = -60.389571, display_name = ("West Cove")},
    ["West Cove"] = { latitude = -51.893768, longitude = -58.544670, display_name = ("West Cove")},
    ["West Head"] = { latitude = -52.002374, longitude = -60.104839, display_name = ("West Head")},
    ["West Point"] = { latitude = -51.359234, longitude = -59.830078, display_name = ("West Point")},
    ["Westley Farm"] = { latitude = -51.559167, longitude = -60.245445, display_name = ("Westley Farm")},
    ["Westpoint Cove"] = { latitude = -51.350966, longitude = -60.680022, display_name = ("Westpoint Cove")},
    ["Whale Bay"] = { latitude = -51.384049, longitude = -59.448223, display_name = ("Whale Bay")},
    ["Whale Point"] = { latitude = -51.873196, longitude = -58.247151, display_name = ("Whale Point")},
    ["Whalebone Cove"] = { latitude = -51.687674, longitude = -57.805515, display_name = ("Whalebone Cove")},
    ["Whaler Bay"] = { latitude = -51.537127, longitude = -60.481153, display_name = ("Whaler Bay")},
    ["Wharton Harbour"] = { latitude = -51.966096, longitude = -59.585808, display_name = ("Wharton Harbour")},
    ["White Bluff"] = { latitude = -51.895517, longitude = -58.750505, display_name = ("White Bluff")},
    ["White Cliff Point"] = { latitude = -51.905011, longitude = -59.593413, display_name = ("White Cliff Point")},
    ["White Rock Bay"] = { latitude = -51.437584, longitude = -59.245148, display_name = ("White Rock Bay")},
    ["White Rock Point"] = { latitude = -51.400605, longitude = -59.195913, display_name = ("White Rock Point")},
    ["Whitsand Bay"] = { latitude = -51.717405, longitude = -60.201817, display_name = ("Whitsand Bay")},
    ["Windy Valley"] = { latitude = -51.618994, longitude = -60.437008, display_name = ("Windy Valley")},
    ["Wine Bay"] = { latitude = -52.263220, longitude = -59.503219, display_name = ("Wine Bay")},
    ["Wine Bluff"] = { latitude = -51.964795, longitude = -61.052753, display_name = ("Wine Bluff")},
    ["Wood Cove"] = { latitude = -51.904341, longitude = -61.103511, display_name = ("Wood Cove")},
    ["Wood Cove"] = { latitude = -52.114175, longitude = -60.828552, display_name = ("Wood Cove")},
    ["Woods Valley"] = { latitude = -51.611764, longitude = -60.447296, display_name = ("Woods Valley")},
    ["Woolly Gut Point"] = { latitude = -51.351663, longitude = -60.665646, display_name = ("Woolly Gut Point")},
    ["Wreck Point"] = { latitude = -51.278634, longitude = -59.545552, display_name = ("Wreck Point")},
    ["Wreck Point Farm"] = { latitude = -51.577344, longitude = -59.083134, display_name = ("Wreck Point Farm")},
    ["Yeguada"] = { latitude = -51.861793, longitude = -59.417379, display_name = ("Yeguada")},
    ["Yorke Bay"] = { latitude = -51.679262, longitude = -57.800362, display_name = ("Yorke Bay")},
    ["The Sound"] = { latitude = -51.730407, longitude = -59.430426, display_name = ("The Sound")},
    ["The Sound"] = { latitude = -51.658177, longitude = -59.321324, display_name = ("The Sound")},
    ["Mount Kent"] = { latitude = -51.670161, longitude = -58.101211, display_name = ("Mount Kent")},
    ["Mount Challenger"] = { latitude = -51.699074, longitude = -58.126390, display_name = ("Mount Challenger")},
    ["Mount Usborne"] = { latitude = -51.703101, longitude = -58.834935, display_name = ("Mount Usborne")},
    ["No Mans Land"] = { latitude = -51.689332, longitude = -58.688344, display_name = ("No Mans Land")},
    ["Wickham Heights"] = { latitude = -51.699126, longitude = -58.419443, display_name = ("Wickham Heights")},
    ["Salvador"] = { latitude = -51.434775, longitude = -58.370467, display_name = ("Salvador")},
    ["Foam Creek"] = { latitude = -51.461585, longitude = -58.308689, display_name = ("Foam Creek")},
    ["Port Louis"] = { latitude = -51.529323, longitude = -58.128474, display_name = ("Port Louis")},
    ["Fitzroy"] = { latitude = -51.784321, longitude = -58.228791, display_name = ("Fitzroy")},
    ["Le Marchand"] = { latitude = -50.745431, longitude = -69.476891, display_name = ("Le Marchand")},
    ["Mata Amarilla"] = { latitude = -49.583809, longitude = -71.179944, display_name = ("Mata Amarilla")},
    ["Mata Grande"] = { latitude = -48.855507, longitude = -67.582036, display_name = ("Mata Grande")},
    ["Miranda"] = { latitude = -53.683333, longitude = -68.116668, display_name = ("Miranda")},
    ["Monte Aymond"] = { latitude = -52.102844, longitude = -69.533337, display_name = ("Monte Aymond")},
    ["Monte Dinero"] = { latitude = -52.323463, longitude = -68.559501, display_name = ("Monte Dinero")},
    ["Paso Gobernador Gregores"] = { latitude = -47.833333, longitude = -66.583334, display_name = ("Paso Gobernador Gregores")},
    ["Paso Gregores"] = { latitude = -47.800000, longitude = -66.583334, display_name = ("Paso Gregores")},
    ["Paso Rodolfo Roballos"] = { latitude = -47.133332, longitude = -71.850023, display_name = ("Paso Rodolfo Roballos")},
    ["Puerto Bandera"] = { latitude = -50.313724, longitude = -72.791908, display_name = ("Puerto Bandera")},
    ["Puerto Coig"] = { latitude = -50.954045, longitude = -69.220979, display_name = ("Puerto Coig")},
    ["Estancia Harberton"] = { latitude = -54.877762, longitude = -67.329386, display_name = ("Estancia Harberton")},
    ["Puerto Remolino"] = { latitude = -54.860454, longitude = -67.859145, display_name = ("Puerto Remolino")},
    ["Puerto San Julian"] = { latitude = -49.306953, longitude = -67.729787, display_name = ("Puerto San Julian")},
    ["Punta del Lago"] = { latitude = -49.595274, longitude = -72.057822, display_name = ("Punta del Lago")},
    ["Punta del Monte"] = { latitude = -51.634492, longitude = -71.312863, display_name = ("Punta del Monte")},
    ["Rincan"] = { latitude = -50.294210, longitude = -69.918910, display_name = ("Rincan")},
    ["Estancia San Justo"] = { latitude = -54.028494, longitude = -68.544922, display_name = ("Estancia San Justo")},
    ["Estancia Sara"] = { latitude = -53.433583, longitude = -68.183738, display_name = ("Estancia Sara")},
    ["Sarmiento"] = { latitude = -54.337442, longitude = -68.180968, display_name = ("Sarmiento")},
    ["Estancia Despedida"] = { latitude = -53.952200, longitude = -68.256912, display_name = ("Estancia Despedida")},
    ["Tamel Aike"] = { latitude = -48.316667, longitude = -70.966680, display_name = ("Tamel Aike")},
    ["Estancia Tapi Aike"] = { latitude = -51.056127, longitude = -71.794226, display_name = ("Estancia Tapi Aike")},
    ["Tellier"] = { latitude = -47.648895, longitude = -66.040225, display_name = ("Tellier")},
    ["Tres Lagos"] = { latitude = -49.598080, longitude = -71.446487, display_name = ("Tres Lagos")},
    ["Tucu Tucu"] = { latitude = -48.433333, longitude = -71.850020, display_name = ("Tucu Tucu")},
    ["Ushuaia"] = { latitude = -54.806933, longitude = -68.307325, display_name = ("Ushuaia")},
    ["Veintiocho de Noviembre"] = { latitude = -51.579567, longitude = -72.211488, display_name = ("Veintiocho de Noviembre")},
    ["Estancia Viamonte"] = { latitude = -53.995575, longitude = -67.414225, display_name = ("Estancia Viamonte")},
    ["Vieja Carmen"] = { latitude = -54.498210, longitude = -67.767154, display_name = ("Vieja Carmen")},
    ["Antonio de Biedma"] = { latitude = -47.487031, longitude = -66.503434, display_name = ("Antonio de Biedma")},
    ["Bajo Caracoles"] = { latitude = -47.443328, longitude = -70.926533, display_name = ("Bajo Caracoles")},
    ["Bajo Picaso"] = { latitude = -49.051259, longitude = -68.387156, display_name = ("Bajo Picaso")},
    ["Cancha Carrera"] = { latitude = -51.256817, longitude = -72.220102, display_name = ("Cancha Carrera")},
    ["Cauchicol"] = { latitude = -53.881017, longitude = -67.929884, display_name = ("Cauchicol")},
    ["Charles Fuhr"] = { latitude = -50.270052, longitude = -71.887286, display_name = ("Charles Fuhr")},
    ["Comandante Portillo"] = { latitude = -54.900000, longitude = -66.000000, display_name = ("Comandante Portillo")},
    ["Corpen"] = { latitude = -49.583333, longitude = -69.500005, display_name = ("Corpen")},
    ["Estancia Cullen"] = { latitude = -52.883340, longitude = -68.446516, display_name = ("Estancia Cullen")},
    ["Diego Ritchie"] = { latitude = -51.978050, longitude = -70.396159, display_name = ("Diego Ritchie")},
    ["Rospentek"] = { latitude = -51.665372, longitude = -72.145268, display_name = ("Rospentek")},
    ["Flecha Negra"] = { latitude = -47.833333, longitude = -69.666674, display_name = ("Flecha Negra")},
    ["Florida Negra"] = { latitude = -48.422050, longitude = -67.354710, display_name = ("Florida Negra")},
    ["Fuentes del Coyle"] = { latitude = -51.029367, longitude = -71.481186, display_name = ("Fuentes del Coyle")},
    ["Gobernador Gregores"] = { latitude = -48.750870, longitude = -70.248552, display_name = ("Gobernador Gregores")},
    ["Gobernador Moyano"] = { latitude = -47.774568, longitude = -68.582995, display_name = ("Gobernador Moyano")},
    ["Gobernador Moyano"] = { latitude = -47.850000, longitude = -66.633334, display_name = ("Gobernador Moyano")},
    ["Gobernador Moyano"] = { latitude = -51.863858, longitude = -70.559914, display_name = ("Gobernador Moyano")},
    ["Guankenken Aike"] = { latitude = -51.429320, longitude = -69.827830, display_name = ("Guankenken Aike")},
    ["Hill Station"] = { latitude = -51.575508, longitude = -69.232891, display_name = ("Hill Station")},
    ["La Aragonesa"] = { latitude = -48.075173, longitude = -69.761083, display_name = ("La Aragonesa")},
    ["La Bajada"] = { latitude = -48.883333, longitude = -69.700006, display_name = ("La Bajada")},
    ["Lago Cardiel"] = { latitude = -48.947044, longitude = -71.376108, display_name = ("Lago Cardiel")},
    ["Laguna Grande"] = { latitude = -49.506762, longitude = -70.159896, display_name = ("Laguna Grande")},
    ["Lai Aike"] = { latitude = -49.400000, longitude = -68.750004, display_name = ("Lai Aike")},
    ["La Marina"] = { latitude = -54.316667, longitude = -68.550002, display_name = ("La Marina")},
    ["Lapataia"] = { latitude = -54.855622, longitude = -68.577236, display_name = ("Lapataia")},
    ["Las Horquetas"] = { latitude = -48.254585, longitude = -71.162442, display_name = ("Las Horquetas")},
    ["Las Perdices"] = { latitude = -50.316667, longitude = -71.250012, display_name = ("Las Perdices")},
    ["Tierra del Fuego"] = { latitude = -53.890464, longitude = -68.348600, display_name = ("Tierra del Fuego")},
    ["Lago Posadas"] = { latitude = -47.565618, longitude = -71.742692, display_name = ("Lago Posadas")},
    ["Tierra del Fuego"] = { latitude = -54.407107, longitude = -67.897491, display_name = ("Tierra del Fuego")},
    ["Santa Cruz"] = { latitude = -48.569333, longitude = -70.160685, display_name = ("Santa Cruz")},
    ["Punta Azconape"] = { latitude = -49.253917, longitude = -67.631239, display_name = ("Punta Azconape")},
    ["Punta Gallows"] = { latitude = -49.310372, longitude = -67.695671, display_name = ("Punta Gallows")},
    ["Cerro Castillo"] = { latitude = -51.256249, longitude = -72.344706, display_name = ("Cerro Castillo")},
    ["Punta Loyola"] = { latitude = -51.608878, longitude = -69.011639, display_name = ("Punta Loyola")},
    ["Calluqueo"] = { latitude = -47.657245, longitude = -72.477491, display_name = ("Calluqueo")},
    ["La Juanita"] = { latitude = -47.336737, longitude = -72.174784, display_name = ("La Juanita")},
    ["La Peligrosa"] = { latitude = -47.380286, longitude = -72.247977, display_name = ("La Peligrosa")},
    ["Lago Escondido"] = { latitude = -54.640774, longitude = -67.762884, display_name = ("Lago Escondido")},
    ["Aeropuerto Viejo"] = { latitude = -50.338096, longitude = -72.246806, display_name = ("Aeropuerto Viejo")},
    ["Laguna Negra"] = { latitude = -54.524730, longitude = -67.292677, display_name = ("Laguna Negra")},
    ["Estancia la Oriental"] = { latitude = -47.804487, longitude = -72.094099, display_name = ("Estancia la Oriental")},
    ["Nativos"] = { latitude = -47.743195, longitude = -65.910980, display_name = ("Nativos")},
    ["Manantiales"] = { latitude = -50.341434, longitude = -72.298383, display_name = ("Manantiales")},
    ["Nueva Soberana"] = { latitude = -50.315879, longitude = -72.314947, display_name = ("Nueva Soberana")},
    ["Las Barrancas"] = { latitude = -53.756583, longitude = -67.729884, display_name = ("Las Barrancas")},
    ["Cerro Calafate"] = { latitude = -50.345661, longitude = -72.260605, display_name = ("Cerro Calafate")},
    ["Condor Cliff"] = { latitude = -50.220282, longitude = -70.931250, display_name = ("Condor Cliff")},
    ["Terrazas de Manantiales"] = { latitude = -50.344153, longitude = -72.310335, display_name = ("Terrazas de Manantiales")},
    ["Huiliche"] = { latitude = -50.364216, longitude = -72.261491, display_name = ("Huiliche")},
    ["Las Buitreras"] = { latitude = -51.725306, longitude = -70.132894, display_name = ("Las Buitreras")},
    ["Las Piedras"] = { latitude = -50.341583, longitude = -72.253668, display_name = ("Las Piedras")},
    ["Salesiano"] = { latitude = -50.348902, longitude = -72.277424, display_name = ("Salesiano")},
    ["Lago Argentino"] = { latitude = -50.344446, longitude = -72.276446, display_name = ("Lago Argentino")},
    ["Linda Vista"] = { latitude = -50.353162, longitude = -72.289283, display_name = ("Linda Vista")},
    ["Casco Viejo"] = { latitude = -50.334722, longitude = -72.266390, display_name = ("Casco Viejo")},
    ["Plumas Verdes"] = { latitude = -50.341102, longitude = -72.284337, display_name = ("Plumas Verdes")},
    ["Ototel Aike"] = { latitude = -50.818402, longitude = -69.515470, display_name = ("Ototel Aike")},
    ["Severino Amelung"] = { latitude = -47.744353, longitude = -65.882563, display_name = ("Severino Amelung")},
    ["Prefectura"] = { latitude = -47.752433, longitude = -65.909614, display_name = ("Prefectura")},
    ["Fuerte San Carlos"] = { latitude = -47.753911, longitude = -65.900867, display_name = ("Fuerte San Carlos")},
    ["Mirador"] = { latitude = -47.747263, longitude = -65.901249, display_name = ("Mirador")},
    ["Randisi"] = { latitude = -47.741155, longitude = -65.889550, display_name = ("Randisi")},
    ["Centenario"] = { latitude = -47.745680, longitude = -65.888739, display_name = ("Centenario")},
    ["Almafuerte"] = { latitude = -47.739640, longitude = -65.888611, display_name = ("Almafuerte")},
    ["Del Alto"] = { latitude = -47.743659, longitude = -65.891165, display_name = ("Del Alto")},
    ["Pevep"] = { latitude = -47.746133, longitude = -65.884953, display_name = ("Pevep")},
    ["Vieira"] = { latitude = -47.745152, longitude = -65.895467, display_name = ("Vieira")},
    ["Puerto Nuevo"] = { latitude = -47.741332, longitude = -65.882291, display_name = ("Puerto Nuevo")},
    ["Quinta Cadario"] = { latitude = -47.749571, longitude = -65.900253, display_name = ("Quinta Cadario")},
    ["La Quinta"] = { latitude = -49.357605, longitude = -72.869925, display_name = ("La Quinta")},
    ["Estancia La Florida"] = { latitude = -48.796935, longitude = -72.551855, display_name = ("Estancia La Florida")},
    ["Tres Cerros"] = { latitude = -48.143225, longitude = -67.652099, display_name = ("Tres Cerros")},
    ["Estancia Rio Ewan"] = { latitude = -54.233493, longitude = -67.216310, display_name = ("Estancia Rio Ewan")},
    ["Estancia Tepi"] = { latitude = -54.250082, longitude = -67.156238, display_name = ("Estancia Tepi")},
    ["La Quinta"] = { latitude = -47.466841, longitude = -67.341048, display_name = ("La Quinta")},
    ["Estancia San Jose"] = { latitude = -49.415962, longitude = -72.789599, display_name = ("Estancia San Jose")},
    ["Los Coihues"] = { latitude = -51.535730, longitude = -72.339195, display_name = ("Los Coihues")},
    ["Las Lengas"] = { latitude = -51.537738, longitude = -72.343467, display_name = ("Las Lengas")},
    ["Islas Malvinas"] = { latitude = -51.534273, longitude = -72.329881, display_name = ("Islas Malvinas")},
    ["Santa Cruz"] = { latitude = -51.539346, longitude = -72.334636, display_name = ("Santa Cruz")},
    ["Las Margaritas"] = { latitude = -51.540984, longitude = -72.337259, display_name = ("Las Margaritas")},
    ["Don Bosco"] = { latitude = -51.542416, longitude = -72.340417, display_name = ("Don Bosco")},
    ["Los Lupinos"] = { latitude = -51.542482, longitude = -72.342855, display_name = ("Los Lupinos")},
    ["Hielos Continentales"] = { latitude = -51.541815, longitude = -72.346996, display_name = ("Hielos Continentales")},
    ["Puerto Bajo las Sombras"] = { latitude = -50.474128, longitude = -72.994062, display_name = ("Puerto Bajo las Sombras")},
    ["Puerto Parry"] = { latitude = -54.798059, longitude = -64.365642, display_name = ("Puerto Parry")},
    ["Cabo de San Juan"] = { latitude = -54.723422, longitude = -63.813242, display_name = ("Cabo de San Juan")},
    ["Punta Cuchillo"] = { latitude = -54.836439, longitude = -64.753253, display_name = ("Punta Cuchillo")},
    ["Faro Negro"] = { latitude = -49.257222, longitude = -67.643271, display_name = ("Faro Negro")},
    ["Punta Guijarro"] = { latitude = -49.256868, longitude = -67.667435, display_name = ("Punta Guijarro")},
    ["Santa Flavia"] = { latitude = -51.545274, longitude = -72.353929, display_name = ("Santa Flavia")},
    ["Don Bosco"] = { latitude = -51.587917, longitude = -72.216739, display_name = ("Don Bosco")},
    ["Frontera Austral"] = { latitude = -51.586442, longitude = -72.212121, display_name = ("Frontera Austral")},
    ["Norte"] = { latitude = -51.576708, longitude = -72.212228, display_name = ("Norte")},
    ["Quiroga"] = { latitude = -51.583425, longitude = -72.212723, display_name = ("Quiroga")},
    ["Santa Cruz"] = { latitude = -51.581889, longitude = -72.215229, display_name = ("Santa Cruz")},
    ["Julia Dufour"] = { latitude = -51.547098, longitude = -72.238197, display_name = ("Julia Dufour")},
    ["Estancia Monte Entrance"] = { latitude = -50.070337, longitude = -68.515416, display_name = ("Estancia Monte Entrance")},
    ["Tolhuin"] = { latitude = -54.511349, longitude = -67.195401, display_name = ("Tolhuin")},
    ["Estancia Berta"] = { latitude = -52.009169, longitude = -72.038526, display_name = ("Estancia Berta")},
    ["Estancia El Palenque"] = { latitude = -51.728046, longitude = -72.267951, display_name = ("Estancia El Palenque")},
    ["Santa Margarita"] = { latitude = -49.558267, longitude = -72.425378, display_name = ("Santa Margarita")},
    ["Estancia La Silesia"] = { latitude = -49.743146, longitude = -71.941012, display_name = ("Estancia La Silesia")},
    ["Estancia Avellaneda"] = { latitude = -50.265333, longitude = -72.837388, display_name = ("Estancia Avellaneda")},
    ["Estancia Los Ventisqueros"] = { latitude = -50.416912, longitude = -72.745527, display_name = ("Estancia Los Ventisqueros")},
    ["Estancia Nibepo Aike"] = { latitude = -50.564280, longitude = -72.863877, display_name = ("Estancia Nibepo Aike")},
    ["Chimen Aike"] = { latitude = -51.706819, longitude = -69.282341, display_name = ("Chimen Aike")},
    ["Palermo Aike"] = { latitude = -51.705803, longitude = -69.717042, display_name = ("Palermo Aike")},
    ["Campo de Invierno"] = { latitude = -50.319583, longitude = -72.246984, display_name = ("Campo de Invierno")},
    ["Alto Calafate"] = { latitude = -50.338510, longitude = -72.232535, display_name = ("Alto Calafate")},
    ["La Beatriz"] = { latitude = -47.453055, longitude = -67.105937, display_name = ("La Beatriz")},
    ["Mancha Blanca"] = { latitude = -47.455083, longitude = -66.880167, display_name = ("Mancha Blanca")},
    ["Altos de Soberana"] = { latitude = -50.320239, longitude = -72.325721, display_name = ("Altos de Soberana")},
    ["Solo Terra"] = { latitude = -50.350922, longitude = -72.245328, display_name = ("Solo Terra")},
    ["Felix Frias"] = { latitude = -50.333138, longitude = -72.257843, display_name = ("Felix Frias")},
    ["Nuevo Manantiales"] = { latitude = -50.335789, longitude = -72.317838, display_name = ("Nuevo Manantiales")},
    ["La Calandria"] = { latitude = -47.619570, longitude = -67.353367, display_name = ("La Calandria")},
    ["La Castora"] = { latitude = -47.584397, longitude = -67.136232, display_name = ("La Castora")},
    ["La Constancia"] = { latitude = -47.544866, longitude = -65.786829, display_name = ("La Constancia")},
    ["Los Medanos"] = { latitude = -47.623271, longitude = -65.817736, display_name = ("Los Medanos")},
    ["Campo Marina"] = { latitude = -47.770016, longitude = -66.018920, display_name = ("Campo Marina")},
    ["Cerro del Paso"] = { latitude = -47.824283, longitude = -66.140911, display_name = ("Cerro del Paso")},
    ["Puerto Jenkins"] = { latitude = -47.764178, longitude = -65.901115, display_name = ("Puerto Jenkins")},
    ["Santa Elena"] = { latitude = -47.794311, longitude = -65.911261, display_name = ("Santa Elena")},
    ["Papa Francisco"] = { latitude = -49.307534, longitude = -67.756625, display_name = ("Papa Francisco")},
    ["Estancia La Siberia"] = { latitude = -49.014304, longitude = -71.061427, display_name = ("Estancia La Siberia")},
    ["Chacras Piedrabuena"] = { latitude = -50.021569, longitude = -69.004401, display_name = ("Chacras Piedrabuena")},
    ["Comercial"] = { latitude = -51.537104, longitude = -72.336783, display_name = ("Comercial")},
    ["Los Mineros"] = { latitude = -51.537769, longitude = -72.332485, display_name = ("Los Mineros")},
    ["Helena"] = { latitude = -49.897482, longitude = -68.607133, display_name = ("Helena")},
    ["Los Granaderos"] = { latitude = -48.223989, longitude = -69.094696, display_name = ("Los Granaderos")},
    ["Santa Catalina"] = { latitude = -48.297926, longitude = -69.076465, display_name = ("Santa Catalina")},
    ["La Levadura"] = { latitude = -47.314061, longitude = -68.289946, display_name = ("La Levadura")},
    ["Estancia Pirinaica"] = { latitude = -54.258577, longitude = -66.910229, display_name = ("Estancia Pirinaica")},
    ["Estancia Puerto Rancho"] = { latitude = -55.046937, longitude = -66.561407, display_name = ("Estancia Puerto Rancho")},
    ["Rancho Tres Amigos"] = { latitude = -54.632427, longitude = -65.280272, display_name = ("Rancho Tres Amigos")},
    ["Terrazas de Nimez"] = { latitude = -50.322286, longitude = -72.260833, display_name = ("Terrazas de Nimez")},
    ["La Pampita"] = { latitude = -49.306602, longitude = -67.774832, display_name = ("La Pampita")},
    ["Arroyo El Sucio"] = { latitude = -48.191174, longitude = -72.309602, display_name = ("Arroyo El Sucio")},
    ["Campamento Mayer"] = { latitude = -48.195712, longitude = -72.337717, display_name = ("Campamento Mayer")},
    ["Entrada Mayer"] = { latitude = -48.209324, longitude = -72.323345, display_name = ("Entrada Mayer")},
    ["Candelario Mancilla"] = { latitude = -48.868596, longitude = -72.743227, display_name = ("Candelario Mancilla")},
    ["Estancia Ana"] = { latitude = -50.594091, longitude = -71.346879, display_name = ("Estancia Ana")},
    ["Maipu"] = { latitude = -49.147049, longitude = -72.483949, display_name = ("Maipu")},
    ["La Bernarda"] = { latitude = -49.162391, longitude = -71.926102, display_name = ("La Bernarda")},
    ["Cerro Colorado"] = { latitude = -49.058749, longitude = -71.839008, display_name = ("Cerro Colorado")},
    ["La Federica"] = { latitude = -49.036873, longitude = -72.238162, display_name = ("La Federica")},
    ["La Tercera"] = { latitude = -49.186332, longitude = -72.364433, display_name = ("La Tercera")},
    ["Sierra Nevada"] = { latitude = -48.922137, longitude = -72.265663, display_name = ("Sierra Nevada")},
    ["San Adolfo"] = { latitude = -49.155453, longitude = -71.725175, display_name = ("San Adolfo")},
    ["Editha"] = { latitude = -48.917580, longitude = -72.326083, display_name = ("Editha")},
    ["La Lila"] = { latitude = -49.013507, longitude = -72.220039, display_name = ("La Lila")},
    ["Chacabuco"] = { latitude = -49.055477, longitude = -72.313304, display_name = ("Chacabuco")},
    ["Rio Meseta"] = { latitude = -49.282194, longitude = -71.921164, display_name = ("Rio Meseta")},
    ["Lago Tar"] = { latitude = -49.277014, longitude = -72.002692, display_name = ("Lago Tar")},
    ["Santa Angelita"] = { latitude = -49.207191, longitude = -72.224056, display_name = ("Santa Angelita")},
    ["Hevia"] = { latitude = -48.912764, longitude = -72.411481, display_name = ("Hevia")},
    ["La Montosa"] = { latitude = -47.299292, longitude = -72.027116, display_name = ("La Montosa")},
    ["La Soledad"] = { latitude = -47.381977, longitude = -72.207754, display_name = ("La Soledad")},
    ["Estancia La Suerte"] = { latitude = -49.138454, longitude = -72.937655, display_name = ("Estancia La Suerte")},
    ["La gruta de Lurdes"] = { latitude = -47.687550, longitude = -66.014012, display_name = ("La gruta de Lurdes")},
    ["Capilla"] = { latitude = -49.598686, longitude = -71.447858, display_name = ("Capilla")},
    ["Las Vizcachas"] = { latitude = -50.762696, longitude = -72.048624, display_name = ("Las Vizcachas")},
    ["Los Rubios"] = { latitude = -50.786879, longitude = -71.430251, display_name = ("Los Rubios")},
    ["Los Viejos"] = { latitude = -50.794872, longitude = -71.347036, display_name = ("Los Viejos")},
    ["Coronel Guarumba"] = { latitude = -50.862275, longitude = -71.815526, display_name = ("Coronel Guarumba")},
    ["La Correntina"] = { latitude = -50.873398, longitude = -71.511372, display_name = ("La Correntina")},
    ["Chorrillo"] = { latitude = -50.849175, longitude = -71.211925, display_name = ("Chorrillo")},
    ["San Miguel"] = { latitude = -50.779461, longitude = -71.194408, display_name = ("San Miguel")},
    ["Librun"] = { latitude = -50.840033, longitude = -71.099003, display_name = ("Librun")},
    ["Rio Pelque"] = { latitude = -50.981447, longitude = -70.992185, display_name = ("Rio Pelque")},
    ["Rivadavia"] = { latitude = -50.758697, longitude = -70.947480, display_name = ("Rivadavia")},
    ["Boleadoras"] = { latitude = -50.867863, longitude = -70.810220, display_name = ("Boleadoras")},
    ["La Chacra"] = { latitude = -50.982005, longitude = -70.676252, display_name = ("La Chacra")},
    ["Peter"] = { latitude = -50.914322, longitude = -70.635606, display_name = ("Peter")},
    ["La Carmen"] = { latitude = -50.948439, longitude = -70.292328, display_name = ("La Carmen")},
    ["Monte Negro"] = { latitude = -51.041090, longitude = -69.869563, display_name = ("Monte Negro")},
    ["Flora"] = { latitude = -50.968965, longitude = -70.361627, display_name = ("Flora")},
    ["San Lorenzo"] = { latitude = -50.911558, longitude = -70.056938, display_name = ("San Lorenzo")},
    ["Tres Lagunas"] = { latitude = -50.775976, longitude = -70.077831, display_name = ("Tres Lagunas")},
    ["Dos Lagunas"] = { latitude = -50.920238, longitude = -69.858427, display_name = ("Dos Lagunas")},
    ["Laguna Cifre"] = { latitude = -50.950353, longitude = -69.983062, display_name = ("Laguna Cifre")},
    ["San Lorenzo"] = { latitude = -51.039574, longitude = -70.386488, display_name = ("San Lorenzo")},
    ["Domi Aike"] = { latitude = -50.830417, longitude = -69.689989, display_name = ("Domi Aike")},
    ["La Filomena"] = { latitude = -51.014731, longitude = -70.277271, display_name = ("La Filomena")},
    ["Coy Inlet"] = { latitude = -50.927556, longitude = -69.218341, display_name = ("Coy Inlet")},
    ["Los Alamos"] = { latitude = -50.913460, longitude = -69.398775, display_name = ("Los Alamos")},
    ["La Aguada"] = { latitude = -50.945091, longitude = -69.340512, display_name = ("La Aguada")},
    ["Moy"] = { latitude = -51.179483, longitude = -69.614896, display_name = ("Moy")},
    ["Monte Carlo"] = { latitude = -51.024590, longitude = -70.629508, display_name = ("Monte Carlo")},
    ["Maria Ines"] = { latitude = -51.032910, longitude = -70.574696, display_name = ("Maria Ines")},
    ["Bandera"] = { latitude = -51.061142, longitude = -70.411205, display_name = ("Bandera")},
    ["Camusu Aike"] = { latitude = -51.116296, longitude = -70.289348, display_name = ("Camusu Aike")},
    ["La Carolina"] = { latitude = -51.059781, longitude = -70.171849, display_name = ("La Carolina")},
    ["La Aida"] = { latitude = -51.167254, longitude = -70.014336, display_name = ("La Aida")},
    ["Bandera"] = { latitude = -51.208097, longitude = -71.176304, display_name = ("Bandera")},
    ["Lujan"] = { latitude = -51.176050, longitude = -70.994412, display_name = ("Lujan")},
    ["La Vanguardia"] = { latitude = -51.021755, longitude = -71.111929, display_name = ("La Vanguardia")},
    ["Chali Aike"] = { latitude = -51.047507, longitude = -70.856328, display_name = ("Chali Aike")},
    ["La Correntina"] = { latitude = -50.916124, longitude = -71.764777, display_name = ("La Correntina")},
    ["La Paloma"] = { latitude = -50.937877, longitude = -71.499452, display_name = ("La Paloma")},
    ["La Felicidad"] = { latitude = -50.921110, longitude = -71.446839, display_name = ("La Felicidad")},
    ["Ocho Hermanos"] = { latitude = -51.082876, longitude = -71.339920, display_name = ("Ocho Hermanos")},
    ["La Nelida"] = { latitude = -51.057675, longitude = -71.284264, display_name = ("La Nelida")},
    ["La Maria"] = { latitude = -51.052124, longitude = -71.603784, display_name = ("La Maria")},
    ["Fuentes del Coyle"] = { latitude = -51.146514, longitude = -71.915765, display_name = ("Fuentes del Coyle")},
    ["La Sarita Argentina"] = { latitude = -51.145214, longitude = -71.841294, display_name = ("La Sarita Argentina")},
    ["La Lucila"] = { latitude = -50.997406, longitude = -71.908714, display_name = ("La Lucila")},
    ["La Siberia"] = { latitude = -51.249165, longitude = -71.429055, display_name = ("La Siberia")},
    ["Alaska"] = { latitude = -51.185763, longitude = -71.336965, display_name = ("Alaska")},
    ["La Tapera"] = { latitude = -51.135301, longitude = -71.467681, display_name = ("La Tapera")},
    ["La Lolita"] = { latitude = -51.153055, longitude = -71.579754, display_name = ("La Lolita")},
    ["La Primavera"] = { latitude = -51.447714, longitude = -72.228270, display_name = ("La Primavera")},
    ["Tres Marias"] = { latitude = -51.318977, longitude = -72.176226, display_name = ("Tres Marias")},
    ["Stag River"] = { latitude = -51.663660, longitude = -72.025840, display_name = ("Stag River")},
    ["La Fermina"] = { latitude = -51.690785, longitude = -72.083675, display_name = ("La Fermina")},
    ["Punta Alta"] = { latitude = -51.684708, longitude = -71.966222, display_name = ("Punta Alta")},
    ["Laguna Larga"] = { latitude = -51.808214, longitude = -71.882625, display_name = ("Laguna Larga")},
    ["Estancia Glencross"] = { latitude = -51.828291, longitude = -71.692448, display_name = ("Estancia Glencross")},
    ["Rincon de Los Morros"] = { latitude = -51.920101, longitude = -71.497324, display_name = ("Rincon de Los Morros")},
    ["Cuadrado"] = { latitude = -51.743128, longitude = -71.315743, display_name = ("Cuadrado")},
    ["Diez Y Ocho"] = { latitude = -51.776086, longitude = -71.123926, display_name = ("Diez Y Ocho")},
    ["Chank Aike"] = { latitude = -51.453020, longitude = -70.514579, display_name = ("Chank Aike")},
    ["San Jose"] = { latitude = -51.281311, longitude = -70.812392, display_name = ("San Jose")},
    ["Minik Aike"] = { latitude = -51.422814, longitude = -71.081302, display_name = ("Minik Aike")},
    ["Corpie Aike"] = { latitude = -51.353084, longitude = -70.654609, display_name = ("Corpie Aike")},
    ["Los Vascos"] = { latitude = -51.431070, longitude = -70.863302, display_name = ("Los Vascos")},
    ["La Obligada"] = { latitude = -51.275958, longitude = -71.175486, display_name = ("La Obligada")},
    ["La Vega"] = { latitude = -51.444866, longitude = -70.330878, display_name = ("La Vega")},
    ["Las Horquetas"] = { latitude = -51.395724, longitude = -70.224121, display_name = ("Las Horquetas")},
    ["Suarez"] = { latitude = -51.380322, longitude = -70.213085, display_name = ("Suarez")},
    ["Las Vegas"] = { latitude = -51.325617, longitude = -70.310106, display_name = ("Las Vegas")},
    ["Cerro Cuadrado"] = { latitude = -51.320499, longitude = -70.002893, display_name = ("Cerro Cuadrado")},
    ["Guer Aike"] = { latitude = -51.452278, longitude = -70.005562, display_name = ("Guer Aike")},
    ["La Matilde"] = { latitude = -51.294548, longitude = -69.971165, display_name = ("La Matilde")},
    ["Moy Aike Chico"] = { latitude = -51.247602, longitude = -69.624339, display_name = ("Moy Aike Chico")},
    ["Los Pozos"] = { latitude = -51.451639, longitude = -69.315894, display_name = ("Los Pozos")},
    ["Del Frigorifico"] = { latitude = -51.614578, longitude = -69.350605, display_name = ("Del Frigorifico")},
    ["Killik Aike Sur"] = { latitude = -51.683113, longitude = -69.368351, display_name = ("Killik Aike Sur")},
    ["Killik Aike Sur"] = { latitude = -51.617580, longitude = -69.466862, display_name = ("Killik Aike Sur")},
    ["Killik Aike Norte"] = { latitude = -51.571289, longitude = -69.445227, display_name = ("Killik Aike Norte")},
    ["Oeste"] = { latitude = -51.517235, longitude = -69.615717, display_name = ("Oeste")},
    ["La Angelina"] = { latitude = -51.436005, longitude = -69.119121, display_name = ("La Angelina")},
    ["Cabo Buen Tiempo"] = { latitude = -51.564513, longitude = -69.070573, display_name = ("Cabo Buen Tiempo")},
    ["Del Rio Chico"] = { latitude = -51.671139, longitude = -69.118381, display_name = ("Del Rio Chico")},
    ["Veintidos"] = { latitude = -51.356330, longitude = -69.580161, display_name = ("Veintidos")},
    ["San Cristobal"] = { latitude = -51.515125, longitude = -70.085139, display_name = ("San Cristobal")},
    ["La Leona"] = { latitude = -51.506458, longitude = -69.779421, display_name = ("La Leona")},
    ["Mulato"] = { latitude = -51.924313, longitude = -70.879837, display_name = ("Mulato")},
    ["Pampa"] = { latitude = -51.949091, longitude = -71.168665, display_name = ("Pampa")},
    ["Dieciseis"] = { latitude = -51.854132, longitude = -71.253222, display_name = ("Dieciseis")},
    ["La Carlota"] = { latitude = -51.847674, longitude = -70.516124, display_name = ("La Carlota")},
    ["Cerro Negro"] = { latitude = -51.931893, longitude = -70.242680, display_name = ("Cerro Negro")},
    ["La Maragata"] = { latitude = -51.868931, longitude = -70.219060, display_name = ("La Maragata")},
    ["Alquinta"] = { latitude = -51.734466, longitude = -70.339546, display_name = ("Alquinta")},
    ["Noya"] = { latitude = -51.722144, longitude = -69.987192, display_name = ("Noya")},
    ["Laguna Colorada"] = { latitude = -51.667626, longitude = -69.917869, display_name = ("Laguna Colorada")},
    ["Media Agua"] = { latitude = -51.840896, longitude = -69.881413, display_name = ("Media Agua")},
    ["Bitzch"] = { latitude = -51.813905, longitude = -69.775300, display_name = ("Bitzch")},
    ["La Argentina"] = { latitude = -51.899693, longitude = -70.042259, display_name = ("La Argentina")},
    ["Pali Aike"] = { latitude = -51.983025, longitude = -69.748869, display_name = ("Pali Aike")},
    ["Osasuna"] = { latitude = -51.955927, longitude = -69.891237, display_name = ("Osasuna")},
    ["Markatch Aike"] = { latitude = -51.919502, longitude = -69.630911, display_name = ("Markatch Aike")},
    ["Tres Hermanos"] = { latitude = -51.940785, longitude = -69.604545, display_name = ("Tres Hermanos")},
    ["Cerro Norte"] = { latitude = -51.840693, longitude = -69.404454, display_name = ("Cerro Norte")},
    ["Viejo"] = { latitude = -51.834961, longitude = -69.519280, display_name = ("Viejo")},
    ["Frailes de Condor"] = { latitude = -51.913756, longitude = -69.122404, display_name = ("Frailes de Condor")},
    ["Frailes de Loyola"] = { latitude = -51.870196, longitude = -68.972497, display_name = ("Frailes de Loyola")},
    ["Negro"] = { latitude = -51.742656, longitude = -68.954570, display_name = ("Negro")},
    ["Beta"] = { latitude = -52.726250, longitude = -68.547351, display_name = ("Beta")},
    ["De La Costa"] = { latitude = -53.597802, longitude = -67.987795, display_name = ("De La Costa")},
    ["Maria Behety"] = { latitude = -53.786150, longitude = -67.936297, display_name = ("Maria Behety")},
    ["Aurelia"] = { latitude = -53.899264, longitude = -68.368782, display_name = ("Aurelia")},
    ["San Luis"] = { latitude = -53.932653, longitude = -67.613643, display_name = ("San Luis")},
    ["Entre Rios"] = { latitude = -54.130726, longitude = -67.272466, display_name = ("Entre Rios")},
    ["Buenos Aires"] = { latitude = -54.162247, longitude = -67.647718, display_name = ("Buenos Aires")},
    ["La Ruby"] = { latitude = -54.156781, longitude = -67.726511, display_name = ("La Ruby")},
    ["Dos Hermanas"] = { latitude = -54.106919, longitude = -67.859184, display_name = ("Dos Hermanas")},
    ["Herminita"] = { latitude = -54.024204, longitude = -67.989174, display_name = ("Herminita")},
    ["La Constancia"] = { latitude = -54.028761, longitude = -68.294282, display_name = ("La Constancia")},
    ["La Libertad"] = { latitude = -54.096262, longitude = -68.287415, display_name = ("La Libertad")},
    ["Maria Laura"] = { latitude = -54.156995, longitude = -68.308915, display_name = ("Maria Laura")},
    ["Rosita"] = { latitude = -54.021954, longitude = -68.472809, display_name = ("Rosita")},
    ["Sierra Nevada"] = { latitude = -54.277743, longitude = -68.380788, display_name = ("Sierra Nevada")},
    ["Astrid"] = { latitude = -54.271704, longitude = -68.002457, display_name = ("Astrid")},
    ["Miramonte"] = { latitude = -54.210512, longitude = -67.744277, display_name = ("Miramonte")},
    ["La Criolla"] = { latitude = -54.214327, longitude = -67.510475, display_name = ("La Criolla")},
    ["Catalana"] = { latitude = -54.173351, longitude = -67.278732, display_name = ("Catalana")},
    ["Las Hijas"] = { latitude = -54.211277, longitude = -67.278314, display_name = ("Las Hijas")},
    ["La Ines"] = { latitude = -54.176529, longitude = -67.030391, display_name = ("La Ines")},
    ["Los Cerros"] = { latitude = -54.335686, longitude = -67.856079, display_name = ("Los Cerros")},
    ["La Rinconada"] = { latitude = -54.371214, longitude = -67.804838, display_name = ("La Rinconada")},
    ["Parador Yawen"] = { latitude = -54.368807, longitude = -67.716894, display_name = ("Parador Yawen")},
    ["Rivadavia"] = { latitude = -54.355029, longitude = -67.540597, display_name = ("Rivadavia")},
    ["Indiana"] = { latitude = -54.347025, longitude = -67.422838, display_name = ("Indiana")},
    ["Aserradero Americano"] = { latitude = -54.465721, longitude = -67.460604, display_name = ("Aserradero Americano")},
    ["La Laguna"] = { latitude = -54.423523, longitude = -67.553129, display_name = ("La Laguna")},
    ["Tonite"] = { latitude = -54.316052, longitude = -67.272485, display_name = ("Tonite")},
    ["Santa Ana"] = { latitude = -54.287321, longitude = -66.756918, display_name = ("Santa Ana")},
    ["Estancia Pirinaica"] = { latitude = -54.258577, longitude = -66.910229, display_name = ("Estancia Pirinaica")},
    ["Estancia Tepi"] = { latitude = -54.250082, longitude = -67.156238, display_name = ("Estancia Tepi")},
    ["Pilarica"] = { latitude = -54.114219, longitude = -68.189271, display_name = ("Pilarica")},
    ["Las Buitreras"] = { latitude = -51.725220, longitude = -70.128850, display_name = ("Las Buitreras")},
    ["Gobernador Mayer"] = { latitude = -51.348268, longitude = -70.291809, display_name = ("Gobernador Mayer")},
    ["Puente Blanco"] = { latitude = -51.892305, longitude = -71.594124, display_name = ("Puente Blanco")},
    ["La Leona"] = { latitude = -49.809259, longitude = -72.053103, display_name = ("La Leona")},
    ["La Esperanza"] = { latitude = -51.022092, longitude = -70.885743, display_name = ("La Esperanza")},
    ["La Barrancosa"] = { latitude = -50.199745, longitude = -70.213738, display_name = ("La Barrancosa")},
    ["Estancia San Ramon"] = { latitude = -50.196600, longitude = -69.906312, display_name = ("Estancia San Ramon")},
    ["Rincon"] = { latitude = -50.294309, longitude = -69.918822, display_name = ("Rincon")},
    ["La Peninsula"] = { latitude = -47.460896, longitude = -71.863000, display_name = ("La Peninsula")},
    ["La Peninsula"] = { latitude = -49.064440, longitude = -68.315556, display_name = ("La Peninsula")},
    ["Alma Gaucha"] = { latitude = -47.888418, longitude = -66.948698, display_name = ("Alma Gaucha")},
    ["Las Chacras"] = { latitude = -48.478452, longitude = -72.557845, display_name = ("Las Chacras")},
    ["Dorotea"] = { latitude = -51.647924, longitude = -72.339118, display_name = ("Dorotea")},
    ["Huertos Dorotea"] = { latitude = -51.618157, longitude = -72.332252, display_name = ("Huertos Dorotea")},
    ["Monte Aymond"] = { latitude = -52.146118, longitude = -69.516473, display_name = ("Monte Aymond")},
    ["Paso Laurita"] = { latitude = -51.689279, longitude = -72.298001, display_name = ("Paso Laurita")},
    ["Capilla"] = { latitude = -50.315016, longitude = -72.791469, display_name = ("Capilla")},
    ["Playita de Pescadores"] = { latitude = -49.216693, longitude = -72.984858, display_name = ("Playita de Pescadores")},
    ["Piedra del Fraile"] = { latitude = -49.227558, longitude = -73.013723, display_name = ("Piedra del Fraile")},
    ["Convivir"] = { latitude = -47.739251, longitude = -65.902745, display_name = ("Convivir")},
    ["Los Notros"] = { latitude = -50.328950, longitude = -72.242808, display_name = ("Los Notros")},
    ["Comandante Luis Piedrabuena"] = { latitude = -49.987276, longitude = -68.912991, display_name = ("Comandante Luis Piedrabuena")},
    ["Brazo Norte"] = { latitude = -52.056235, longitude = -70.005431, display_name = ("Brazo Norte")},
    ["Costa Centro"] = { latitude = -54.808705, longitude = -68.304695, display_name = ("Costa Centro")},
    ["Grota Antonio Gil"] = { latitude = -51.500582, longitude = -72.252174, display_name = ("Grota Antonio Gil")},
    ["Estancia Don Braulio"] = { latitude = -51.718385, longitude = -69.296456, display_name = ("Estancia Don Braulio")},
    ["Estancia Entre Rios"] = { latitude = -48.265328, longitude = -72.218942, display_name = ("Estancia Entre Rios")},
    ["Puerto Almanza"] = { latitude = -54.870409, longitude = -67.564406, display_name = ("Puerto Almanza")},
    ["Estancia Moat"] = { latitude = -54.973617, longitude = -66.745034, display_name = ("Estancia Moat")},
    ["Dungeness"] = { latitude = -52.394832, longitude = -68.431165, display_name = ("Dungeness")},
    ["Interior Dungeness"] = { latitude = -52.339613, longitude = -68.469438, display_name = ("Interior Dungeness")},
    ["Las Barrancas"] = { latitude = -52.318940, longitude = -68.587263, display_name = ("Las Barrancas")},
    ["Daniel Uno"] = { latitude = -52.342340, longitude = -68.666549, display_name = ("Daniel Uno")},
    ["Puesto Alamiro"] = { latitude = -52.228077, longitude = -69.023152, display_name = ("Puesto Alamiro")},
    ["Puesto Chocolate"] = { latitude = -52.176616, longitude = -69.453188, display_name = ("Puesto Chocolate")},
    ["Puesto Mulato"] = { latitude = -52.018599, longitude = -71.252006, display_name = ("Puesto Mulato")},
    ["Puesto Pioneros"] = { latitude = -52.026759, longitude = -71.540790, display_name = ("Puesto Pioneros")},
    ["Provincias Unidas"] = { latitude = -54.495274, longitude = -67.208691, display_name = ("Provincias Unidas")},
    ["Islas del Sur"] = { latitude = -54.518772, longitude = -67.201981, display_name = ("Islas del Sur")},
    ["Los Naranjos"] = { latitude = -54.513628, longitude = -67.207170, display_name = ("Los Naranjos")},
    ["Las Violetas"] = { latitude = -53.672897, longitude = -67.926644, display_name = ("Las Violetas")},
    ["Monte Aymond"] = { latitude = -52.102844, longitude = -69.533337, display_name = ("Monte Aymond")},
    ["Monte Dinero"] = { latitude = -52.323463, longitude = -68.559501, display_name = ("Monte Dinero")},
    ["Paso Rodolfo Roballos"] = { latitude = -47.133332, longitude = -71.850023, display_name = ("Paso Rodolfo Roballos")},
    ["Estancia Harberton"] = { latitude = -54.877762, longitude = -67.329386, display_name = ("Estancia Harberton")},
    ["Puerto Remolino"] = { latitude = -54.860454, longitude = -67.859145, display_name = ("Puerto Remolino")},
    ["Estancia San Justo"] = { latitude = -54.028494, longitude = -68.544922, display_name = ("Estancia San Justo")},
    ["Veintiocho de Noviembre"] = { latitude = -51.579567, longitude = -72.211488, display_name = ("Veintiocho de Noviembre")},
    ["Cancha Carrera"] = { latitude = -51.256817, longitude = -72.220102, display_name = ("Cancha Carrera")},
    ["Castillo"] = { latitude = -53.668110, longitude = -68.465338, display_name = ("Castillo")},
    ["Diego Ritchie"] = { latitude = -51.978050, longitude = -70.396159, display_name = ("Diego Ritchie")},
    ["Rospentek"] = { latitude = -51.665372, longitude = -72.145268, display_name = ("Rospentek")},
    ["La Marina"] = { latitude = -54.316667, longitude = -68.550002, display_name = ("La Marina")},
    ["Lapataia"] = { latitude = -54.855622, longitude = -68.577236, display_name = ("Lapataia")},
    ["Puerto Curtze"] = { latitude = -52.802679, longitude = -71.394580, display_name = ("Puerto Curtze")},
    ["Lazo"] = { latitude = -51.123177, longitude = -72.821959, display_name = ("Lazo")},
    ["Monte Alto"] = { latitude = -52.533167, longitude = -70.966644, display_name = ("Monte Alto")},
    ["Aserradero La Paciencia"] = { latitude = -54.369495, longitude = -69.162109, display_name = ("Aserradero La Paciencia")},
    ["Caleta Clarencia"] = { latitude = -52.892751, longitude = -70.106510, display_name = ("Caleta Clarencia")},
    ["La Quema"] = { latitude = -47.599999, longitude = -73.100039, display_name = ("La Quema")},
    ["Baquedano"] = { latitude = -53.266667, longitude = -69.983337, display_name = ("Baquedano")},
    ["Animales"] = { latitude = -47.833332, longitude = -73.466710, display_name = ("Animales")},
    ["San Juan"] = { latitude = -53.644662, longitude = -70.958192, display_name = ("San Juan")},
    ["Punta Espora"] = { latitude = -52.496010, longitude = -69.520669, display_name = ("Punta Espora")},
    ["Caleta Ferrari"] = { latitude = -54.857108, longitude = -68.819531, display_name = ("Caleta Ferrari")},
    ["Dumestre"] = { latitude = -51.788672, longitude = -72.467155, display_name = ("Dumestre")},
    ["Ekewern"] = { latitude = -52.866667, longitude = -69.516670, display_name = ("Ekewern")},
    ["Caleta Tortel"] = { latitude = -47.803471, longitude = -73.537689, display_name = ("Caleta Tortel")},
    ["Seguel"] = { latitude = -46.949489, longitude = -72.793955, display_name = ("Seguel")},
    ["Cochrane"] = { latitude = -47.254164, longitude = -72.573244, display_name = ("Cochrane")},
    ["Puerto Nuevo"] = { latitude = -53.348604, longitude = -69.436163, display_name = ("Puerto Nuevo")},
    ["Puerto Prat"] = { latitude = -51.636103, longitude = -72.648449, display_name = ("Puerto Prat")},
    ["Tres Puentes"] = { latitude = -53.119153, longitude = -70.875206, display_name = ("Tres Puentes")},
    ["Puerto Sara"] = { latitude = -52.623815, longitude = -70.199421, display_name = ("Puerto Sara")},
    ["Bombalot"] = { latitude = -52.269362, longitude = -71.185096, display_name = ("Bombalot")},
    ["Manantiales"] = { latitude = -52.545090, longitude = -69.420827, display_name = ("Manantiales")},
    ["Puerto Progreso"] = { latitude = -52.479026, longitude = -69.469991, display_name = ("Puerto Progreso")},
    ["Puerto Guadal"] = { latitude = -46.844571, longitude = -72.704322, display_name = ("Puerto Guadal")},
    ["Porvenir"] = { latitude = -53.295674, longitude = -70.368745, display_name = ("Porvenir")},
    ["Puerto Williams"] = { latitude = -54.935496, longitude = -67.606704, display_name = ("Puerto Williams")},
    ["Puerto Zenteno"] = { latitude = -52.785992, longitude = -70.804605, display_name = ("Puerto Zenteno")},
    ["Bersovio el Covacio"] = { latitude = -53.433333, longitude = -70.066671, display_name = ("Bersovio el Covacio")},
    ["Carpa"] = { latitude = -52.598697, longitude = -71.201065, display_name = ("Carpa")},
    ["Puerto Bertrand"] = { latitude = -47.015128, longitude = -72.827529, display_name = ("Puerto Bertrand")},
    ["Reserva Fiscal"] = { latitude = -52.650000, longitude = -69.350003, display_name = ("Reserva Fiscal")},
    ["Puerto Navarino"] = { latitude = -54.925972, longitude = -68.323515, display_name = ("Puerto Navarino")},
    ["Puerto Yartou"] = { latitude = -53.885965, longitude = -70.140251, display_name = ("Puerto Yartou")},
    ["Caleta Puerto Nuevo"] = { latitude = -47.676575, longitude = -73.081475, display_name = ("Caleta Puerto Nuevo")},
    ["Punta Catalina"] = { latitude = -52.548403, longitude = -68.757271, display_name = ("Punta Catalina")},
    ["Isla Dawson"] = { latitude = -53.890515, longitude = -70.685742, display_name = ("Isla Dawson")},
    ["Reserva Nacional Tamango"] = { latitude = -47.242465, longitude = -72.524046, display_name = ("Reserva Nacional Tamango")},
    ["Punta Mancilla"] = { latitude = -47.801782, longitude = -73.534946, display_name = ("Punta Mancilla")},
    ["Punta Rodolfo"] = { latitude = -47.806386, longitude = -73.548738, display_name = ("Punta Rodolfo")},
    ["Cerro Sombrero"] = { latitude = -52.776462, longitude = -69.290911, display_name = ("Cerro Sombrero")},
    ["Caleta Piedras"] = { latitude = -55.036483, longitude = -67.021939, display_name = ("Caleta Piedras")},
    ["Caleta Banner"] = { latitude = -55.019887, longitude = -66.925857, display_name = ("Caleta Banner")},
    ["Caleta Eugenia"] = { latitude = -54.932967, longitude = -67.312696, display_name = ("Caleta Eugenia")},
    ["Caleta Las Casas"] = { latitude = -55.270341, longitude = -66.643108, display_name = ("Caleta Las Casas")},
    ["Caleta Carlos"] = { latitude = -55.177094, longitude = -66.548154, display_name = ("Caleta Carlos")},
    ["Kemoa"] = { latitude = -55.187355, longitude = -67.457751, display_name = ("Kemoa")},
    ["Caleta Wulaia"] = { latitude = -55.047595, longitude = -68.147134, display_name = ("Caleta Wulaia")},
    ["Estancia Puerto Consuelo"] = { latitude = -51.604313, longitude = -72.656923, display_name = ("Estancia Puerto Consuelo")},
    ["Cerro Castillo"] = { latitude = -51.256249, longitude = -72.344706, display_name = ("Cerro Castillo")},
    ["Estancia Tranquilo"] = { latitude = -51.822437, longitude = -72.164226, display_name = ("Estancia Tranquilo")},
    ["Puerto Toro"] = { latitude = -51.412983, longitude = -73.079812, display_name = ("Puerto Toro")},
    ["Mina Guarello"] = { latitude = -50.367541, longitude = -75.337388, display_name = ("Mina Guarello")},
    ["Barrio Industrial"] = { latitude = -53.106547, longitude = -70.890182, display_name = ("Barrio Industrial")},
    ["Laguna Lynch"] = { latitude = -53.173128, longitude = -71.001413, display_name = ("Laguna Lynch")},
    ["Barranco Amarrillo"] = { latitude = -53.095566, longitude = -70.878004, display_name = ("Barranco Amarrillo")},
    ["Chabunco"] = { latitude = -52.988120, longitude = -70.814839, display_name = ("Chabunco")},
    ["Kon Aiken"] = { latitude = -52.947987, longitude = -70.844535, display_name = ("Kon Aiken")},
    ["Cabo Negro"] = { latitude = -52.974696, longitude = -70.825217, display_name = ("Cabo Negro")},
    ["Los Calafates"] = { latitude = -53.034450, longitude = -70.861454, display_name = ("Los Calafates")},
    ["Estancia Yendegaia"] = { latitude = -54.858305, longitude = -68.820743, display_name = ("Estancia Yendegaia")},
    ["Islas Ildefonso"] = { latitude = -55.838351, longitude = -69.348071, display_name = ("Islas Ildefonso")},
    ["Estancia Almirantazgo"] = { latitude = -54.461441, longitude = -69.087070, display_name = ("Estancia Almirantazgo")},
    ["Estancia Las Flores"] = { latitude = -53.685009, longitude = -68.702034, display_name = ("Estancia Las Flores")},
    ["Estancia El Estero"] = { latitude = -53.605440, longitude = -68.745337, display_name = ("Estancia El Estero")},
    ["Estancia La Laguna"] = { latitude = -53.470639, longitude = -68.724388, display_name = ("Estancia La Laguna")},
    ["Puerto Bories"] = { latitude = -51.689948, longitude = -72.536391, display_name = ("Puerto Bories")},
    ["Estancia Valdivia"] = { latitude = -51.503187, longitude = -72.688892, display_name = ("Estancia Valdivia")},
    ["Estancia Entre Vientos"] = { latitude = -52.778965, longitude = -71.278431, display_name = ("Estancia Entre Vientos")},
    ["Gobernador Philippi"] = { latitude = -52.747488, longitude = -71.009229, display_name = ("Gobernador Philippi")},
    ["Puerto Harris"] = { latitude = -53.835225, longitude = -70.453125, display_name = ("Puerto Harris")},
    ["Parroquia"] = { latitude = -47.253452, longitude = -72.581531, display_name = ("Parroquia")},
    ["Capilla Puerto Bertrand"] = { latitude = -47.015288, longitude = -72.828226, display_name = ("Capilla Puerto Bertrand")},
    ["Parroquia"] = { latitude = -53.301643, longitude = -70.437442, display_name = ("Parroquia")},
    ["Estancia Draga"] = { latitude = -53.415109, longitude = -70.052471, display_name = ("Estancia Draga")},
    ["Los Canelos"] = { latitude = -53.464275, longitude = -70.179761, display_name = ("Los Canelos")},
    ["Estancia Armonia"] = { latitude = -53.335002, longitude = -69.774712, display_name = ("Estancia Armonia")},
    ["Estancia Jeanette Naria"] = { latitude = -53.314245, longitude = -69.731072, display_name = ("Estancia Jeanette Naria")},
    ["Estancia Maria Soledad"] = { latitude = -53.325603, longitude = -69.675789, display_name = ("Estancia Maria Soledad")},
    ["Estancia Zenia"] = { latitude = -53.318641, longitude = -69.701763, display_name = ("Estancia Zenia")},
    ["Estancia Los Copihues"] = { latitude = -53.336877, longitude = -69.456837, display_name = ("Estancia Los Copihues")},
    ["Caleta Los Crillos"] = { latitude = -53.339076, longitude = -69.608123, display_name = ("Caleta Los Crillos")},
    ["Estancia Teresita"] = { latitude = -53.330793, longitude = -69.761221, display_name = ("Estancia Teresita")},
    ["Onaisin"] = { latitude = -53.387012, longitude = -69.280914, display_name = ("Onaisin")},
    ["La Rinconada"] = { latitude = -46.757430, longitude = -72.898063, display_name = ("La Rinconada")},
    ["Miraflores"] = { latitude = -46.755841, longitude = -72.872506, display_name = ("Miraflores")},
    ["Ocho Hermanas"] = { latitude = -47.120720, longitude = -72.881843, display_name = ("Ocho Hermanas")},
    ["Puesto Tejuela"] = { latitude = -47.176759, longitude = -72.366164, display_name = ("Puesto Tejuela")},
    ["Potrero Las Vacas"] = { latitude = -47.049537, longitude = -72.383845, display_name = ("Potrero Las Vacas")},
    ["Los Carreras"] = { latitude = -47.040647, longitude = -72.347796, display_name = ("Los Carreras")},
    ["Ripidos del Baker"] = { latitude = -47.042200, longitude = -72.818297, display_name = ("Ripidos del Baker")},
    ["Los Tres Limones"] = { latitude = -47.303094, longitude = -72.970077, display_name = ("Los Tres Limones")},
    ["Los Leones"] = { latitude = -47.213851, longitude = -72.869481, display_name = ("Los Leones")},
    ["Valle Chacabuco"] = { latitude = -47.117020, longitude = -72.486045, display_name = ("Valle Chacabuco")},
    ["La Piedra Blanca"] = { latitude = -47.296636, longitude = -72.916975, display_name = ("La Piedra Blanca")},
    ["Rio Nef"] = { latitude = -47.112893, longitude = -72.925101, display_name = ("Rio Nef")},
    ["Valle Grande"] = { latitude = -47.281788, longitude = -72.727980, display_name = ("Valle Grande")},
    ["Poco a Poco"] = { latitude = -47.333421, longitude = -72.755587, display_name = ("Poco a Poco")},
    ["Calluqueo"] = { latitude = -47.657245, longitude = -72.477491, display_name = ("Calluqueo")},
    ["La Rinconada"] = { latitude = -47.315269, longitude = -73.036430, display_name = ("La Rinconada")},
    ["La Juanita"] = { latitude = -47.336737, longitude = -72.174784, display_name = ("La Juanita")},
    ["Bajo Pascua"] = { latitude = -48.228838, longitude = -73.215855, display_name = ("Bajo Pascua")},
    ["Caleta Puerto Alegre"] = { latitude = -47.747700, longitude = -73.241568, display_name = ("Caleta Puerto Alegre")},
    ["La Esperanza"] = { latitude = -47.470873, longitude = -72.539033, display_name = ("La Esperanza")},
    ["Laguna Larga"] = { latitude = -47.468441, longitude = -72.804897, display_name = ("Laguna Larga")},
    ["Valle La Tranquera"] = { latitude = -47.607010, longitude = -72.930341, display_name = ("Valle La Tranquera")},
    ["La Peligrosa"] = { latitude = -47.380286, longitude = -72.247977, display_name = ("La Peligrosa")},
    ["La Delicia"] = { latitude = -47.478502, longitude = -72.482797, display_name = ("La Delicia")},
    ["La Rinconada"] = { latitude = -47.566979, longitude = -72.523841, display_name = ("La Rinconada")},
    ["Caleta Yungay"] = { latitude = -47.933368, longitude = -73.324544, display_name = ("Caleta Yungay")},
    ["Caleta Cachorro"] = { latitude = -47.704420, longitude = -73.090578, display_name = ("Caleta Cachorro")},
    ["Santa Margarita"] = { latitude = -47.332510, longitude = -72.847759, display_name = ("Santa Margarita")},
    ["La Isla"] = { latitude = -47.332723, longitude = -72.870774, display_name = ("La Isla")},
    ["Arroyo Chueco"] = { latitude = -47.337493, longitude = -72.962271, display_name = ("Arroyo Chueco")},
    ["Lago Quetru"] = { latitude = -48.146783, longitude = -73.107067, display_name = ("Lago Quetru")},
    ["Puesto Nutria"] = { latitude = -52.544696, longitude = -72.031135, display_name = ("Puesto Nutria")},
    ["Cacique Mulato"] = { latitude = -52.456263, longitude = -71.338350, display_name = ("Cacique Mulato")},
    ["Puerto Slight"] = { latitude = -46.805303, longitude = -75.582010, display_name = ("Puerto Slight")},
    ["Pesquera Mac Lean"] = { latitude = -54.940111, longitude = -67.712389, display_name = ("Pesquera Mac Lean")},
    ["Caleta Douglas"] = { latitude = -55.174065, longitude = -68.103992, display_name = ("Caleta Douglas")},
    ["Estancia Dora"] = { latitude = -52.198713, longitude = -72.555850, display_name = ("Estancia Dora")},
    ["Estancia Lago Pinto"] = { latitude = -52.060065, longitude = -72.399181, display_name = ("Estancia Lago Pinto")},
    ["Estancia Puerto Aldea"] = { latitude = -52.138100, longitude = -72.654624, display_name = ("Estancia Puerto Aldea")},
    ["Estancia Linacre"] = { latitude = -52.041183, longitude = -72.664249, display_name = ("Estancia Linacre")},
    ["Estancia Solar"] = { latitude = -51.930372, longitude = -72.362387, display_name = ("Estancia Solar")},
    ["Estancia Tres Pasos"] = { latitude = -51.420599, longitude = -72.466943, display_name = ("Estancia Tres Pasos")},
    ["Estancia Florita"] = { latitude = -52.815156, longitude = -71.401451, display_name = ("Estancia Florita")},
    ["Estancia Santa Rosa de Lima"] = { latitude = -52.981408, longitude = -71.840572, display_name = ("Estancia Santa Rosa de Lima")},
    ["Ankel"] = { latitude = -52.906627, longitude = -71.021444, display_name = ("Ankel")},
    ["Estancia Don Pancho"] = { latitude = -52.689704, longitude = -71.904227, display_name = ("Estancia Don Pancho")},
    ["Estancia Entre Vientos"] = { latitude = -52.744968, longitude = -71.450824, display_name = ("Estancia Entre Vientos")},
    ["Estancia Eva"] = { latitude = -52.694340, longitude = -71.678652, display_name = ("Estancia Eva")},
    ["Caleta"] = { latitude = -53.056841, longitude = -71.283647, display_name = ("Caleta")},
    ["Ponsomby"] = { latitude = -52.654276, longitude = -71.470338, display_name = ("Ponsomby")},
    ["Estancia Chilenita"] = { latitude = -52.701817, longitude = -71.804749, display_name = ("Estancia Chilenita")},
    ["Estancia Mina Rica"] = { latitude = -53.020308, longitude = -71.048847, display_name = ("Estancia Mina Rica")},
    ["Estancia Gloria"] = { latitude = -52.651371, longitude = -71.489438, display_name = ("Estancia Gloria")},
    ["Puesto Once"] = { latitude = -52.677206, longitude = -71.948967, display_name = ("Puesto Once")},
    ["Estancia El Salto"] = { latitude = -52.690165, longitude = -71.637657, display_name = ("Estancia El Salto")},
    ["Estancia Rocallosa"] = { latitude = -52.658995, longitude = -71.973670, display_name = ("Estancia Rocallosa")},
    ["Estancia Iniverno"] = { latitude = -52.888426, longitude = -71.607353, display_name = ("Estancia Iniverno")},
    ["Estancia San Carlos"] = { latitude = -52.693495, longitude = -71.591260, display_name = ("Estancia San Carlos")},
    ["Puesto Garay"] = { latitude = -52.669103, longitude = -72.093180, display_name = ("Puesto Garay")},
    ["Puerto Percy"] = { latitude = -52.900763, longitude = -70.267330, display_name = ("Puerto Percy")},
    ["Estancia la Oriental"] = { latitude = -47.804487, longitude = -72.094099, display_name = ("Estancia la Oriental")},
    ["Iglesia Puerto Williams"] = { latitude = -54.934527, longitude = -67.610928, display_name = ("Iglesia Puerto Williams")},
    ["Iglesia Cerro Sombrero"] = { latitude = -52.776627, longitude = -69.288748, display_name = ("Iglesia Cerro Sombrero")},
    ["Punta Passage"] = { latitude = -50.766254, longitude = -74.420393, display_name = ("Punta Passage")},
    ["Puerto Gray"] = { latitude = -48.919049, longitude = -74.313453, display_name = ("Puerto Gray")},
    ["Peninsula Baquedano"] = { latitude = -48.722342, longitude = -74.322657, display_name = ("Peninsula Baquedano")},
    ["Caleta Lackawana"] = { latitude = -49.168349, longitude = -74.408650, display_name = ("Caleta Lackawana")},
    ["Caleta Lucas"] = { latitude = -49.000589, longitude = -74.407219, display_name = ("Caleta Lucas")},
    ["Caleta Hoskyn"] = { latitude = -48.943895, longitude = -74.414108, display_name = ("Caleta Hoskyn")},
    ["Caleta Level"] = { latitude = -49.118040, longitude = -74.355904, display_name = ("Caleta Level")},
    ["Peninsula Broome"] = { latitude = -49.372174, longitude = -74.414700, display_name = ("Peninsula Broome")},
    ["Caleta Grau"] = { latitude = -49.340337, longitude = -74.410710, display_name = ("Caleta Grau")},
    ["Peninsula Elliott"] = { latitude = -49.338714, longitude = -74.416246, display_name = ("Peninsula Elliott")},
    ["Punta Porpoise"] = { latitude = -50.740802, longitude = -74.519732, display_name = ("Punta Porpoise")},
    ["Caleta Rayo"] = { latitude = -50.758314, longitude = -74.537411, display_name = ("Caleta Rayo")},
    ["Punta Cutter"] = { latitude = -50.711786, longitude = -74.517988, display_name = ("Punta Cutter")},
    ["Puerto Micaela"] = { latitude = -49.511862, longitude = -74.271064, display_name = ("Puerto Micaela")},
    ["Caleta Lucrecia"] = { latitude = -49.511862, longitude = -74.266193, display_name = ("Caleta Lucrecia")},
    ["Agua Fresca Sur"] = { latitude = -53.514716, longitude = -70.951519, display_name = ("Agua Fresca Sur")},
    ["Templo San Juan Bosco"] = { latitude = -51.736955, longitude = -72.494012, display_name = ("Templo San Juan Bosco")},
    ["Capilla Buen Pastor"] = { latitude = -51.731365, longitude = -72.490678, display_name = ("Capilla Buen Pastor")},
    ["Cerro Pancho"] = { latitude = -51.686884, longitude = -72.492078, display_name = ("Cerro Pancho")},
    ["Endesa"] = { latitude = -51.731143, longitude = -72.486350, display_name = ("Endesa")},
    ["Etnias Unidas"] = { latitude = -51.739156, longitude = -72.481760, display_name = ("Etnias Unidas")},
    ["Nueva Esperanza"] = { latitude = -51.733536, longitude = -72.479856, display_name = ("Nueva Esperanza")},
    ["Octavio Castro"] = { latitude = -51.720114, longitude = -72.494355, display_name = ("Octavio Castro")},
    ["Natales Antiguo"] = { latitude = -51.723359, longitude = -72.491379, display_name = ("Natales Antiguo")},
    ["Nueva Patagonia"] = { latitude = -51.732517, longitude = -72.505584, display_name = ("Nueva Patagonia")},
    ["Chile Nuevo"] = { latitude = -51.735838, longitude = -72.490441, display_name = ("Chile Nuevo")},
    ["Costanera"] = { latitude = -51.734219, longitude = -72.504367, display_name = ("Costanera")},
    ["Morro Chico"] = { latitude = -52.055267, longitude = -71.428107, display_name = ("Morro Chico")},
    ["Juan Pablo II"] = { latitude = -51.740819, longitude = -72.491847, display_name = ("Juan Pablo II")},
    ["Eduardo Frei Montalva"] = { latitude = -51.741191, longitude = -72.486353, display_name = ("Eduardo Frei Montalva")},
    ["Torres del Paine"] = { latitude = -51.738845, longitude = -72.484287, display_name = ("Torres del Paine")},
    ["Estancia Rosario"] = { latitude = -51.734992, longitude = -72.527791, display_name = ("Estancia Rosario")},
    ["Estancia Mercedes"] = { latitude = -51.760973, longitude = -72.837520, display_name = ("Estancia Mercedes")},
    ["Iglesia Metodista Chile Nuevo"] = { latitude = -51.734920, longitude = -72.491168, display_name = ("Iglesia Metodista Chile Nuevo")},
    ["Puesto Gallego Chico"] = { latitude = -52.024748, longitude = -70.735214, display_name = ("Puesto Gallego Chico")},
    ["Punta Yamana"] = { latitude = -54.967033, longitude = -69.031027, display_name = ("Punta Yamana")},
    ["Laguna Amarga"] = { latitude = -50.979515, longitude = -72.800459, display_name = ("Laguna Amarga")},
    ["Caleta Dos de Mayo"] = { latitude = -54.866105, longitude = -68.691247, display_name = ("Caleta Dos de Mayo")},
    ["Punta Flores"] = { latitude = -54.921092, longitude = -68.253785, display_name = ("Punta Flores")},
    ["Caleta Fique"] = { latitude = -54.908449, longitude = -68.212819, display_name = ("Caleta Fique")},
    ["Punta Bartlett"] = { latitude = -54.903021, longitude = -68.222947, display_name = ("Punta Bartlett")},
    ["Punta Moya"] = { latitude = -54.918267, longitude = -68.233847, display_name = ("Punta Moya")},
    ["Caleta Segura"] = { latitude = -54.916540, longitude = -68.212304, display_name = ("Caleta Segura")},
    ["Puerto Corriente"] = { latitude = -54.989434, longitude = -68.365872, display_name = ("Puerto Corriente")},
    ["Caleta Molinare"] = { latitude = -55.000482, longitude = -68.352353, display_name = ("Caleta Molinare")},
    ["Caleta Domingo"] = { latitude = -55.039458, longitude = -68.317066, display_name = ("Caleta Domingo")},
    ["Caleta Contreras"] = { latitude = -54.873520, longitude = -68.775852, display_name = ("Caleta Contreras")},
    ["Caleta Tanswuani"] = { latitude = -55.072564, longitude = -68.327241, display_name = ("Caleta Tanswuani")},
    ["Punta Tasbani"] = { latitude = -55.079857, longitude = -68.329511, display_name = ("Punta Tasbani")},
    ["Puerto Grandi"] = { latitude = -55.211109, longitude = -67.921785, display_name = ("Puerto Grandi")},
    ["Puerto Micalvito"] = { latitude = -55.195510, longitude = -67.867415, display_name = ("Puerto Micalvito")},
    ["Estancia La Florida"] = { latitude = -48.796935, longitude = -72.551855, display_name = ("Estancia La Florida")},
    ["Caleta Bevan"] = { latitude = -55.229353, longitude = -67.440832, display_name = ("Caleta Bevan")},
    ["Caleta Castillo"] = { latitude = -55.230168, longitude = -68.255568, display_name = ("Caleta Castillo")},
    ["Caleta Canacus"] = { latitude = -55.219714, longitude = -68.298607, display_name = ("Caleta Canacus")},
    ["Punta Carpintero"] = { latitude = -55.221301, longitude = -68.210509, display_name = ("Punta Carpintero")},
    ["Punta Morrison"] = { latitude = -53.836012, longitude = -70.436359, display_name = ("Punta Morrison")},
    ["Punta Tesner"] = { latitude = -53.836106, longitude = -70.421532, display_name = ("Punta Tesner")},
    ["Punta Bernabe"] = { latitude = -53.838423, longitude = -70.427540, display_name = ("Punta Bernabe")},
    ["Punta Rafael"] = { latitude = -53.855256, longitude = -70.431900, display_name = ("Punta Rafael")},
    ["Punta Arboleda"] = { latitude = -53.864722, longitude = -70.420742, display_name = ("Punta Arboleda")},
    ["Estancia La Cumbre"] = { latitude = -50.734411, longitude = -72.409431, display_name = ("Estancia La Cumbre")},
    ["Isla Victor"] = { latitude = -47.176348, longitude = -72.114299, display_name = ("Isla Victor")},
    ["Iglesia Exumenica"] = { latitude = -52.427218, longitude = -71.415102, display_name = ("Iglesia Exumenica")},
    ["Los Coihues"] = { latitude = -51.535730, longitude = -72.339195, display_name = ("Los Coihues")},
    ["Las Lengas"] = { latitude = -51.537738, longitude = -72.343467, display_name = ("Las Lengas")},
    ["Islas Malvinas"] = { latitude = -51.534273, longitude = -72.329881, display_name = ("Islas Malvinas")},
    ["Santa Cruz"] = { latitude = -51.539346, longitude = -72.334636, display_name = ("Santa Cruz")},
    ["Las Margaritas"] = { latitude = -51.540984, longitude = -72.337259, display_name = ("Las Margaritas")},
    ["Don Bosco"] = { latitude = -51.542416, longitude = -72.340417, display_name = ("Don Bosco")},
    ["Los Lupinos"] = { latitude = -51.542482, longitude = -72.342855, display_name = ("Los Lupinos")},
    ["Hielos Continentales"] = { latitude = -51.541815, longitude = -72.346996, display_name = ("Hielos Continentales")},
    ["Huertos Familiares"] = { latitude = -51.698345, longitude = -72.488467, display_name = ("Huertos Familiares")},
    ["Santa Flavia"] = { latitude = -51.545274, longitude = -72.353929, display_name = ("Santa Flavia")},
    ["Don Bosco"] = { latitude = -51.587917, longitude = -72.216739, display_name = ("Don Bosco")},
    ["Frontera Austral"] = { latitude = -51.586442, longitude = -72.212121, display_name = ("Frontera Austral")},
    ["Norte"] = { latitude = -51.576708, longitude = -72.212228, display_name = ("Norte")},
    ["Quiroga"] = { latitude = -51.583425, longitude = -72.212723, display_name = ("Quiroga")},
    ["Santa Cruz"] = { latitude = -51.581889, longitude = -72.215229, display_name = ("Santa Cruz")},
    ["Julia Dufour"] = { latitude = -51.547098, longitude = -72.238197, display_name = ("Julia Dufour")},
    ["Estancia Don Doimo"] = { latitude = -52.789670, longitude = -71.091457, display_name = ("Estancia Don Doimo")},
    ["Estancia La Invernada"] = { latitude = -52.278024, longitude = -71.347983, display_name = ("Estancia La Invernada")},
    ["Estancia Creek"] = { latitude = -52.027585, longitude = -71.835858, display_name = ("Estancia Creek")},
    ["Estancia Berta"] = { latitude = -52.009169, longitude = -72.038526, display_name = ("Estancia Berta")},
    ["Estancia Flores"] = { latitude = -51.962168, longitude = -72.145017, display_name = ("Estancia Flores")},
    ["Estancia Don Marcelino"] = { latitude = -51.758165, longitude = -72.247360, display_name = ("Estancia Don Marcelino")},
    ["Estancia El Palenque"] = { latitude = -51.728046, longitude = -72.267951, display_name = ("Estancia El Palenque")},
    ["Estancia Aurelia Del Carmen"] = { latitude = -52.748023, longitude = -71.013050, display_name = ("Estancia Aurelia Del Carmen")},
    ["Agroturismo Los Manantiales"] = { latitude = -51.216136, longitude = -72.455777, display_name = ("Agroturismo Los Manantiales")},
    ["Shuka"] = { latitude = -51.738760, longitude = -72.479123, display_name = ("Shuka")},
    ["Terranova"] = { latitude = -51.738501, longitude = -72.477245, display_name = ("Terranova")},
    ["Estadio"] = { latitude = -51.726139, longitude = -72.484459, display_name = ("Estadio")},
    ["Corvi"] = { latitude = -51.733180, longitude = -72.491331, display_name = ("Corvi")},
    ["Puerto Toro"] = { latitude = -55.083060, longitude = -67.075865, display_name = ("Puerto Toro")},
    ["Comercial"] = { latitude = -51.537104, longitude = -72.336783, display_name = ("Comercial")},
    ["Los Mineros"] = { latitude = -51.537769, longitude = -72.332485, display_name = ("Los Mineros")},
    ["Estancia Skyring"] = { latitude = -52.549343, longitude = -71.958305, display_name = ("Estancia Skyring")},
    ["Cerro Guido"] = { latitude = -50.940219, longitude = -72.454272, display_name = ("Cerro Guido")},
    ["Estancia Dynevor"] = { latitude = -52.532138, longitude = -72.327485, display_name = ("Estancia Dynevor")},
    ["Caleta Jara"] = { latitude = -52.533409, longitude = -72.199463, display_name = ("Caleta Jara")},
    ["Caleta Aihuaia"] = { latitude = -54.945317, longitude = -68.431300, display_name = ("Caleta Aihuaia")},
    ["Caleta Letier"] = { latitude = -54.942406, longitude = -68.462551, display_name = ("Caleta Letier")},
    ["Caleta Molinate"] = { latitude = -55.009354, longitude = -68.334574, display_name = ("Caleta Molinate")},
    ["Caleta Yamana"] = { latitude = -54.965738, longitude = -69.028905, display_name = ("Caleta Yamana")},
    ["Caleta Sonia"] = { latitude = -54.960376, longitude = -69.010499, display_name = ("Caleta Sonia")},
    ["Caleta Olla"] = { latitude = -54.940564, longitude = -69.146661, display_name = ("Caleta Olla")},
    ["Caleta Evening"] = { latitude = -54.959179, longitude = -69.479897, display_name = ("Caleta Evening")},
    ["Caleta Morning"] = { latitude = -54.920202, longitude = -69.496822, display_name = ("Caleta Morning")},
    ["Estancia Santa Rosa"] = { latitude = -54.903466, longitude = -68.157187, display_name = ("Estancia Santa Rosa")},
    ["Caleta Mejillones"] = { latitude = -54.900733, longitude = -68.002317, display_name = ("Caleta Mejillones")},
    ["Puerto Mejillones"] = { latitude = -54.904021, longitude = -67.989808, display_name = ("Puerto Mejillones")},
    ["Puerto Hondo"] = { latitude = -55.079105, longitude = -70.032788, display_name = ("Puerto Hondo")},
    ["Puerto Almeida"] = { latitude = -54.854675, longitude = -70.657038, display_name = ("Puerto Almeida")},
    ["Puerto Ballenas"] = { latitude = -54.847283, longitude = -70.550351, display_name = ("Puerto Ballenas")},
    ["Caleta Aguada"] = { latitude = -55.072852, longitude = -70.840915, display_name = ("Caleta Aguada")},
    ["Isla Treble"] = { latitude = -55.092597, longitude = -71.035583, display_name = ("Isla Treble")},
    ["Caleta Doris"] = { latitude = -54.979973, longitude = -71.139211, display_name = ("Caleta Doris")},
    ["Isla Selma"] = { latitude = -54.921881, longitude = -71.475756, display_name = ("Isla Selma")},
    ["Cabo Fletcher"] = { latitude = -54.653279, longitude = -71.308521, display_name = ("Cabo Fletcher")},
    ["Isla Astorga"] = { latitude = -54.597906, longitude = -72.077724, display_name = ("Isla Astorga")},
    ["Caleta Brecknock"] = { latitude = -54.551152, longitude = -71.877160, display_name = ("Caleta Brecknock")},
    ["Puerto Consuelo"] = { latitude = -54.545932, longitude = -71.516158, display_name = ("Puerto Consuelo")},
    ["Puerto Tanteo"] = { latitude = -54.413299, longitude = -71.499703, display_name = ("Puerto Tanteo")},
    ["Puerto King"] = { latitude = -54.422819, longitude = -71.221705, display_name = ("Puerto King")},
    ["Caleta Wilfredo"] = { latitude = -55.870673, longitude = -67.114708, display_name = ("Caleta Wilfredo")},
    ["Rio Grande"] = { latitude = -53.791510, longitude = -67.704934, display_name = ("Rio Grande")},
    ["Estancia Puerto Rancho"] = { latitude = -55.046937, longitude = -66.561407, display_name = ("Estancia Puerto Rancho")},
    ["Vertedero"] = { latitude = -49.115832, longitude = -74.390374, display_name = ("Vertedero")},
    ["Los Pioneros"] = { latitude = -51.728856, longitude = -72.476261, display_name = ("Los Pioneros")},
    ["Punta Sedger"] = { latitude = -53.661658, longitude = -70.956762, display_name = ("Punta Sedger")},
    ["Laguna Azul"] = { latitude = -50.853998, longitude = -72.700889, display_name = ("Laguna Azul")},
    ["Gemita"] = { latitude = -50.874411, longitude = -72.703797, display_name = ("Gemita")},
    ["Lago Sarmiento"] = { latitude = -51.005428, longitude = -72.599023, display_name = ("Lago Sarmiento")},
    ["San Luis"] = { latitude = -51.014177, longitude = -72.500274, display_name = ("San Luis")},
    ["Estancia Entre Lagos"] = { latitude = -51.065949, longitude = -72.549804, display_name = ("Estancia Entre Lagos")},
    ["San Antonio"] = { latitude = -51.152146, longitude = -72.520213, display_name = ("San Antonio")},
    ["Isla Lincoln"] = { latitude = -51.168967, longitude = -72.597499, display_name = ("Isla Lincoln")},
    ["Albergue Chileno"] = { latitude = -50.973130, longitude = -72.875996, display_name = ("Albergue Chileno")},
    ["Hotel Lago Grey"] = { latitude = -51.122880, longitude = -73.116350, display_name = ("Hotel Lago Grey")},
    ["Administracia Parque Torres del Paine"] = { latitude = -51.175866, longitude = -72.954499, display_name = ("Administracia Parque Torres del Paine")},
    ["Complejo Torres del Paine"] = { latitude = -51.386627, longitude = -72.794637, display_name = ("Complejo Torres del Paine")},
    ["Santucci"] = { latitude = -51.307294, longitude = -72.646857, display_name = ("Santucci")},
    ["Vega Castillo"] = { latitude = -51.303816, longitude = -72.647865, display_name = ("Vega Castillo")},
    ["Hotel Rubens"] = { latitude = -52.039287, longitude = -71.941053, display_name = ("Hotel Rubens")},
    ["Aserradero Monte Alto"] = { latitude = -52.051198, longitude = -71.894720, display_name = ("Aserradero Monte Alto")},
    ["Dos Lagunas"] = { latitude = -51.511928, longitude = -72.509151, display_name = ("Dos Lagunas")},
    ["La Esperanza"] = { latitude = -48.242630, longitude = -73.516136, display_name = ("La Esperanza")},
    ["Fiordo Steffens"] = { latitude = -47.622366, longitude = -73.657725, display_name = ("Fiordo Steffens")},
    ["La Brisca"] = { latitude = -47.807504, longitude = -73.526652, display_name = ("La Brisca")},
    ["Isla San Francisco"] = { latitude = -48.067385, longitude = -73.580907, display_name = ("Isla San Francisco")},
    ["Ventisquero Jorge Montt"] = { latitude = -48.194167, longitude = -73.464083, display_name = ("Ventisquero Jorge Montt")},
    ["Pampa Guanaco"] = { latitude = -54.051056, longitude = -68.803126, display_name = ("Pampa Guanaco")},
    ["Cabeza de Mar"] = { latitude = -52.801717, longitude = -71.001175, display_name = ("Cabeza de Mar")},
    ["Guairabo"] = { latitude = -53.338356, longitude = -70.956558, display_name = ("Guairabo")},
    ["Agua Fresca Norte"] = { latitude = -53.459476, longitude = -70.973166, display_name = ("Agua Fresca Norte")},
    ["Arroyo El Sucio"] = { latitude = -48.191174, longitude = -72.309602, display_name = ("Arroyo El Sucio")},
    ["Campamento Mayer"] = { latitude = -48.195712, longitude = -72.337717, display_name = ("Campamento Mayer")},
    ["Entrada Mayer"] = { latitude = -48.209324, longitude = -72.323345, display_name = ("Entrada Mayer")},
    ["Pampa Grande"] = { latitude = -48.296577, longitude = -72.493432, display_name = ("Pampa Grande")},
    ["Las Claras"] = { latitude = -48.295892, longitude = -72.476684, display_name = ("Las Claras")},
    ["Las Margaritas"] = { latitude = -48.305712, longitude = -72.436809, display_name = ("Las Margaritas")},
    ["Pampa Chica"] = { latitude = -48.316616, longitude = -72.503967, display_name = ("Pampa Chica")},
    ["La Centinela"] = { latitude = -48.395002, longitude = -72.649259, display_name = ("La Centinela")},
    ["Santa Eva"] = { latitude = -48.430265, longitude = -72.635015, display_name = ("Santa Eva")},
    ["Viejo Roble"] = { latitude = -48.783798, longitude = -72.759755, display_name = ("Viejo Roble")},
    ["Candelario Mancilla"] = { latitude = -48.868596, longitude = -72.743227, display_name = ("Candelario Mancilla")},
    ["Ventisquero Chico"] = { latitude = -48.863020, longitude = -72.897756, display_name = ("Ventisquero Chico")},
    ["Las Carmelas"] = { latitude = -48.900666, longitude = -72.979865, display_name = ("Las Carmelas")},
    ["Isla Central"] = { latitude = -48.774391, longitude = -72.895820, display_name = ("Isla Central")},
    ["La Lezna"] = { latitude = -48.706184, longitude = -73.023382, display_name = ("La Lezna")},
    ["Bajo Esperanza"] = { latitude = -48.421037, longitude = -72.980907, display_name = ("Bajo Esperanza")},
    ["Laguna San Rafael"] = { latitude = -46.639497, longitude = -73.872997, display_name = ("Laguna San Rafael")},
    ["Los Maitenes"] = { latitude = -46.855073, longitude = -72.379703, display_name = ("Los Maitenes")},
    ["Laguna La Manga"] = { latitude = -46.877339, longitude = -72.701664, display_name = ("Laguna La Manga")},
    ["Pampa Seguel"] = { latitude = -46.986004, longitude = -72.788397, display_name = ("Pampa Seguel")},
    ["Lago Plomo Sur"] = { latitude = -47.010740, longitude = -72.988804, display_name = ("Lago Plomo Sur")},
    ["Lago Plomo Norte"] = { latitude = -47.001796, longitude = -72.979792, display_name = ("Lago Plomo Norte")},
    ["Lago Bertrand"] = { latitude = -46.901836, longitude = -72.877137, display_name = ("Lago Bertrand")},
    ["Lago Negro"] = { latitude = -46.895252, longitude = -72.789063, display_name = ("Lago Negro")},
    ["Ensenada Villarino"] = { latitude = -54.922173, longitude = -67.779399, display_name = ("Ensenada Villarino")},
    ["Caleta Lewaia"] = { latitude = -54.929608, longitude = -68.338882, display_name = ("Caleta Lewaia")},
    ["Arroyo Los Toros"] = { latitude = -47.818186, longitude = -72.730822, display_name = ("Arroyo Los Toros")},
    ["Los CIpreses"] = { latitude = -47.485511, longitude = -72.940629, display_name = ("Los CIpreses")},
    ["La Esperanza Sur"] = { latitude = -47.481088, longitude = -72.908613, display_name = ("La Esperanza Sur")},
    ["Buena Vista Sur"] = { latitude = -47.496748, longitude = -72.894065, display_name = ("Buena Vista Sur")},
    ["La Herradura"] = { latitude = -47.329806, longitude = -72.589276, display_name = ("La Herradura")},
    ["Laguna Esmeralda"] = { latitude = -47.320104, longitude = -72.576745, display_name = ("Laguna Esmeralda")},
    ["Puerto Nuevo"] = { latitude = -47.265819, longitude = -72.482523, display_name = ("Puerto Nuevo")},
    ["Laguna Escondida"] = { latitude = -47.243732, longitude = -72.388162, display_name = ("Laguna Escondida")},
    ["Frutillar"] = { latitude = -47.246107, longitude = -72.428696, display_name = ("Frutillar")},
    ["Dos Arroyos"] = { latitude = -47.242130, longitude = -72.329903, display_name = ("Dos Arroyos")},
    ["La Guachita"] = { latitude = -47.242450, longitude = -72.285947, display_name = ("La Guachita")},
    ["La Montosa"] = { latitude = -47.299292, longitude = -72.027116, display_name = ("La Montosa")},
    ["La Soledad"] = { latitude = -47.381977, longitude = -72.207754, display_name = ("La Soledad")},
    ["Santa Rosa"] = { latitude = -47.465831, longitude = -72.446287, display_name = ("Santa Rosa")},
    ["Seis Hermanos"] = { latitude = -47.463175, longitude = -72.542154, display_name = ("Seis Hermanos")},
    ["La Pachacha"] = { latitude = -47.454064, longitude = -72.577453, display_name = ("La Pachacha")},
    ["Sin Huella"] = { latitude = -47.432107, longitude = -72.589201, display_name = ("Sin Huella")},
    ["La Primavera"] = { latitude = -51.447714, longitude = -72.228270, display_name = ("La Primavera")},
    ["Tres Marias"] = { latitude = -51.318977, longitude = -72.176226, display_name = ("Tres Marias")},
    ["La Fermina"] = { latitude = -51.690785, longitude = -72.083675, display_name = ("La Fermina")},
    ["Laguna Larga"] = { latitude = -51.808214, longitude = -71.882625, display_name = ("Laguna Larga")},
    ["Rincon de Los Morros"] = { latitude = -51.920101, longitude = -71.497324, display_name = ("Rincon de Los Morros")},
    ["Pampa"] = { latitude = -51.949091, longitude = -71.168665, display_name = ("Pampa")},
    ["Pali Aike"] = { latitude = -51.983025, longitude = -69.748869, display_name = ("Pali Aike")},
    ["Osasuna"] = { latitude = -51.955927, longitude = -69.891237, display_name = ("Osasuna")},
    ["Rosita"] = { latitude = -54.021954, longitude = -68.472809, display_name = ("Rosita")},
    ["Timaukel"] = { latitude = -53.688797, longitude = -69.902275, display_name = ("Timaukel")},
    ["Biotex"] = { latitude = -53.658004, longitude = -69.717224, display_name = ("Biotex")},
    ["Los Coles"] = { latitude = -52.560165, longitude = -71.696809, display_name = ("Los Coles")},
    ["Cruz del Sur"] = { latitude = -52.557386, longitude = -71.544394, display_name = ("Cruz del Sur")},
    ["Estancia Santa Florencia"] = { latitude = -52.688038, longitude = -71.159152, display_name = ("Estancia Santa Florencia")},
    ["Monte Bello"] = { latitude = -52.633102, longitude = -71.208709, display_name = ("Monte Bello")},
    ["Puesto Galy"] = { latitude = -52.385479, longitude = -71.404517, display_name = ("Puesto Galy")},
    ["Estancia Onamonte"] = { latitude = -53.989247, longitude = -69.115475, display_name = ("Estancia Onamonte")},
    ["Sector Chacras"] = { latitude = -46.850571, longitude = -72.698799, display_name = ("Sector Chacras")},
    ["Las Chacras"] = { latitude = -48.478452, longitude = -72.557845, display_name = ("Las Chacras")},
    ["Aerodromo Enrique Mayer Soto"] = { latitude = -47.785862, longitude = -73.526357, display_name = ("Aerodromo Enrique Mayer Soto")},
    ["Parque Karukinka"] = { latitude = -54.150252, longitude = -68.711751, display_name = ("Parque Karukinka")},
    ["Russfin"] = { latitude = -53.750757, longitude = -69.210967, display_name = ("Russfin")},
    ["Campo Lindo"] = { latitude = -53.312178, longitude = -69.460037, display_name = ("Campo Lindo")},
    ["Cecilia"] = { latitude = -53.278268, longitude = -69.326345, display_name = ("Cecilia")},
    ["Dorotea"] = { latitude = -51.647924, longitude = -72.339118, display_name = ("Dorotea")},
    ["Huertos Dorotea"] = { latitude = -51.618157, longitude = -72.332252, display_name = ("Huertos Dorotea")},
    ["Hotel Explora"] = { latitude = -51.116256, longitude = -72.990730, display_name = ("Hotel Explora")},
    ["Espora"] = { latitude = -52.491211, longitude = -69.422756, display_name = ("Espora")},
    ["Doce de Febrero"] = { latitude = -52.893513, longitude = -70.141170, display_name = ("Doce de Febrero")},
    ["Blessen"] = { latitude = -53.079155, longitude = -70.869196, display_name = ("Blessen")},
    ["Pampa Alegre"] = { latitude = -53.072890, longitude = -70.865130, display_name = ("Pampa Alegre")},
    ["Estancia Zenteno"] = { latitude = -52.806512, longitude = -70.797879, display_name = ("Estancia Zenteno")},
    ["Estancia Tamel Aike"] = { latitude = -52.756655, longitude = -70.810397, display_name = ("Estancia Tamel Aike")},
    ["Puesto Segunda Angostura"] = { latitude = -52.648078, longitude = -70.347401, display_name = ("Puesto Segunda Angostura")},
    ["Monte Aymond"] = { latitude = -52.146118, longitude = -69.516473, display_name = ("Monte Aymond")},
    ["Lago Leal"] = { latitude = -48.025871, longitude = -73.122166, display_name = ("Lago Leal")},
    ["Barrio Industrial"] = { latitude = -54.930718, longitude = -67.571524, display_name = ("Barrio Industrial")},
    ["Paso Laurita"] = { latitude = -51.689279, longitude = -72.298001, display_name = ("Paso Laurita")},
    ["Aeropuerto"] = { latitude = -53.006840, longitude = -70.830867, display_name = ("Aeropuerto")},
    ["Los Naranjos"] = { latitude = -53.032203, longitude = -70.837240, display_name = ("Los Naranjos")},
    ["Las Siembras"] = { latitude = -53.039477, longitude = -70.878621, display_name = ("Las Siembras")},
    ["Aves del Sur"] = { latitude = -53.033232, longitude = -70.903856, display_name = ("Aves del Sur")},
    ["La Cruz"] = { latitude = -53.063169, longitude = -70.869064, display_name = ("La Cruz")},
    ["Pueblo Vasco"] = { latitude = -53.075704, longitude = -70.874987, display_name = ("Pueblo Vasco")},
    ["Vrsalovic"] = { latitude = -53.078356, longitude = -70.887325, display_name = ("Vrsalovic")},
    ["Fuerte Bulnes"] = { latitude = -53.629971, longitude = -70.918854, display_name = ("Fuerte Bulnes")},
    ["Rinconada Bulnes"] = { latitude = -53.593269, longitude = -70.938812, display_name = ("Rinconada Bulnes")},
    ["Discordia"] = { latitude = -53.268093, longitude = -70.944880, display_name = ("Discordia")},
    ["Estancia San Pedro"] = { latitude = -53.250811, longitude = -70.953361, display_name = ("Estancia San Pedro")},
    ["Natales Oriente"] = { latitude = -51.729525, longitude = -72.473877, display_name = ("Natales Oriente")},
    ["Diffunta Correa"] = { latitude = -51.698075, longitude = -72.431193, display_name = ("Diffunta Correa")},
    ["Estancia Asturiana"] = { latitude = -51.879054, longitude = -72.083969, display_name = ("Estancia Asturiana")},
    ["Estancia Laguna Dorotea"] = { latitude = -51.548837, longitude = -72.496217, display_name = ("Estancia Laguna Dorotea")},
    ["Regimiento Lanceros"] = { latitude = -51.711773, longitude = -72.439286, display_name = ("Regimiento Lanceros")},
    ["Huertos Endesa"] = { latitude = -51.732968, longitude = -72.467439, display_name = ("Huertos Endesa")},
    ["Isabel Riquelme"] = { latitude = -51.742595, longitude = -72.457472, display_name = ("Isabel Riquelme")},
    ["Estancia La Caravana"] = { latitude = -51.818187, longitude = -72.225514, display_name = ("Estancia La Caravana")},
    ["Puerto Nuevo"] = { latitude = -52.555450, longitude = -71.964865, display_name = ("Puerto Nuevo")},
    ["La Frontera"] = { latitude = -53.265089, longitude = -68.716562, display_name = ("La Frontera")},
    ["La Herradura"] = { latitude = -48.253982, longitude = -72.387931, display_name = ("La Herradura")},
    ["Estancia Gladis"] = { latitude = -52.554967, longitude = -70.406201, display_name = ("Estancia Gladis")},
    ["Piedra del Fraile"] = { latitude = -49.227558, longitude = -73.013723, display_name = ("Piedra del Fraile")},
    ["Ana Navarro"] = { latitude = -53.080864, longitude = -70.889650, display_name = ("Ana Navarro")},
    ["Paso del Vuelto"] = { latitude = -49.821923, longitude = -73.850491, display_name = ("Paso del Vuelto")},
    ["Estancia Perales"] = { latitude = -51.542409, longitude = -72.843228, display_name = ("Estancia Perales")},
    ["Estancia La Portada"] = { latitude = -52.235789, longitude = -70.275169, display_name = ("Estancia La Portada")},
    ["Estancia La Portada"] = { latitude = -52.072955, longitude = -70.195828, display_name = ("Estancia La Portada")},
    ["Puesto Treinta y Siete"] = { latitude = -52.055970, longitude = -70.536943, display_name = ("Puesto Treinta y Siete")},
    ["La Vega"] = { latitude = -52.289937, longitude = -70.531644, display_name = ("La Vega")},
    ["Estancia Oasy Harbour"] = { latitude = -52.516535, longitude = -70.431899, display_name = ("Estancia Oasy Harbour")},
    ["Brazo Norte"] = { latitude = -52.056235, longitude = -70.005431, display_name = ("Brazo Norte")},
    ["Estancia Rose Aike Sur"] = { latitude = -52.284494, longitude = -70.114229, display_name = ("Estancia Rose Aike Sur")},
    ["Estancia Los Coipos"] = { latitude = -52.678978, longitude = -70.786135, display_name = ("Estancia Los Coipos")},
    ["Estancia Avelina"] = { latitude = -52.573300, longitude = -70.772019, display_name = ("Estancia Avelina")},
    ["Estancia Los Cisnes"] = { latitude = -52.620034, longitude = -70.825887, display_name = ("Estancia Los Cisnes")},
    ["Estancia Tres Chorrillos"] = { latitude = -52.519286, longitude = -70.716552, display_name = ("Estancia Tres Chorrillos")},
    ["Estancia Divina Esperanza"] = { latitude = -52.321467, longitude = -71.042790, display_name = ("Estancia Divina Esperanza")},
    ["Estancia Leona English"] = { latitude = -52.389336, longitude = -71.048506, display_name = ("Estancia Leona English")},
    ["Estancia Penitente"] = { latitude = -52.119768, longitude = -71.384551, display_name = ("Estancia Penitente")},
    ["Estancia Macerena"] = { latitude = -53.094961, longitude = -71.195519, display_name = ("Estancia Macerena")},
    ["Marcela"] = { latitude = -53.475945, longitude = -71.195189, display_name = ("Marcela")},
    ["Estancia Mimica"] = { latitude = -52.766262, longitude = -69.521196, display_name = ("Estancia Mimica")},
    ["Estancia Mercedes"] = { latitude = -53.395959, longitude = -70.311980, display_name = ("Estancia Mercedes")},
    ["Puerto Paredes"] = { latitude = -55.091295, longitude = -70.682970, display_name = ("Puerto Paredes")},
    ["Puerto Chico"] = { latitude = -55.094981, longitude = -70.618452, display_name = ("Puerto Chico")},
    ["Caleta Buen Refugio"] = { latitude = -52.836867, longitude = -74.576989, display_name = ("Caleta Buen Refugio")},
    ["Punta Segunda"] = { latitude = -52.090345, longitude = -73.096054, display_name = ("Punta Segunda")},
    ["Punta Barranco"] = { latitude = -52.091255, longitude = -73.098177, display_name = ("Punta Barranco")},
    ["Punta Kirke Sur"] = { latitude = -52.101023, longitude = -73.120687, display_name = ("Punta Kirke Sur")},
    ["Punta Boca"] = { latitude = -52.086456, longitude = -73.109958, display_name = ("Punta Boca")},
    ["Punta Benitez"] = { latitude = -52.078069, longitude = -73.081548, display_name = ("Punta Benitez")},
    ["Punta Alta"] = { latitude = -52.085335, longitude = -73.082577, display_name = ("Punta Alta")},
    ["Punta Seca"] = { latitude = -52.078425, longitude = -73.058202, display_name = ("Punta Seca")},
    ["Punta Cono"] = { latitude = -52.073097, longitude = -73.048031, display_name = ("Punta Cono")},
    ["Punta Pasaje"] = { latitude = -52.066581, longitude = -73.032796, display_name = ("Punta Pasaje")},
    ["Punta Medio"] = { latitude = -52.065025, longitude = -73.025456, display_name = ("Punta Medio")},
    ["Punta Escoben"] = { latitude = -52.055789, longitude = -73.019878, display_name = ("Punta Escoben")},
    ["Caleta Zorro"] = { latitude = -52.065605, longitude = -73.010522, display_name = ("Caleta Zorro")},
    ["Punta Entrada"] = { latitude = -52.060196, longitude = -73.003398, display_name = ("Punta Entrada")},
    ["Punta Restinga"] = { latitude = -52.059833, longitude = -73.009986, display_name = ("Punta Restinga")},
    ["Angostura Kirke"] = { latitude = -52.058432, longitude = -73.010308, display_name = ("Angostura Kirke")},
    ["Costa Centro"] = { latitude = -54.808705, longitude = -68.304695, display_name = ("Costa Centro")},
    ["Fundo San Fernando"] = { latitude = -53.516299, longitude = -70.963340, display_name = ("Fundo San Fernando")},
    ["China Creek"] = { latitude = -53.154076, longitude = -69.182848, display_name = ("China Creek")},
    ["Grota Antonio Gil"] = { latitude = -51.500582, longitude = -72.252174, display_name = ("Grota Antonio Gil")},
    ["Estancia Entre Rios"] = { latitude = -48.265328, longitude = -72.218942, display_name = ("Estancia Entre Rios")},
    ["Punta Vergara"] = { latitude = -51.928689, longitude = -72.576822, display_name = ("Punta Vergara")},
    ["Ladrilleros"] = { latitude = -51.834170, longitude = -72.725600, display_name = ("Ladrilleros")},
    ["San Luis"] = { latitude = -51.690244, longitude = -72.641448, display_name = ("San Luis")},
    ["Esmeralda"] = { latitude = -53.622372, longitude = -70.475991, display_name = ("Esmeralda")},
    ["Estancia Kerber"] = { latitude = -52.092902, longitude = -72.059502, display_name = ("Estancia Kerber")},
    ["Estancia San Lucas"] = { latitude = -52.143330, longitude = -72.059988, display_name = ("Estancia San Lucas")},
    ["Estancia Las Nieves"] = { latitude = -52.305619, longitude = -71.840212, display_name = ("Estancia Las Nieves")},
    ["Puerto Almanza"] = { latitude = -54.870409, longitude = -67.564406, display_name = ("Puerto Almanza")},
    ["Estancia Moat"] = { latitude = -54.973617, longitude = -66.745034, display_name = ("Estancia Moat")},
    ["Dungeness"] = { latitude = -52.394832, longitude = -68.431165, display_name = ("Dungeness")},
    ["Asturiana"] = { latitude = -51.879134, longitude = -72.083448, display_name = ("Asturiana")},
    ["Isabel Riquelme"] = { latitude = -51.871646, longitude = -72.465547, display_name = ("Isabel Riquelme")},
    ["Rotunda"] = { latitude = -51.964359, longitude = -72.584992, display_name = ("Rotunda")},
    ["Lago Pinto"] = { latitude = -52.102842, longitude = -72.412986, display_name = ("Lago Pinto")},
    ["Loteo Las Flores"] = { latitude = -53.055261, longitude = -70.867235, display_name = ("Loteo Las Flores")},
    ["Romero"] = { latitude = -52.356660, longitude = -70.444921, display_name = ("Romero")},
    ["Lago Vargas"] = { latitude = -47.697060, longitude = -73.076762, display_name = ("Lago Vargas")},
    ["Puesto Buque Quemado"] = { latitude = -52.407205, longitude = -69.568150, display_name = ("Puesto Buque Quemado")},
    ["Puesto Cerro Negro"] = { latitude = -52.400557, longitude = -69.641565, display_name = ("Puesto Cerro Negro")},
    ["Puesto Rancho Carancho"] = { latitude = -52.360647, longitude = -69.487559, display_name = ("Puesto Rancho Carancho")},
    ["Interior Dungeness"] = { latitude = -52.339613, longitude = -68.469438, display_name = ("Interior Dungeness")},
    ["Las Barrancas"] = { latitude = -52.318940, longitude = -68.587263, display_name = ("Las Barrancas")},
    ["Daniel Uno"] = { latitude = -52.342340, longitude = -68.666549, display_name = ("Daniel Uno")},
    ["Catalina Dos"] = { latitude = -52.375862, longitude = -68.787445, display_name = ("Catalina Dos")},
    ["Catalina Uno"] = { latitude = -52.390295, longitude = -68.787268, display_name = ("Catalina Uno")},
    ["Anguila"] = { latitude = -52.409154, longitude = -68.784227, display_name = ("Anguila")},
    ["Puesto Alamiro"] = { latitude = -52.228077, longitude = -69.023152, display_name = ("Puesto Alamiro")},
    ["Puesto Chocolate"] = { latitude = -52.176616, longitude = -69.453188, display_name = ("Puesto Chocolate")},
    ["Puesto Manantial Alto"] = { latitude = -52.265334, longitude = -69.545810, display_name = ("Puesto Manantial Alto")},
    ["Puesto Monte Aymond"] = { latitude = -52.152339, longitude = -69.595839, display_name = ("Puesto Monte Aymond")},
    ["Puesto Caravana"] = { latitude = -52.212071, longitude = -69.698369, display_name = ("Puesto Caravana")},
    ["Puesto La Calle"] = { latitude = -52.225946, longitude = -69.985221, display_name = ("Puesto La Calle")},
    ["Puesto Pampa Larga"] = { latitude = -52.231292, longitude = -69.828837, display_name = ("Puesto Pampa Larga")},
    ["Puesto Roci Aike"] = { latitude = -52.187763, longitude = -70.177161, display_name = ("Puesto Roci Aike")},
    ["Las Barrancas"] = { latitude = -52.167131, longitude = -70.266302, display_name = ("Las Barrancas")},
    ["Puesto Teniente Merino"] = { latitude = -52.143342, longitude = -70.452375, display_name = ("Puesto Teniente Merino")},
    ["Puesto Vargas"] = { latitude = -52.159971, longitude = -70.532484, display_name = ("Puesto Vargas")},
    ["Puesto Final"] = { latitude = -52.157786, longitude = -70.535451, display_name = ("Puesto Final")},
    ["Puesto El Centro"] = { latitude = -52.091769, longitude = -70.417326, display_name = ("Puesto El Centro")},
    ["Puesto Cuarenta y Cuatro"] = { latitude = -52.074871, longitude = -70.860037, display_name = ("Puesto Cuarenta y Cuatro")},
    ["Lagunita"] = { latitude = -52.110769, longitude = -71.163455, display_name = ("Lagunita")},
    ["Puesto Lagunita"] = { latitude = -52.121721, longitude = -71.171534, display_name = ("Puesto Lagunita")},
    ["Puesto Caravana"] = { latitude = -52.115203, longitude = -71.276688, display_name = ("Puesto Caravana")},
    ["Ann Aiken"] = { latitude = -52.061522, longitude = -71.230210, display_name = ("Ann Aiken")},
    ["Puesto Ann Aiken"] = { latitude = -52.088304, longitude = -71.274698, display_name = ("Puesto Ann Aiken")},
    ["Puesto Mulato"] = { latitude = -52.018599, longitude = -71.252006, display_name = ("Puesto Mulato")},
    ["Puesto Palermo"] = { latitude = -52.062465, longitude = -71.426839, display_name = ("Puesto Palermo")},
    ["Palermo"] = { latitude = -52.128193, longitude = -71.403654, display_name = ("Palermo")},
    ["Puesto Bishop"] = { latitude = -52.132876, longitude = -71.406739, display_name = ("Puesto Bishop")},
    ["Vilicic"] = { latitude = -52.134897, longitude = -71.555307, display_name = ("Vilicic")},
    ["Puesto Morro Chico"] = { latitude = -52.048631, longitude = -71.553665, display_name = ("Puesto Morro Chico")},
    ["Puesto Pioneros"] = { latitude = -52.026759, longitude = -71.540790, display_name = ("Puesto Pioneros")},
    ["Seno Otway Norte"] = { latitude = -52.803131, longitude = -71.173937, display_name = ("Seno Otway Norte")},
    ["Estancia Laguna Toro"] = { latitude = -52.793563, longitude = -71.147040, display_name = ("Estancia Laguna Toro")},
    ["Parador Laguna Blanca"] = { latitude = -52.799794, longitude = -71.190625, display_name = ("Parador Laguna Blanca")},
    ["Puesto La Calle"] = { latitude = -52.620097, longitude = -71.146565, display_name = ("Puesto La Calle")},
    ["Mina Invierno"] = { latitude = -52.885486, longitude = -71.611374, display_name = ("Mina Invierno")},
    ["Estancia Adela"] = { latitude = -52.850396, longitude = -71.510362, display_name = ("Estancia Adela")},
    ["Puesto Vilicic"] = { latitude = -52.802563, longitude = -71.402667, display_name = ("Puesto Vilicic")},
    ["Puesto Viejo"] = { latitude = -52.723083, longitude = -71.551014, display_name = ("Puesto Viejo")},
    ["Estancia del Pilar"] = { latitude = -52.710677, longitude = -71.423951, display_name = ("Estancia del Pilar")},
    ["Estancia La Reina"] = { latitude = -52.761944, longitude = -71.354814, display_name = ("Estancia La Reina")},
    ["Puesto Las Charas"] = { latitude = -52.689612, longitude = -71.416774, display_name = ("Puesto Las Charas")},
    ["Puesto La Laguna"] = { latitude = -52.506730, longitude = -71.208715, display_name = ("Puesto La Laguna")},
    ["Puesto Borrego Uno"] = { latitude = -52.524688, longitude = -71.318402, display_name = ("Puesto Borrego Uno")},
    ["Puesto Dieciocho"] = { latitude = -52.446408, longitude = -71.442470, display_name = ("Puesto Dieciocho")},
    ["Puesto de Arrieros"] = { latitude = -52.396285, longitude = -71.410879, display_name = ("Puesto de Arrieros")},
    ["Refugio de Arrieros"] = { latitude = -52.280578, longitude = -71.347799, display_name = ("Refugio de Arrieros")},
    ["Puesto Bus"] = { latitude = -52.297255, longitude = -71.452035, display_name = ("Puesto Bus")},
    ["Macarena"] = { latitude = -52.287854, longitude = -71.613371, display_name = ("Macarena")},
    ["Rancho Salazar Uno"] = { latitude = -52.402236, longitude = -71.795105, display_name = ("Rancho Salazar Uno")},
    ["Rancho Salazar Dos"] = { latitude = -52.495899, longitude = -71.693263, display_name = ("Rancho Salazar Dos")},
    ["Puesto Martinic"] = { latitude = -52.551349, longitude = -71.826623, display_name = ("Puesto Martinic")},
    ["Puesto Nutria"] = { latitude = -52.523961, longitude = -72.127942, display_name = ("Puesto Nutria")},
    ["Isla Marta"] = { latitude = -52.556943, longitude = -72.298979, display_name = ("Isla Marta")},
    ["Surgidero Furia"] = { latitude = -52.625027, longitude = -72.410892, display_name = ("Surgidero Furia")},
    ["Isla Unicornio"] = { latitude = -52.631631, longitude = -72.373973, display_name = ("Isla Unicornio")},
    ["Isla Unicornio Sur"] = { latitude = -52.638090, longitude = -72.396043, display_name = ("Isla Unicornio Sur")},
    ["Punta Laura"] = { latitude = -52.649443, longitude = -72.403982, display_name = ("Punta Laura")},
    ["Mardin"] = { latitude = -52.651056, longitude = -72.415392, display_name = ("Mardin")},
    ["Condominio Vista Norte"] = { latitude = -53.096710, longitude = -70.881977, display_name = ("Condominio Vista Norte")},
    ["Parroquia de Barranco Amarillo"] = { latitude = -53.094565, longitude = -70.877653, display_name = ("Parroquia de Barranco Amarillo")},
    ["Gruta Virgen de Covadonga"] = { latitude = -53.053915, longitude = -70.891379, display_name = ("Gruta Virgen de Covadonga")},
    ["Laguna de Los Cisnes"] = { latitude = -53.274474, longitude = -70.367704, display_name = ("Laguna de Los Cisnes")},
    ["Lomas del Baquedano"] = { latitude = -53.289182, longitude = -70.358475, display_name = ("Lomas del Baquedano")},
    ["Futuro Patagonia"] = { latitude = -53.291369, longitude = -70.378248, display_name = ("Futuro Patagonia")},
    ["Sector Las Siembras"] = { latitude = -53.035698, longitude = -70.899996, display_name = ("Sector Las Siembras")},
    ["Glaciar Grey"] = { latitude = -51.738356, longitude = -72.474903, display_name = ("Glaciar Grey")},
    ["Glaciar Serrano"] = { latitude = -51.738108, longitude = -72.472936, display_name = ("Glaciar Serrano")},
    ["Punta Arenas"] = { latitude = -53.155050, longitude = -70.922954, display_name = ("Punta Arenas")},
    ["Rio Gallegos"] = { latitude = -51.625855, longitude = -69.223086, display_name = ("Rio Gallegos")},
}
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  Module loading log
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
veaf.loggers.get(veafNamedPoints.Id):info(string.format("Loading version %s", veafNamedPoints.Version))

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  MODULE TESTS
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
--[[
-- LOGGING DISABLED WHEN COMPILING("MODULE TESTS: " .. veafNamedPoints.Id)
veafNamedPoints.addCities()
veafNamedPoints.addAirbases()
-- LOGGING DISABLED WHEN COMPILING(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
for _, veafNamedPoint in pairs(veafNamedPoints.namedPoints) do
    if (veaf.startsWith(_, "AIRBASE", false)) then
-- LOGGING DISABLED WHEN COMPILING(">>> %s", veaf.p(veafNamedPoint))    
    end
-- LOGGING DISABLED WHEN COMPILING(">>> %s", veaf.p(veafNamedPoint))
    veafNamedPoints.markid = veafNamedPoints.markid + 1
    trigger.action.markToAll(veafNamedPoints.markid, "VEAF - Point named ".. _, veafNamedPoint, true) 

end
-- LOGGING DISABLED WHEN COMPILING(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
]]

------------------ END script veafNamedPoints.lua  ------------------


------------------ START script veafQraManager.lua  ------------------

------------------------------------------------------------------
-- VEAF Quick Reaction Alert for DCS World
-- https://en.wikipedia.org/wiki/Quick_Reaction_Alert
-- By Zip (2020) and Rex (2022)
--
-- Features:
-- ---------
-- * Define zones that are defended by an AI flight
-- * Default behavior: when an ennemy aircraft enters the zone, QRA patrol is spawned; then, when it is destroyed, the zone is not defended anymore; when all enemy aircrafts have left the zone, it resets and can respawn a new QRA
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafQraManager = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafQraManager.Id = "QRA"

--- Version.
veafQraManager.Version = "1.2.2"

-- trace level, specific to this module
--veafQraManager.LogLevel = "trace"

veaf.loggers.new(veafQraManager.Id, veafQraManager.LogLevel)

function veafQraManager.statusToString(status)
    if status == veafQraManager.STATUS_WILLREARM then return "STATUS_WILLREARM" end
    if status == veafQraManager.STATUS_READY then return "STATUS_READY" end
    if status == veafQraManager.STATUS_READY_WAITINGFORMORE then return "STATUS_READY_WAITINGFORMORE" end
    if status == veafQraManager.STATUS_ACTIVE then return "STATUS_ACTIVE" end
    if status == veafQraManager.STATUS_DEAD then return "STATUS_DEAD" end
    return ""
  end
veafQraManager.STATUS_WILLREARM = 0
veafQraManager.STATUS_READY = 1
veafQraManager.STATUS_READY_WAITINGFORMORE = 1.5
veafQraManager.STATUS_ACTIVE = 2
veafQraManager.STATUS_DEAD = 3

--scheduled states
veafQraManager.STATUS_OUT = 4
veafQraManager.STATUS_NOAIRBASE = 5
veafQraManager.STATUS_STOP = 6

veafQraManager.WATCHDOG_DELAY = 5

veafQraManager.MINIMUM_LIFE_FOR_QRA_IN_PERCENT = 10

veafQraManager.DEFAULT_airbaseMinLifePercent = 0.9

veafQraManager.AllSilence = false --value to set all spawned QRAs to silent if true. By default it's false but this value can be set in the missionConfig
veafQraManager.DEFAULT_MESSAGE_START = "%s is online"
veafQraManager.DEFAULT_MESSAGE_DEPLOY = "%s is deploying"
veafQraManager.DEFAULT_MESSAGE_DESTROYED = "%s has been destroyed"
veafQraManager.DEFAULT_MESSAGE_READY = "%s is ready"
veafQraManager.DEFAULT_MESSAGE_OUT = "%s is out of aircrafts"
veafQraManager.DEFAULT_MESSAGE_RESUPPLIED = "%s has been resupplied"
veafQraManager.DEFAULT_MESSAGE_AIRBASE_DOWN = "%s lost it's airbase"
veafQraManager.DEFAULT_MESSAGE_AIRBASE_UP = "%s now has an airbase"
veafQraManager.DEFAULT_MESSAGE_STOP = "%s is offline"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafQraManager.qras = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafQRA class methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafQRA = {}
function VeafQRA.init(object)
    -- technical name (QRA instance name)
    object.name = nil
    -- trigger zone name (if set, we'll use a DCS trigger zone)
    object.triggerZoneName = nil
    -- center (point in the center of the circle, when not using a DCS trigger zone)
    object.zoneCenter = nil
    -- radius (size of the circle, when not using a zone)
    object.zoneRadius = nil
    -- draw the zone on screen
    object.drawZone = false
    -- description for the briefing
    object.description = nil
    -- aircraft groups forming the QRA
    object.groups = {}
    -- aircraft groups forming the QRA, in a table by enemy quantity (i.e. if this number of enemies are in the zone, spawn these groups)
    object.groupsToDeployByEnemyQuantity = {}
    -- coalition for the QRA
    object.coalition = nil
    -- coalitions the QRA is defending against
    object.ennemyCoalitions = {}
    -- message when the QRA is started
    object.messageStart = veafQraManager.DEFAULT_MESSAGE_START
    -- event when the QRA is started
    object.onStart = nil
    -- message when the QRA is triggered
    object.messageDeploy = veafQraManager.DEFAULT_MESSAGE_DEPLOY
    -- event when the QRA is triggered
    object.onDeploy = nil
    -- message when the QRA is destroyed
    object.messageDestroyed = veafQraManager.DEFAULT_MESSAGE_DESTROYED
    -- event when the QRA is destroyed
    object.onDestroyed = nil
    -- message when the QRA is ready
    object.messageReady = veafQraManager.DEFAULT_MESSAGE_READY
    -- event when the QRA is ready
    object.onReady = nil
    -- message when the QRA is out of aircrafts
    object.messageOut = veafQraManager.DEFAULT_MESSAGE_OUT
    -- event when the QRA is out of aircrafts
    object.onOut = nil
    -- message when the QRA has been resupplied and will start operations against
    object.messageResupplied = veafQraManager.DEFAULT_MESSAGE_RESUPPLIED
    -- event when the QRA has been resupplied and will start operations against
    object.onResupplied = nil
    -- message when the QRA has lost the airbase it operates from
    object.messageAirbaseDown = veafQraManager.DEFAULT_MESSAGE_AIRBASE_DOWN
    -- event when the QRA has lost the airbase it operates from
    object.onAirbaseDown = nil
    -- message when the QRA has retrieved the airbase it operates from and will start operations again
    object.messageAirbaseUp = veafQraManager.DEFAULT_MESSAGE_AIRBASE_UP
    -- event when the QRA has retrieved the airbase it operates from and will start operations again
    object.onAirbaseUp = nil
    -- message when the QRA is stopped
    object.messageStop = veafQraManager.DEFAULT_MESSAGE_STOP
    -- event when the QRA is stopped
    object.onStop = nil
	-- silent means no message is emitted
    object.silent = veafQraManager.AllSilence
    -- default position for respawns (im meters, lat/lon, relative to the zone center)
    object.respawnDefaultOffset = {latDelta=0, lonDelta=0}
    -- radius of the defenders groups spawn
    object.respawnRadius = 250
    -- reacts when helicopters enter the zone
    object.reactOnHelicopters = false
    -- delay before activating
    object.delayBeforeActivating = -1
    -- delay before rearming
    object.delayBeforeRearming = -1
    -- the enemy does not have to leave the zone before the QRA is rearmed
    object.noNeedToLeaveZoneBeforeRearming = false
    -- reset the QRA immediately if all the enemy units leave the zone
    object.resetWhenLeavingZone = false
    -- maximum number of QRA ready for action at once, -1 indicates infinite
    object.QRAmaxCount = -1
    -- number of groups of aircrafts that can be spawned for this QRA in total, -1 indicates infinite.
    object.QRAcount = -1
    -- delay in minutes before the QRA counter is increased by one, simulating some sort of logistic chain of aircrafts.
    object.delayBeforeQRAresupply = 0
    -- maximum number of resupplies at a given time, simulating some sort of warehousing, -1 indicates infinite. Is decremented every time a resupply happens. 0 indicates no resupply.
    object.QRAresupplyMax = -1
    -- minimum QRAcount that will trigger a resupply, -1 indicates as soon as an aircraft is lost
    object.QRAminCountforResupply = -1
    -- how many aircraft groups are resupplied at once   
    object.resupplyAmount = 1
    -- indicator to know if the QRA is being resupplied or not
    object.isResupplying = false
    -- name of the airport to which the QRA is linked, QRAs will be deployed only if this is set and the airport is captured by the QRA's coalition or if this is not set
    object.airportLink = nil
    -- minimum linked airbase life percentage (from 0 to 1) for the QRA to have it's airbase available
    object.airportMinLifePercent = veafQraManager.DEFAULT_airbaseMinLifePercent
    -- boolean to know if the status OUT was announced or not
    object.outAnnounced = false
    -- boolean to know if the status NOAIRBASE was announced or not
    object.noAB_announced = false
    -- minimum number of enemies in the zone to trigger deployment; updated automatically by setGroupsToDeployByEnemyQuantity
    object.minimumNbEnemyPlanes = -1
    -- planes in the zone will only be detected below this altitude (in feet)
    object.minimumAltitude = -999999
    -- planes in the zone will only be detected above this altitude (in feet)
    object.maximumAltitude = 999999
    object.timer = nil
    object.state = nil
    object.scheduled_state = nil
    object._enemyHumanUnits = nil
    object.spawnedGroupsNames = {}
end

function VeafQRA.ToggleAllSilence(state)
    if state then
        veafQraManager.AllSilence = true
    else
        veafQraManager.AllSilence = false
    end
end

function VeafQRA:new(objectToCopy)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA:new()")

    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object
    VeafQRA.init(objectToCreate)

    return objectToCreate
end

function VeafQRA:setName(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[]:setName(%s)", veaf.p(value))
    self.name = value
    return veafQraManager.add(self) -- add the QRA to the QRA list as soon as a name is available to index it
end

function VeafQRA:setTriggerZone(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setTriggerZone(%s)", veaf.p(self.name), veaf.p(value))
    self.triggerZoneName = value
    return self
end

function VeafQRA:setZoneCenter(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setZoneCenter(%s)", veaf.p(self.name), veaf.p(value))
    self.zoneCenter = value
    return self
end

function VeafQRA:setZoneCenterFromCoordinates(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setZoneCenterFromCoordinates(%s)", veaf.p(self.name), veaf.p(value))
    local _lat, _lon = veaf.computeLLFromString(value)
    local vec3 = coord.LLtoLO(_lat, _lon)
    return self:setZoneCenter(vec3)
end

function VeafQRA:setZoneRadius(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setZoneRadius(%s)", veaf.p(self.name), veaf.p(value))
    self.zoneRadius = value
    return self
end

function VeafQRA:setDescription(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setDescription(%s)", veaf.p(self.name), veaf.p(value))
    self.description = value
    return veafQraManager.add(self) -- add the QRA to the QRA list as soon as a name is available to index it
end

function VeafQRA:getDescription()
    return self.description or self.name
end

function VeafQRA:getName()
  return self.name or self.description
end

function VeafQRA:addGroup(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:addGroup(%s)", veaf.p(self.name), veaf.p(value))
    if not self.groupsToDeployByEnemyQuantity[1] then
        self.groupsToDeployByEnemyQuantity[1] = {}
    end
    table.insert(self.groupsToDeployByEnemyQuantity[1], value)
    return self
end

function VeafQRA:addRandomGroup(groups, number, bias)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:addRandomGroup(%s, %s, %s)", veaf.p(self.name), veaf.p(groups), veaf.p(number), veaf.p(bias))
    return self:addGroup({groups, number or 1, bias or 0})
end

function VeafQRA:setGroupsToDeployByEnemyQuantity(enemyNb, groupsToDeploy)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setGroupsToDeployByEnemyQuantity(%s) -> %s", veaf.p(self.name), veaf.p(enemyNb), veaf.p(groupsToDeploy))
    self.groupsToDeployByEnemyQuantity[enemyNb] = groupsToDeploy
    if self.minimumNbEnemyPlanes == -1 or self.minimumNbEnemyPlanes > enemyNb then
        self.minimumNbEnemyPlanes = enemyNb
    end
    return self
end

function VeafQRA:setRandomGroupsToDeployByEnemyQuantity(enemyNb, groups, number, bias)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setRandomGroupsToDeployByEnemyQuantity(%s, %s, %s, %s)", veaf.p(self.name), veaf.p(enemyNb), veaf.p(groups), veaf.p(number), veaf.p(bias))
    return self:setGroupsToDeployByEnemyQuantity(enemyNb, {groups, number or 1, bias or 0})
end

function VeafQRA:setCoalition(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setCoalition(%s)", veaf.p(self.name), veaf.p(value))
    self.coalition = value
    return self
end

function VeafQRA:addEnnemyCoalition(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:addEnnemyCoalition(%s)", veaf.p(self.name), veaf.p(value))
    self.ennemyCoalitions[value] = value
    return self
end

function VeafQRA:getEnnemyCoalition()
    local result = nil
    for coalition, _ in pairs(self.ennemyCoalitions) do
      result = coalition
      break
    end
    return result
end

function VeafQRA:setMessageStart(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageStart(%s)", veaf.p(self.name), veaf.p(value))
    self.messageStart = value
    return self
end

function VeafQRA:setOnStart(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnStart()", veaf.p(self.name))
    self.onStart = value
    return self
end

function VeafQRA:setMessageDeploy(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageDeploy(%s)", veaf.p(self.name), veaf.p(value))
    self.messageDeploy = value
    return self
end

function VeafQRA:setOnDeploy(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnDeploy()", veaf.p(self.name))
    self.onDeploy = value
    return self
end

function VeafQRA:setMessageDestroyed(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageDestroyed(%s)", veaf.p(self.name), veaf.p(value))
    self.messageDestroyed = value
    return self
end

function VeafQRA:setOnDestroyed(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnDestroyed()", veaf.p(self.name))
    self.onDestroyed = value
    return self
end

function VeafQRA:setMessageReady(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageReady(%s)", veaf.p(self.name), veaf.p(value))
    self.messageReady = value
    return self
end

function VeafQRA:setOnReady(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnReady()", veaf.p(self.name))
    self.onReady = value
    return self
end

function VeafQRA:setMessageOut(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageOut(%s)", veaf.p(self.name), veaf.p(value))
    self.messageOut = value
    return self
end

function VeafQRA:setOnOut(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnOut()", veaf.p(self.name))
    self.onOut = value
    return self
end

function VeafQRA:setMessageResupplied(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageResupplied(%s)", veaf.p(self.name), veaf.p(value))
    self.messageResupplied = value
    return self
end

function VeafQRA:setOnResupplied(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnResupplied()", veaf.p(self.name))
    self.onResupplied = value
    return self
end

function VeafQRA:setMessageAirbaseDown(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageAirbaseDown(%s)", veaf.p(self.name), veaf.p(value))
    self.messageAirbaseDown = value
    return self
end

function VeafQRA:setOnAirbaseDown(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnAirbaseDown()", veaf.p(self.name))
    self.onAirbaseDown = value
    return self
end

function VeafQRA:setMessageAirbaseUp(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageAirbaseUp(%s)", veaf.p(self.name), veaf.p(value))
    self.messageAirbaseUp = value
    return self
end

function VeafQRA:setOnAirbaseUp(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnAirbaseUp()", veaf.p(self.name))
    self.onAirbaseUp = value
    return self
end

function VeafQRA:setMessageStop(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMessageStop(%s)", veaf.p(self.name), veaf.p(value))
    self.messageStop = value
    return self
end

function VeafQRA:setOnStop(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setOnStop()", veaf.p(self.name))
    self.onStop = value
    return self
end

function VeafQRA:setSilent(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setSilent(%s)", veaf.p(self.name), veaf.p(value))
    self.silent = value or false
    return self
end

function VeafQRA:setDrawZone(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setDrawZone(%s)", veaf.p(self.name), veaf.p(value))
    self.drawZone = value or false
    return self
end

--TODO, warehousing for each group within a QRA and not just the whole QRA
function VeafQRA:setQRAcount(count)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setQRAcount(%s)", veaf.p(self.name), veaf.p(count))
    if count and type(count) == 'number' and count >= -1 then
        self.QRAcount = count
    end
    return self
end

function VeafQRA:setQRAmaxCount(maxCount)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setQRAmaxCount(%s)", veaf.p(self.name), veaf.p(maxCount))
    if maxCount and type(maxCount) == 'number' and maxCount >= -1 then
        self.QRAmaxCount = maxCount
    end
    return self
end

function VeafQRA:setQRAresupplyDelay(resupplyDelay)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setQRAresupplyDelay(%s)", veaf.p(self.name), veaf.p(resupplyDelay))
    if resupplyDelay and type(resupplyDelay) == 'number' and resupplyDelay >= 0 then
        self.delayBeforeQRAresupply = resupplyDelay
    end
    return self
end

function VeafQRA:setQRAmaxResupplyCount(maxResupplyCount)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setQRAmaxResupplyCount(%s)", veaf.p(self.name), veaf.p(maxResupplyCount))
    if maxResupplyCount and type(maxResupplyCount) == 'number' and maxResupplyCount >= -1 then
        self.QRAresupplyMax = maxResupplyCount
    end
    return self
end

function VeafQRA:setQRAminCountforResupply(minCountforResupply)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setQRAminCountforResupply(%s)", veaf.p(self.name), veaf.p(minCountforResupply))
    if minCountforResupply and type(minCountforResupply) == 'number' and minCountforResupply >= -1 and minCountforResupply ~= 0 then
        self.QRAminCountforResupply = minCountforResupply
    end
    return self
end

function VeafQRA:setResupplyAmount(resupplyAmount)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setResupplyAmount(%s)", veaf.p(self.name), veaf.p(resupplyAmount))
    if resupplyAmount and type(resupplyAmount) == 'number' and resupplyAmount >= 1 then
        self.resupplyAmount = resupplyAmount
    end
    return self
end

function VeafQRA:setAirportLink(airport_name)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setAirportLink(%s)", veaf.p(self.name), veaf.p(airport_name))
    if airport_name and type(airport_name) == 'string' and Airbase.getByName(airport_name) then
        self.airportLink = airport_name
    end
    return self
end

function VeafQRA:setAirportMinLifePercent(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setAirportMinLifePercent(%s)", veaf.p(self.name), veaf.p(value))
    if value and value >= 0 and value <= 1 then
        self.airportMinLifePercent = value
    end
    return self
end

function VeafQRA:setReactOnHelicopters()
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setReactOnHelicopters()", veaf.p(self.name))
    self.reactOnHelicopters = true
    return self
end

---set the default respawn offset (in meters, relative to the zone center)
---@param defaultOffsetLatitude any in meters
---@param defaultOffsetLongitude any in meters
---@return table self
function VeafQRA:setRespawnDefaultOffset(defaultOffsetLatitude, defaultOffsetLongitude)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setRespawnDefaultOffset(%s, %s)", veaf.p(self.name), veaf.p(defaultOffsetLatitude), veaf.p(defaultOffsetLongitude))
    self.respawnDefaultOffset = { latDelta = defaultOffsetLatitude, lonDelta = defaultOffsetLongitude}
    return self
end

function VeafQRA:setRespawnRadius(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setRespawnRadius(%s)", veaf.p(self.name), veaf.p(value))
    self.respawnRadius = value
    if self.respawnRadius < 250 then self.respawnRadius = 250 end
    return self
end

function VeafQRA:setDelayBeforeRearming(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setDelayBeforeRearming(%s)", veaf.p(self.name), veaf.p(value))
    self.delayBeforeRearming = value
    return self
end

function VeafQRA:setNoNeedToLeaveZoneBeforeRearming()
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setNoNeedToLeaveZoneBeforeRearming()", veaf.p(self.name))
    self.noNeedToLeaveZoneBeforeRearming = true
    return self
end

function VeafQRA:setResetWhenLeavingZone()
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setResetWhenLeavingZone()", veaf.p(self.name))
    self.resetWhenLeavingZone = true
    return self
end

function VeafQRA:setDelayBeforeActivating(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setDelayBeforeActivating(%s)", veaf.p(self.name), veaf.p(value))
    self.delayBeforeActivating = value
    return self
end

function VeafQRA:setMinimumAltitudeInFeet(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMinimumAltitudeInFeet(%s)", veaf.p(self.name), veaf.p(value))
  self.minimumAltitude = value * 0.3048 -- convert from feet
  return self
end

function VeafQRA:getMinimumAltitudeInMeters()
  return self.minimumAltitude
end

function VeafQRA:setMaximumAltitudeInFeet(value)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setMaximumAltitudeInFeet(%s)", veaf.p(self.name), veaf.p(value))
  self.maximumAltitude = value * 0.3048 -- convert from feet
  return self
end

function VeafQRA:getMaximumAltitudeInMeters()
  return self.maximumAltitude
end

function VeafQRA:_getEnemyHumanUnits()
    if not self._enemyHumanUnits then
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:_getEnemyHumanUnits() - computing", veaf.p(self.name))
        self._enemyHumanUnits = {}
        for _, unit in pairs(mist.DBs.humansByName) do
            local coalitionId = 0
            if unit.coalition then
                if unit.coalition:lower() == "red" then
                    coalitionId = coalition.side.RED
                elseif unit.coalition:lower() == "blue" then
                    coalitionId = coalition.side.BLUE
                end
            end
            if self.ennemyCoalitions[coalitionId] then
                if unit.category then
                    if     (unit.category == "plane")
                        or (unit.category == "helicopter" and self.reactOnHelicopters)
                    then
-- LOGGING DISABLED WHEN COMPILING("adding unit to enemy human units for QRA")
                        table.insert(self._enemyHumanUnits, unit.unitName)
                    end
                end
            end
        end
    end
    return self._enemyHumanUnits
end

function VeafQRA:check()
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:check()", veaf.p(self.name))
-- LOGGING DISABLED WHEN COMPILING("self.state=%s", veaf.p(veafQraManager.statusToString(self.state)))
-- LOGGING DISABLED WHEN COMPILING("timer.getTime()=%s", veaf.p(timer.getTime()))

    --scheduled state application is attempted regardless of airportlink checks etc. to take into account user requested states which go through scheduled_states as well
    --Stop scheduled is checked before even running the check function as it has the highest priority
    self:applyScheduledState()

    if self.state ~= veafQraManager.STATUS_STOP then

        --if the QRA is linked to an airbase. Airport is checked before even trying to deploy a group and check warehousing which has a lower priority
        if self.airportLink then
-- LOGGING DISABLED WHEN COMPILING("Checking Airport link : %s", veaf.p(self.airportLink))
            self:checkAirport()
            self:applyScheduledState()
        end

        if self.state ~= veafQraManager.STATUS_NOAIRBASE then

            --if warehousing is activated. Warehousing is checked before even trying to deploy a group
            if self.QRAcount ~= -1 then
-- LOGGING DISABLED WHEN COMPILING("Checking Warehousing...")
-- LOGGING DISABLED WHEN COMPILING("QRACount : %s", veaf.p(self.QRAcount))
                self:checkWarehousing()
                self:applyScheduledState()
            end

            if self.state ~= veafQraManager.STATUS_OUT then
                local unitNames = self:_getEnemyHumanUnits()
                local unitsInZone = nil
                local triggerZone = veaf.getTriggerZone(self.triggerZoneName)

                if (not veaf.isNullOrEmpty(self.triggerZoneName) and triggerZone == nil) then
                    veaf.loggers.get(veafQraManager.Id):error("QRA has a non-existant zone: " .. self.triggerZoneName)
                end

                if triggerZone then
                    if triggerZone.type == 0 then -- circular
                        unitsInZone = mist.getUnitsInZones(unitNames, {self.triggerZoneName})
                    elseif triggerZone.type == 2 then -- quad point
                        unitsInZone = mist.getUnitsInPolygon(unitNames, triggerZone.verticies)
                    end
                else
                    unitsInZone = veaf.findUnitsInCircle(self.zoneCenter, self.zoneRadius, false, unitNames)
                end
                local nbUnitsInZone = 0
                for _, unit in pairs(unitsInZone) do
                    -- check the unit altitude against the ceiling and floor
                    if unit:inAir() then -- never count a landed aircraft
                        local alt = unit:getPoint().y
                        if alt >= self:getMinimumAltitudeInMeters() and alt <= self:getMaximumAltitudeInMeters() then
                            nbUnitsInZone = nbUnitsInZone + 1
                        end
                    end
                end
                if (self.state == veafQraManager.STATUS_READY) and (unitsInZone and nbUnitsInZone > 0) then
-- LOGGING DISABLED WHEN COMPILING(string.format("self.state set to veafQraManager.STATUS_READY_WAITINGFORMORE at timer.getTime()=%s", veaf.p(timer.getTime())))
                    self.state = veafQraManager.STATUS_READY_WAITINGFORMORE
                    self.timeSinceReady = timer.getTime()
                elseif (self.state == veafQraManager.STATUS_READY_WAITINGFORMORE) and (unitsInZone and nbUnitsInZone > 0) and (timer.getTime() - self.timeSinceReady > self.delayBeforeActivating) then
                    -- trigger the QRA
                    self:deploy(nbUnitsInZone)
                    self.timeSinceReady = -1
                elseif (self.state == veafQraManager.STATUS_DEAD) and (self.noNeedToLeaveZoneBeforeRearming or (not unitsInZone or nbUnitsInZone == 0)) then
                    -- rearm the QRA after a delay (if set)
                    if self.delayBeforeRearming > 0 then
                        mist.scheduleFunction(VeafQRA.rearm, {self}, timer.getTime()+self.delayBeforeRearming)
                        self.state = veafQraManager.STATUS_WILLREARM
                    else
                        self:rearm()
                    end
                elseif (self.state == veafQraManager.STATUS_ACTIVE) then
                    local qraAlive = false
                    local qraInAir = false
                    for _, groupName in pairs(self.spawnedGroupsNames) do
                        local group = Group.getByName(groupName)
                        if group then
                            local groupAtLeastOneUnitAlive = false
                            local groupAtLeastOneUnitInAir = false
                            local category = group:getCategory()
                            local units = group:getUnits()
                            if units then
                                for _,unit in pairs(units) do
                                    if unit then
                                        local unitLife = unit:getLife()
                                        local unitLife0 = 0
                                        if unit.getLife0 then -- statics have no life0
                                          unitLife0 = unit:getLife0()
                                        end
                                        local unitLifePercent = unitLife
                                        if unitLife0 > 0 then
                                          unitLifePercent = 100 * unitLife / unitLife0
                                        end
                                        if unitLifePercent >= veafQraManager.MINIMUM_LIFE_FOR_QRA_IN_PERCENT then
                                            groupAtLeastOneUnitAlive = true
                                        end
                                        if category == 0 --[[airplanes]] or category == 1 --[[helicopters]] then
                                            -- check if at least one unit is still airborne
                                            if unit:inAir() then
                                                groupAtLeastOneUnitInAir = true
                                            end
                                        else
                                            -- consider that ground units have never landed
                                            groupAtLeastOneUnitInAir = true
                                        end
                                    end
                                end
                            end
                            qraAlive = qraAlive or groupAtLeastOneUnitAlive
                            qraInAir = qraInAir or groupAtLeastOneUnitInAir
-- LOGGING DISABLED WHEN COMPILING("qraAlive=%s", veaf.p(qraAlive))
-- LOGGING DISABLED WHEN COMPILING("qraInAir=%s", veaf.p(qraInAir))
                        end
                    end
                    if not qraAlive then
                        -- signal QRA destroyed
                        self:destroyed()
                    elseif (self.resetWhenLeavingZone and nbUnitsInZone == 0) or not qraInAir then
                        -- QRA reset
                        self:rearm()
                    end
                end
            end
        end

        mist.scheduleFunction(VeafQRA.check, {self}, timer.getTime() + veafQraManager.WATCHDOG_DELAY)
    end
end

function VeafQRA:setScheduledState(scheduledState)
    --priority level 1
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:setScheduledState(%s)", veaf.p(self.name), veaf.p(scheduledState))
    if scheduledState == veafQraManager.STATUS_STOP then
        self.scheduled_state = veafQraManager.STATUS_STOP
-- LOGGING DISABLED WHEN COMPILING("QRA STOP scheduled")
    --priority level 2
    elseif scheduledState == veafQraManager.STATUS_NOAIRBASE and self.scheduled_state ~= veafQraManager.STATUS_STOP then
        self.scheduled_state = veafQraManager.STATUS_NOAIRBASE
-- LOGGING DISABLED WHEN COMPILING("QRA NOAIRBASE scheduled")
    --priority level 3
    elseif scheduledState == veafQraManager.STATUS_OUT and self.scheduled_state ~= veafQraManager.STATUS_STOP and self.scheduled_state ~= veafQraManager.STATUS_NOAIRBASE then
        self.scheduled_state = veafQraManager.STATUS_OUT
-- LOGGING DISABLED WHEN COMPILING("QRA OUT scheduled")
    end
    return self
end

function VeafQRA:applyScheduledState()
    if self.scheduled_state and self.state ~= veafQraManager.STATUS_ACTIVE then
-- LOGGING DISABLED WHEN COMPILING("QRA taking scheduled status : %s", veaf.p(self.scheduled_state))
        self.state = self.scheduled_state
    end
end

function VeafQRA:checkAirport()
    local QRA_airportObject = veaf.getAirbaseForCoalition(self.airportLink, self.coalition)
    local airport_life_percent = nil
    if QRA_airportObject then
        airport_life_percent = veaf.getAirbaseLife(self.airportLink, true)
    end

-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s] is linked to airbase %s", veaf.p(self.name), veaf.p(self.airportLink))

    if not QRA_airportObject or airport_life_percent < self.airportMinLifePercent then
-- LOGGING DISABLED WHEN COMPILING("QRA lost it's airbase")
        self:setScheduledState(veafQraManager.STATUS_NOAIRBASE)
        if not self.silent and not self.noAB_announced then
            local msg = string.format(self.messageAirbaseDown, self:getDescription())
            for coalition, _ in pairs(self.ennemyCoalitions) do
                trigger.action.outTextForCoalition(coalition, msg, 15)
            end
        end
        if self.onAirbaseDown then
            self.onAirbaseDown(QRA_airportObject)
        end
        self.noAB_announced = true

    elseif self.state == veafQraManager.STATUS_NOAIRBASE then
-- LOGGING DISABLED WHEN COMPILING("QRA has it's airbase %s", veaf.p(QRA_airportObject:getName()))
        if not self.silent then
            local msg = string.format(self.messageAirbaseUp, self:getDescription())
            for coalition, _ in pairs(self.ennemyCoalitions) do
                trigger.action.outTextForCoalition(coalition, msg, 15)
            end
        end
        if self.onAirbaseUp then
            self.onAirbaseUp(QRA_airportObject)
        end

        self.noAB_announced = false
        self.state = veafQraManager.STATUS_DEAD --QRA that have just been recommisionned act as if they were dead since they need to be rearmed after a delay
        if self.scheduled_state == veafQraManager.STATUS_NOAIRBASE then self.scheduled_state = nil end --make sure you reset the scheduled state if you are within the bounds of this method
    end
end

    -- -- maximum number of QRA ready for action at once, -1 indicates infinite
    -- QRAmaxCount = -1
    -- -- number of groups of aircrafts that can be spawned for this QRA in total, -1 indicates infinite
    -- QRAcount = -1
    -- -- delay in minutes before the QRA counter is increased by one, simulating some sort of logistic chain of aircrafts.
    -- delayBeforeQRAresupply = 0
    -- -- maximum number of resupplies at a given time, simulating some sort of warehousing, -1 indicates infinite. Is decremented every time a resupply happens if not equal to -1 originally. 0 indicated no resupply.
    -- QRAresupplyMax = -1
    -- -- minimum QRAcount that will trigger a resupply, -1 indicates as soon as an aircraft is lost
    -- QRAminCountforResupply = -1
    -- -- how many aircraft groups are resupplied at once   
    --resupplyAmount = 1
    -- -- indicator to know if the QRA is being resupplied or not
    --isResupplying = false

function VeafQRA:checkWarehousing()
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s] resupply state is %s", veaf.p(self.name), veaf.p(self.isResupplying))

    --if a resupply is not already on the way and if there are aircrafts in stock and if the available aircraft count is below the threshold or if an aircraft was just lost and the resupply mode indicates to resupply whenever an aircraft is lost
    if not self.isResupplying and self.QRAresupplyMax ~= 0 and (self.QRAcount < self.QRAminCountforResupply or (self.QRAcount < self.QRAmaxCount or (self.QRAmaxCount == -1 and self.state == veafQraManager.STATUS_DEAD)) and self.QRAminCountforResupply == -1) then
-- LOGGING DISABLED WHEN COMPILING("QRA has %s/%s aircraft groups available", veaf.p(self.QRAcount), veaf.p(self.QRAmaxCount))
-- LOGGING DISABLED WHEN COMPILING("QRA has %s aircraft groups ready for resupply (-1 for infinite)", veaf.p(self.QRAresupplyMax))
-- LOGGING DISABLED WHEN COMPILING("QRA resupply asks for %s aircraft groups", veaf.p(self.resupplyAmount))
        local resupplyAmount = self.resupplyAmount
        --take into account the maximum number of QRA groups as to not oversupply it
        if self.QRAmaxCount ~= -1 and resupplyAmount > self.QRAmaxCount - self.QRAcount then
            resupplyAmount = self.QRAmaxCount - self.QRAcount
-- LOGGING DISABLED WHEN COMPILING("There are only %s available aircraft group slots for this QRA", veaf.p(self.QRAmaxCount - self.QRAcount))
        end

        --take into account the maximum number of QRA groups that can be supplied by the stock
        if self.QRAresupplyMax ~= -1 and resupplyAmount > self.QRAresupplyMax then
            resupplyAmount = self.QRAresupplyMax
-- LOGGING DISABLED WHEN COMPILING("QRA can only be resupplied by %s aircraft groups", veaf.p(self.QRAresupplyMax))
        end

-- LOGGING DISABLED WHEN COMPILING("%s aircraft groups will be handled for resupply", veaf.p(resupplyAmount))
        if resupplyAmount > 0 then
            self.isResupplying = true
            if self.delayBeforeQRAresupply > 0 then
-- LOGGING DISABLED WHEN COMPILING("QRA will be resupplied in %s seconds", veaf.p(self.delayBeforeQRAresupply))
                mist.scheduleFunction(VeafQRA.resupply, {self, resupplyAmount}, timer.getTime()+self.delayBeforeQRAresupply)
            else
-- LOGGING DISABLED WHEN COMPILING("QRA is being resupplied...")
                self:resupply(resupplyAmount)
            end
        end
    end

    if self.QRAcount == 0 then
-- LOGGING DISABLED WHEN COMPILING("QRA is out of aircraft groups")
        if not self.silent and not self.outAnnounced then
            local msg = string.format(self.messageOut, self:getDescription())
            for coalition, _ in pairs(self.ennemyCoalitions) do
                trigger.action.outTextForCoalition(coalition, msg, 15)
            end
            self.outAnnounced = true
        end
        if self.onOut then
            self.onOut()
        end

        self:setScheduledState(veafQraManager.STATUS_OUT)
    end
end

function VeafQRA:resupply(resupplyAmount)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:resupply(%s)", veaf.p(self.name), veaf.p(resupplyAmount))

    --if the QRA can still operate, then execute the resupply, the list would need to be expanded if new scheduled status blocking operations were added
    if self.scheduled_state ~= veafQraManager.STATUS_NOAIRBASE and self.scheduled_state ~= veafQraManager.STATUS_STOP then
        if resupplyAmount and type(resupplyAmount) == 'number' and resupplyAmount > 0 then
-- LOGGING DISABLED WHEN COMPILING("QRA is going to be resupplied, old count is : %s", veaf.p(self.QRAcount))
            self.QRAcount = self.QRAcount + resupplyAmount
-- LOGGING DISABLED WHEN COMPILING("QRA was resupplied, new count is : %s", veaf.p(self.QRAcount))

-- LOGGING DISABLED WHEN COMPILING("QRA previously had %s aircraft groups ready for resupply (-1 for infinite)", veaf.p(self.QRAresupplyMax))
            if self.QRAresupplyMax ~= -1 then
                self.QRAresupplyMax = self.QRAresupplyMax - resupplyAmount
                if self.QRAresupplyMax < 0 then
                    self.QRAresupplyMax = 0
                end
            end
-- LOGGING DISABLED WHEN COMPILING("QRA now only has %s aircraft groups ready for resupply (-1 for infinite)", veaf.p(self.QRAresupplyMax))

            if self.state == veafQraManager.STATUS_OUT then
-- LOGGING DISABLED WHEN COMPILING("QRA now has at least one aircraft group ready for action, resuming service...")
                if not self.silent then
                    local msg = string.format(self.messageResupplied, self:getDescription())
                    for coalition, _ in pairs(self.ennemyCoalitions) do
                        trigger.action.outTextForCoalition(coalition, msg, 15)
                    end
                end
                if self.onResupplied then
                    self.onResupplied()
                end

                self.outAnnounced = false
                self.state = veafQraManager.STATUS_DEAD --QRA that have just arrived act as if the QRA had just died, they need to be rearmed
                if self.scheduled_state == veafQraManager.STATUS_OUT then self.scheduled_state = nil end --make sure you reset the scheduled state if you are within the bounds of this method
            end
        end
    else
-- LOGGING DISABLED WHEN COMPILING("QRA is no longer operating, resupply did not take place")
    end

    self.isResupplying = false
end

function VeafQRA:chooseGroupsToDeploy(nbUnitsInZone)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:chooseGroupsToDeploy(%s)", veaf.p(self.name), veaf.p(nbUnitsInZone))
    local biggestNumberLowerThanUnitsInZone = -1
    local groupsToDeploy = nil
    for enemyNb, groups in pairs(self.groupsToDeployByEnemyQuantity) do
        if nbUnitsInZone >= enemyNb then
            biggestNumberLowerThanUnitsInZone = enemyNb
            groupsToDeploy = groups
        end
    end
    if groupsToDeploy then
        -- process a random group definition
        local groupsToChooseFrom = groupsToDeploy[1]
        local numberOfGroups = groupsToDeploy[2]
        local bias = groupsToDeploy[3]
        if groupsToChooseFrom and type(groupsToChooseFrom) == "table" and numberOfGroups and type(numberOfGroups) == "number" and bias and type(bias) == "number" then
        local result = {}
            for _ = 1, numberOfGroups do
                local group = veaf.randomlyChooseFrom(groupsToChooseFrom, bias)
-- LOGGING DISABLED WHEN COMPILING("group=%s", veaf.p(group))
                table.insert(result, group)
            end
            groupsToDeploy = result
        end
    end
    return groupsToDeploy
end

function VeafQRA:deploy(nbUnitsInZone)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:deploy()", veaf.p(self.name))
-- LOGGING DISABLED WHEN COMPILING("nbUnitsInZone=[%s]", veaf.p(nbUnitsInZone))
    if self.minimumNbEnemyPlanes ~= -1 and self.minimumNbEnemyPlanes > nbUnitsInZone then
-- LOGGING DISABLED WHEN COMPILING("not enough enemies in zone, min=%s", veaf.p(self.minimumNbEnemyPlanes))
        return
    end

    if not self.silent then
        local msg = string.format(self.messageDeploy, self:getDescription())
        for coalition, _ in pairs(self.ennemyCoalitions) do
            trigger.action.outTextForCoalition(coalition, msg, 15)
        end
    end
    local groupsToDeploy = self:chooseGroupsToDeploy(nbUnitsInZone)
    self.spawnedGroupsNames = {}
    if groupsToDeploy then
        local zoneCenter = {}
        if self.triggerZoneName then
            local triggerZone = veaf.getTriggerZone(self.triggerZoneName)
            zoneCenter.x = triggerZone.x
            zoneCenter.z = triggerZone.y
            zoneCenter.y = 0
        elseif self.zoneCenter then
            zoneCenter = self.zoneCenter
        end
        for _, groupNameOrCommand in pairs(groupsToDeploy) do
            -- check if this is a DCS group or a VEAF command
            if veaf.startsWith(groupNameOrCommand, "[") or veaf.startsWith(groupNameOrCommand, "-") then
                -- this is a command
                local command = groupNameOrCommand
                local latDelta = self.respawnDefaultOffset.latDelta
                local lonDelta = self.respawnDefaultOffset.lonDelta
                if veaf.startsWith(groupNameOrCommand, "[") then
                    -- extract relative coordinates and the actual command
                    local coords
                    coords, command = groupNameOrCommand:match("%[(.*)%](.*)")
-- LOGGING DISABLED WHEN COMPILING("coords=%s", veaf.p(coords))
-- LOGGING DISABLED WHEN COMPILING("command=%s", veaf.p(command))
                    if coords then
                        latDelta, lonDelta = coords:match("([%+-%d]+),%s*([%+-%d]+)")
                    end
                end
-- LOGGING DISABLED WHEN COMPILING("running command [%s]", veaf.p(command))
-- LOGGING DISABLED WHEN COMPILING("latDelta = [%s]", veaf.p(latDelta))
-- LOGGING DISABLED WHEN COMPILING("lonDelta = [%s]", veaf.p(lonDelta))
                local position = {x = zoneCenter.x - lonDelta, y = zoneCenter.y, z = zoneCenter.z + latDelta}
                local randomPosition = mist.getRandPointInCircle(position, self.respawnRadius)
                local spawnedGroupsNames = {}
                veafInterpreter.execute(command, randomPosition, self.coalition, nil, spawnedGroupsNames)
                for _, newGroupName in pairs(spawnedGroupsNames) do
                    table.insert(self.spawnedGroupsNames, newGroupName)
                end
            else
                -- this is a DCS group
                local groupName = groupNameOrCommand
-- LOGGING DISABLED WHEN COMPILING("spawning group [%s]", veaf.p(groupName))
                local group = Group.getByName(groupName)
                if not group then
                    veaf.loggers.get(veafQraManager.Id):error("group [%s] does not exist in the mission!", veaf.p(groupName))
                else
-- LOGGING DISABLED WHEN COMPILING("group=%s", veaf.p(group))
-- LOGGING DISABLED WHEN COMPILING("group:getUnits()=%s", veaf.p(group:getUnits()))
                    local spawnSpot = {x = zoneCenter.x - self.respawnDefaultOffset.lonDelta, y = zoneCenter.y, z = zoneCenter.z + self.respawnDefaultOffset.latDelta}
                    -- Try and set the spawn spot at the place the group has been set in the Mission Editor.
                    -- Unfortunately this is sometimes not possible because DCS is not returning the group units for some reason.
                    -- When this happens we'll default to the default spawn offset (same as spawning with VEAF commands)
                    if not group:getUnit(1) then
                        veaf.loggers.get(veafQraManager.Id):warn("group [%s] does not have any unit!", veaf.p(groupName))
                    else
                        spawnSpot =  group:getUnit(1):getPoint()
                    end
                    local vars = {}
                    vars.point = mist.getRandPointInCircle(spawnSpot, self.respawnRadius)
                    vars.point.z = vars.point.y
                    vars.point.y = spawnSpot.y
                    vars.gpName = groupName
                    vars.action = 'clone'
                    vars.route = mist.getGroupRoute(groupName, 'task')
                    local newGroup = mist.teleportToPoint(vars) -- respawn with radius
                    if newGroup then
                        table.insert(self.spawnedGroupsNames, newGroup.name)
                    end
                end
            end
        end
-- LOGGING DISABLED WHEN COMPILING("self.spawnedGroups=%s", veaf.p(self.spawnedGroupsNames))
        self.state = veafQraManager.STATUS_ACTIVE
    end
    if self.onDeploy then
        self.onDeploy(nbUnitsInZone)
    end
end

function VeafQRA:destroyed()
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:destroyed()", veaf.p(self.name))
    if not self.silent then
        local msg = string.format(self.messageDestroyed, self:getDescription())
        for coalition, _ in pairs(self.ennemyCoalitions) do
            trigger.action.outTextForCoalition(coalition, msg, 15)
        end
    end
    if self.onDestroyed then
        self.onDestroyed()
    end
    self.state = veafQraManager.STATUS_DEAD

    if self.QRAcount > 0 then
-- LOGGING DISABLED WHEN COMPILING("QRA will now see one of it's aicraft groups removed")
        self.QRAcount = self.QRAcount - 1
    end
end

function VeafQRA:rearm(silent)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:rearm()", veaf.p(self.name))
    if not self.silent and not silent then
        local msg = string.format(self.messageReady, self:getDescription())
        for coalition, _ in pairs(self.ennemyCoalitions) do
            trigger.action.outTextForCoalition(coalition, msg, 15)
        end
    end
    if self.spawnedGroupsNames then
        for _, groupName in pairs(self.spawnedGroupsNames) do
            local group = Group.getByName(groupName)
            if group then
                group:destroy()
            end
        end
    end
    if self.onReady then
        self.onReady()
    end
    self.state = veafQraManager.STATUS_READY
end

function VeafQRA:start()
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:start()", veaf.p(self.name))
    self.scheduled_state = nil --make sure you reset the scheduled state if you are within the bounds of this method
    self:rearm()
    self:check()

    -- draw the zone
    if self.drawZone then
        if self.triggerZoneName then
            self.zoneDrawing = mist.marker.drawZone(self.triggerZoneName, {message=self:getDescription(), readOnly=true})
        else
            self.zoneDrawing = VeafCircleOnMap:new()
            :setName(self:getName())
            :setCoalition(self:getEnnemyCoalition())
            :setCenter(self.zoneCenter)
            :setRadius(self.zoneRadius)
            :setLineType("dashed")
            :setColor("white")
            :setFillColor("transparent")
            :draw()
        end
    end

    if not self.silent then
        local msg = string.format(self.messageStart, self:getDescription())
        for coalition, _ in pairs(self.ennemyCoalitions) do
            trigger.action.outTextForCoalition(coalition, msg, 15)
        end
    end
    if self.onStart then
        self.onStart()
    end

    return self
end

function VeafQRA:stop(silent)
-- LOGGING DISABLED WHEN COMPILING("VeafQRA[%s]:stop()", veaf.p(self.name))
    self:setScheduledState(veafQraManager.STATUS_STOP)

    -- just in case, despawn the spawned groups
    if self.spawnedGroupsNames then
        for _, groupName in pairs(self.spawnedGroupsNames) do
            local group = Group.getByName(groupName)
            if group then group:destroy() end
        end
    end

    -- erase the zone
    if self.zoneDrawing then
        if self.triggerZoneName then
            mist.marker.remove(self.zoneDrawing.markId)
        else
            self.zoneDrawing:erase()
        end
        self.zoneDrawing = nil
    end

    if not self.silent and not silent then
        local msg = string.format(self.messageStop, self:getDescription())
        for coalition, _ in pairs(self.ennemyCoalitions) do
            trigger.action.outTextForCoalition(coalition, msg, 15)
        end
    end
    if self.onStop then
        self.onStop()
    end

    return self
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafQraManager.add(aQraObject, aName)
  local name = aName or aQraObject:getName()
  veafQraManager.qras[name] = aQraObject
  return aQraObject
end

function veafQraManager.get(aNameString)
  return veafQraManager.qras[aNameString]
end

veaf.loggers.get(veafQraManager.Id):info(string.format("Loading version %s", veafQraManager.Version))

------------------ END script veafQraManager.lua  ------------------


------------------ START script veafSanctuary.lua  ------------------

------------------------------------------------------------------
-- VEAF Sanctuary Zone script
-- By zip (2021)
--
-- Features:
-- ---------
-- * This module offers support for creating sanctuary zones in a mission
-- * A sanctuary zone warns and then destroys all the human aircrafts of other coalitions when they loiter in the zone
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafSanctuary = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafSanctuary.Id = "SANCTUARY"

--- Version.
veafSanctuary.Version = "1.6.2"

-- trace level, specific to this module
--veafSanctuary.LogLevel = "trace"

veaf.loggers.new(veafSanctuary.Id, veafSanctuary.LogLevel)

veafSanctuary.RecordAction = true
veafSanctuary.RecordTrace = false
veafSanctuary.RecordTraceTrespassing = false
veafSanctuary.RecordTraceShooting = false

-- delay before the sanctuary zones start reporting
veafSanctuary.DelayForStartup = 0

-- delay between each check of the sanctuary zones
veafSanctuary.DelayBetweenChecks = 15

-- default delay before warning
veafSanctuary.DEFAULT_DELAY_WARNING = 0

-- default message when entering the zone
veafSanctuary.DEFAULT_MESSAGE_WARNING = "Warning, %s : you've entered a sanctuary zone and will be shot in %d seconds if you don't leave IMMEDIATELY"

-- time to display the messages
veafSanctuary.MESSAGE_TIME = 20

-- default delay before instantly killing the offender
veafSanctuary.DEFAULT_DELAY_INSTANT = -1

-- default delay before spawning defenses
veafSanctuary.DEFAULT_DELAY_SPAWN = -1

-- default message when defenses are spawned
veafSanctuary.DEFAULT_MESSAGE_SPAWN = "You've been warned : deploying defense systems"

-- time to start spawning harder defenses
veafSanctuary.HARDER_DEFENSES_AFTER = 75

-- time to start removing the defenses
veafSanctuary.DELETE_DEFENSES_AFTER = 75

-- time before handling weapons
veafSanctuary.DESTROY_WEAPONS_AFTER = 2

-- clean slate
veafSanctuary.FORGIVE_SHOOTER_AFTER = 10 * 60 -- 10 minutes

-- default message to target when weapon launch is detected
veafSanctuary.DEFAULT_MESSAGE_SHOT_TARGET = "Warning, %s : you've been attacked by %s ; we destroyed the missile in the air !"

-- default message to launcher when weapon launch is detected
veafSanctuary.DEFAULT_MESSAGE_SHOT_LAUNCHER = "Warning, %s : you've attacked %s ; we destroyed the missile in the air. Don't do that again or we'll destroy you !"

-- number of offenses (misile launches at players in a zone) that will justify destruction
veafSanctuary.DEFAULT_OFFENSES_BEFORE_DESTRUCTION = 3

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafSanctuary.initialized = false
veafSanctuary.spawnedSAMs  = {}
veafSanctuary.humanUnitsToFollow  = {}
veafSanctuary.zonesList  = {}
veafSanctuary.humanUnits = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafSanctuary._recordAction(message)
    if message and veafSanctuary.RecordAction then
        local _filename = "sanctuary_zones"
        if veaf.config.MISSION_NAME then
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.config.MISSION_NAME=%s", veaf.p(veaf.config.MISSION_NAME)))
            _filename = _filename .. "-" .. veaf.config.MISSION_NAME
        end
        if veaf.config.SERVER_NAME then
-- LOGGING DISABLED WHEN COMPILING(string.format("veaf.config.SERVER_NAME=%s", veaf.p(veaf.config.SERVER_NAME)))
            _filename = _filename .. "-" .. veaf.config.SERVER_NAME
        end
        _filename = _filename  .. ".log"
-- LOGGING DISABLED WHEN COMPILING(string.format("_filename=%s", veaf.p(_filename)))

        veaf.writeLineToTextFile(message, _filename)
    end
end

function veafSanctuary.recordAction(message)
    if message then
        local _message = "ACTION  - " .. message
        veaf.loggers.get(veafSanctuary.Id):info(_message)
        veafSanctuary._recordAction(veafSanctuary._recordAction(" INFO    SCRIPTING: VEAF - I - " .. _message))
    end
end

function veafSanctuary.recordTrace(message)
    if message and veafSanctuary.RecordTrace then
        local _message = "SANCTUARY - " .. message
-- LOGGING DISABLED WHEN COMPILING(_message)
        veafSanctuary._recordAction(" INFO    SCRIPTING: VEAF - T - " .. _message)
    end
end

function veafSanctuary.recordTraceShooting(message)
    if message and veafSanctuary.RecordTraceShooting then
        local _message = "SHOOTING - " .. message
-- LOGGING DISABLED WHEN COMPILING(_message)
        veafSanctuary._recordAction(" INFO    SCRIPTING: VEAF - T - " .. _message)
    end
end

function veafSanctuary.recordTraceTrespassing(message)
    if message and veafSanctuary.RecordTraceTrespassing then
        local _message = "TRESPASS - " .. message
-- LOGGING DISABLED WHEN COMPILING(_message)
        veafSanctuary._recordAction(" INFO    SCRIPTING: VEAF - T - " .. _message)
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- objects
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafSanctuaryZone = {}

function VeafSanctuaryZone:new(objectToCopy)
    local objectToCreate = objectToCopy or {} -- create object if user does not provide one
    setmetatable(objectToCreate, self)
    self.__index = self

    -- init the new object

    -- name
    objectToCreate.name = nil
    -- coalition that is forbidden to enter the zone
    objectToCreate.protectFromCoalition = nil
    -- if true, missiles fired at units in the zone will be destroyed
    objectToCreate.protectFromMissiles = nil
    -- position on the map
    objectToCreate.position = nil
    -- if set, the zone is a circle of center *position* and of radius *radius*
    objectToCreate.radius = nil
    objectToCreate.radiusSquared = nil
    -- if set, the zone is a polygon - this is a simple list of points
    objectToCreate.polygon = nil
    -- delay before warning - if -1, no warning
    objectToCreate.delayWarning = veafSanctuary.DEFAULT_DELAY_WARNING
    -- warning message
    objectToCreate.messageWarning = veafSanctuary.DEFAULT_MESSAGE_WARNING
    -- delay before instant kill - if -1, no instant kill
    objectToCreate.delayInstant = veafSanctuary.DEFAULT_DELAY_INSTANT
    -- delay before spawn of defense systems - if -1, no spawn
    objectToCreate.delaySpawn = veafSanctuary.DEFAULT_DELAY_SPAWN
    -- spawn message
    objectToCreate.messageSpawn = veafSanctuary.DEFAULT_MESSAGE_SPAWN
    -- message to target when weapon launch is detected
    objectToCreate.messageShotTarget = veafSanctuary.DEFAULT_MESSAGE_SHOT_TARGET
    --message to launcher when weapon launch is detected
    objectToCreate.messageShotLauncher = veafSanctuary.DEFAULT_MESSAGE_SHOT_LAUNCHER
    objectToCreate.spawnedGroups = {}
    objectToCreate.offensesByOffender = {}
    objectToCreate.offensesBeforeDestruction = veafSanctuary.DEFAULT_OFFENSES_BEFORE_DESTRUCTION

    return objectToCreate
end

---
--- setters and getters
---

function VeafSanctuaryZone:setName(value)
    self.name = value
    return self
end

function VeafSanctuaryZone:getName()
    return self.name
end

function VeafSanctuaryZone:setCoalition(value)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafSanctuaryZone[%s]:setCoalition(%s)", veaf.p(self.name), veaf.p(value)))
    self.coalition = value
    return self
end

function VeafSanctuaryZone:getCoalition()
    return self.coalition
end

function VeafSanctuaryZone:setProtectFromMissiles()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafSanctuaryZone[%s]:setProtectFromMissiles()", veaf.p(self.name)))
    self.protectFromMissiles = true
    return self
end

function VeafSanctuaryZone:isProtectFromMissiles()
    return self.protectFromMissiles
end

function VeafSanctuaryZone:setPosition(value)
    self.position = value
    return self
end

function VeafSanctuaryZone:getPosition()
    return self.position
end

function VeafSanctuaryZone:setRadius(value)
    self.radius = value
    if value ~= nil then
        self.radiusSquared = value * value
    else
        self.radiusSquared = nil
    end
    return self
end

function VeafSanctuaryZone:getRadius()
    return self.radius
end

function VeafSanctuaryZone:setPolygon(value)
    self.polygon = value
    return self
end

function VeafSanctuaryZone:setPolygonFromUnitsInSequence(unitNamePrefix, markPositions)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafSanctuaryZone[%s]:setPolygonFromUnitsInSequence(%s, %s)", veaf.p(self.name), veaf.p(unitNamePrefix), veaf.p(markPositions)))

    local unitNames = {}
    local sequence = 0
    while true do
        sequence = sequence + 1
        local unitName = string.format("%s #%03d", unitNamePrefix, sequence)
-- LOGGING DISABLED WHEN COMPILING(string.format("unitName=%s", veaf.p(unitName)))
        local unit = Unit.getByName(unitName)
        if not unit then
            local group = Group.getByName(unitName)
            if group then
                unit = group:getUnit(1)
            end
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("unit=%s", veaf.p(veaf.ifnn(unit, "getID"))))
        if not unit then
            return self:setPolygonFromUnits(unitNames, markPositions)
        else
            table.insert(unitNames, unitName)
        end
    end
end

function VeafSanctuaryZone:setPolygonFromUnits(unitNames, markPositions)

    -- Color of the line marking the zone ({r, g, b, a})
    local LINE_COLOR = {0/255, 255/255, 100/255, 255/255}
    local LINE_TYPE = VeafDrawingOnMap.LINE_TYPE.twodashes

-- LOGGING DISABLED WHEN COMPILING(string.format("VeafSanctuaryZone[%s]:setPolygonFromUnits()", veaf.p(self.name)))
-- LOGGING DISABLED WHEN COMPILING(string.format("markPositions = %s", veaf.p(markPositions)))
    local polygon = veaf.getPolygonFromUnits(unitNames)
    if polygon and #polygon > 0 then
-- LOGGING DISABLED WHEN COMPILING(string.format("polygon = %s", veaf.p(polygon)))
        self:setPolygon(polygon)
        if markPositions then
            local drawing = VeafDrawingOnMap:new()
            :setName(self:getName())
            :setColor(LINE_COLOR)
            :setLineType(LINE_TYPE)
            :addPoints(self:getPolygon())
            :setCoalition(self:getCoalition())
            :draw()
        end
    end
    return self
end

function VeafSanctuaryZone:getPolygon()
    return self.polygon
end

function VeafSanctuaryZone:setDelayWarning(value)
    self.delayWarning = value
    return self
end

function VeafSanctuaryZone:getDelayWarning()
    return self.delayWarning
end

function VeafSanctuaryZone:setOffensesBeforeDestruction(value)
    self.offensesBeforeDestruction = value
    return self
end

function VeafSanctuaryZone:getOffensesBeforeDestruction()
    return self.offensesBeforeDestruction
end

function VeafSanctuaryZone:setMessageWarning(value)
    self.messageWarning = value
    return self
end

function VeafSanctuaryZone:getMessageWarning()
    return self.messageWarning
end

function VeafSanctuaryZone:setMessageShotTarget(value)
    self.messageShotTarget = value
    return self
end

function VeafSanctuaryZone:getMessageShotTarget()
    return self.messageShotTarget
end

function VeafSanctuaryZone:setMessageShotLauncher(value)
    self.messageShotLauncher = value
    return self
end

function VeafSanctuaryZone:getMessageShotLauncher()
    return self.messageShotLauncher
end

function VeafSanctuaryZone:setDelayInstant(value)
    self.delayInstant = value
    return self
end

function VeafSanctuaryZone:getDelayInstant()
    return self.delayInstant
end

function VeafSanctuaryZone:setDelaySpawn(value)
    self.delaySpawn = value
    return self
end

function VeafSanctuaryZone:getDelaySpawn()
    return self.delaySpawn
end

function VeafSanctuaryZone:setMessageSpawn(value)
    self.messageSpawn = value
    return self
end

function VeafSanctuaryZone:getMessageSpawn()
    return self.messageSpawn
end

function VeafSanctuaryZone:addSpawnedGroups(spawnedGroupsNames)
    for _, groupName in pairs(spawnedGroupsNames) do
        self.spawnedGroups[groupName] = timer.getTime()
    end
    return self
end

function VeafSanctuaryZone:getSpawnedGroups()
    return self.spawnedGroups
end

---
--- business methods
---

function VeafSanctuaryZone:deployDefenses(position, unit, timeInZone)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafSanctuaryZone[%s]:deployDefenses()", veaf.p(self.name)))
-- LOGGING DISABLED WHEN COMPILING(string.format("position=%s", veaf.p(position)))
    -- compute the position of the unit in 20 seconds
    local positionIn20s = mist.vec.add(position, mist.vec.scalarMult(unit:getVelocity(), 20))
-- LOGGING DISABLED WHEN COMPILING(string.format("positionIn20s=%s", veaf.p(positionIn20s)))
    -- compute the position of the unit in 40 seconds, 
    local positionIn40s = mist.vec.add(position, mist.vec.scalarMult(unit:getVelocity(), 40))
-- LOGGING DISABLED WHEN COMPILING(string.format("positionIn40s=%s", veaf.p(positionIn40s)))
    -- compute a heading towards the unit
    local heading = mist.utils.round(mist.utils.toDegree(mist.getHeading(unit)), 0)
-- LOGGING DISABLED WHEN COMPILING(string.format("heading=%s", veaf.p(heading)))
    local heading1 = heading*math.random(70,130)/100
-- LOGGING DISABLED WHEN COMPILING(string.format("heading1=%s", veaf.p(heading1)))
    local heading1S = string.format(", hdg %s", tostring(veaf.invertHeading(heading1)))
-- LOGGING DISABLED WHEN COMPILING(string.format("heading1S=%s", veaf.p(heading1S)))
    local heading2 = heading*math.random(70,130)/100
-- LOGGING DISABLED WHEN COMPILING(string.format("heading2=%s", veaf.p(heading2)))
    local heading2S = string.format(", hdg %s", tostring(veaf.invertHeading(heading2)))
-- LOGGING DISABLED WHEN COMPILING(string.format("heading2S=%s", veaf.p(heading2S)))

    if veafShortcuts then
        local ship1 = "-burke"
        local ship2 = "-ticonderoga"
        local sam1 = "-roland"
        local sam2 = "-patriot"
        if self:getCoalition() == 1 then
            -- red side units
            ship1 = "-rezky"
            ship2 = "-pyotr"
            sam1 = "-roland"
            sam2 = "-patriot"
        end

        local spawnedGroupsNames = {}
        local surfaceType = land.getSurfaceType(mist.utils.makeVec2(position))
-- LOGGING DISABLED WHEN COMPILING(string.format("surfaceType=%s", veaf.p(surfaceType)))
        if surfaceType == 2 or surfaceType == 3 then
            -- this is water
            veafShortcuts.ExecuteAlias(ship1, "radius 2000, multiplier 2, skynet false"..heading1S, positionIn20s, self:getCoalition(), nil, true, spawnedGroupsNames)
            veafShortcuts.ExecuteAlias(ship1, "radius 3000, multiplier 2, skynet false"..heading2S, positionIn40s, self:getCoalition(), nil, true, spawnedGroupsNames)
        else
            -- this is land    
            veafShortcuts.ExecuteAlias(sam1, "radius 2000, multiplier 2, skynet false"..heading1S, positionIn20s, self:getCoalition(), nil, true, spawnedGroupsNames)
            veafShortcuts.ExecuteAlias(sam1, "radius 2000, multiplier 2, skynet false"..heading2S, positionIn20s, self:getCoalition(), nil, true, spawnedGroupsNames)
        end
        self:addSpawnedGroups(spawnedGroupsNames)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnedGroupsNames = %s", veaf.p(spawnedGroupsNames)))
        if timeInZone > veafSanctuary.HARDER_DEFENSES_AFTER then
            if surfaceType == 2 or surfaceType == 3 then
                -- this is water
                veafShortcuts.ExecuteAlias(ship2, "radius 3000, multiplier 2, skynet false"..heading1S, positionIn20s, self:getCoalition(), nil, true, spawnedGroupsNames)
                veafShortcuts.ExecuteAlias(ship2, "radius 4000, multiplier 2, skynet false"..heading2S, positionIn40s, self:getCoalition(), nil, true, spawnedGroupsNames)
            else
                -- this is land    
                veafShortcuts.ExecuteAlias(sam2, "radius 3000, skynet false"..heading1S, positionIn20s, self:getCoalition(), nil, true, spawnedGroupsNames)
                veafShortcuts.ExecuteAlias(sam2, "radius 4000, skynet false"..heading2S, positionIn40s, self:getCoalition(), nil, true, spawnedGroupsNames)
            end
            self:addSpawnedGroups(spawnedGroupsNames)
-- LOGGING DISABLED WHEN COMPILING(string.format("spawnedGroupsNames = %s", veaf.p(spawnedGroupsNames)))
        end
    end
end

function VeafSanctuaryZone:cleanupDefenses()
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafSanctuaryZone[%s]:cleanupDefenses()", veaf.p(self.name)))
    local oldestTimeToKeep = timer.getTime() - veafSanctuary.DELETE_DEFENSES_AFTER
    for name, time in pairs(self:getSpawnedGroups()) do
        if time < oldestTimeToKeep then
            local group = Group.getByName(name)
            if group then
                group:destroy()
                self:getSpawnedGroups()[name] = nil
            end
        end
    end
end

function VeafSanctuaryZone:isPositionInZone(position)
    local inZone = false
    if self:getPolygon() then
-- LOGGING DISABLED WHEN COMPILING("polygon mode")
        inZone = mist.pointInPolygon(position, self:getPolygon())
    elseif self:getPosition() then
-- LOGGING DISABLED WHEN COMPILING("circle and radius mode")
        local distanceFromCenter = ((position.x - self:getPosition().x)^2 + (position.z - self:getPosition().z)^2)^0.5
-- LOGGING DISABLED WHEN COMPILING(string.format("distanceFromCenter=%d, radius=%d", distanceFromCenter, self:getRadius()))
        inZone = distanceFromCenter < self:getRadius()
    end
    return inZone
end

function VeafSanctuaryZone:forgive(playerName)
-- LOGGING DISABLED WHEN COMPILING(string.format("VeafSanctuaryZone[%s]:forgive(%s)", veaf.p(self.name), veaf.p(playerName)))
    self.offensesByOffender[playerName] = 0
end

function VeafSanctuaryZone:handleWeapon(weapon)
    veafSanctuary.recordTraceShooting(string.format("VeafSanctuaryZone[%s]:handleWeapon()", veaf.p(self.name)))
    veafSanctuary.recordTraceShooting(string.format("weapon=%s", veaf.p(veaf.ifnns(weapon, {"getID", "getName", "getTypeName"}))))
    if not weapon then
        return
    end
    if self:isProtectFromMissiles() then
        -- check if the missile was shot by a human from the other coalition
        local launcherUnit = weapon:getLauncher()
        veafSanctuary.recordTraceShooting(string.format("launcherUnit=%s", veaf.p(veaf.ifnns(launcherUnit, {"getID", "getName", "getTypeName", "getPlayerName", "getCoalition"}))))
        if launcherUnit and launcherUnit:getCoalition() ~= self:getCoalition() then
            local launcherPlayername = launcherUnit:getPlayerName()
            -- TODO debug - REMOVE LATER
            -- if not launcherPlayername or launcherPlayername == "" then
            --      launcherPlayername = "AI-launcher" 
            -- end
            -- TODO debug - REMOVE LATER
            if launcherPlayername and launcherPlayername ~= "" then
                -- check if the target is a human from our coalition
                local target = weapon:getTarget()
                local targetUnit = Unit.getByName(target:getName())
                veafSanctuary.recordTraceShooting(string.format("targetUnit=%s", veaf.p(veaf.ifnns(targetUnit, {"getID", "getName", "getTypeName", "getPlayerName", "getCoalition"}))))
                if targetUnit and targetUnit:getCoalition() == self:getCoalition() then
                    local targetPlayername = targetUnit:getPlayerName()
                    -- if the target is AI, then protect it anyway (protect assets, prevent bases bombing)
                    if not targetPlayername or targetPlayername == "" then
                        targetPlayername = "AI-target"
                    end
                    -- TODO debug - REMOVE LATER
                   veafSanctuary.recordTraceShooting(string.format("targetPlayername=%s", veaf.p(targetPlayername)))
                    if targetPlayername and targetPlayername ~= "" then
                        -- check if the target is in the zone
                        local position = targetUnit:getPosition().p
                        veafSanctuary.recordTraceShooting(string.format("position=%s", veaf.p(position)))
                        local inZone = self:isPositionInZone(position)
                        if inZone then
                            -- destroy the weapon with flak  - :destroy() does not work for human players and weapons in MP
                            veafSpawn.destroyObjectWithFlak(weapon, 1)
                            -- warn the target
                            local message = string.format(self:getMessageShotTarget(), targetPlayername, launcherPlayername)
                            veafSanctuary.recordAction(string.format("Issuing a warning to target : %s", message))
                            trigger.action.outTextForGroup(targetUnit:getGroup():getID(), message, veafSanctuary.MESSAGE_TIME)
                            -- count the offence
                            local count = self.offensesByOffender[launcherPlayername]
                            if not self.offensesByOffender[launcherPlayername] then
                                self.offensesByOffender[launcherPlayername] = 1
                            else
                                self.offensesByOffender[launcherPlayername] = self.offensesByOffender[launcherPlayername] + 1
                            end
                            veafSanctuary.recordTraceShooting(string.format("self.offensesByOffender[launcherPlayername]=%s", veaf.p(self.offensesByOffender[launcherPlayername])))
                            if self.offensesByOffender[launcherPlayername] >= self:getOffensesBeforeDestruction() then
                                -- destroy the offender
                                local message = string.format("Instantly killing unit %s, too many offenses agains players in zone %s", launcherPlayername, self:getName())
                                trigger.action.outTextForCoalition(self:getCoalition(), message, veafSanctuary.MESSAGE_TIME)
                                veafSanctuary.recordAction(message)
                                -- flak the plane - :destroy() does not work for human players and weapons in MP
                                veafSpawn.destroyObjectWithFlak(launcherUnit, 2, 2)
                                -- forgive the player in 10 minutes (let him get out of trouble and don't kill him straight if he comes back)
                                mist.scheduleFunction(VeafSanctuaryZone.forgive, {self, launcherPlayername}, timer.getTime() + veafSanctuary.FORGIVE_SHOOTER_AFTER)
                            else
                                -- warn the launcher
                                local message = string.format(self:getMessageShotLauncher(), launcherPlayername, targetPlayername)
                                veafSanctuary.recordAction(string.format("Issuing a warning to shooter : %s", message))
                                trigger.action.outTextForGroup(launcherUnit:getGroup():getID(), message, veafSanctuary.MESSAGE_TIME)
                            end
                        end
                    end
                end
            end
        end
    end
end

function VeafSanctuaryZone:handleUnit(unit, data)
    veafSanctuary.recordTraceTrespassing(string.format("VeafSanctuaryZone[%s]:handleUnit()", veaf.p(self.name)))

    if not(unit) then
        return
    end

    local coalition = unit:getCoalition()
    if coalition == self:getCoalition() then
        veafSanctuary.recordTraceTrespassing(string.format("We're not concerned by this unit"))
        return -- we're not concerned by this unit
    end

    local position = unit:getPosition().p
    veafSanctuary.recordTraceTrespassing(string.format("position=%s", veaf.p(position)))
    local inZone = self:isPositionInZone(position)
    veafSanctuary.recordTraceTrespassing(string.format("inZone=%s", veaf.p(inZone)))

    -- let's decide what we do
    if inZone then
        local firstInZone = data.firstInZone
        if firstInZone < 0 then
            firstInZone = timer.getTime()
            data.firstInZone = firstInZone
        end
        local unitname = unit:getName()
        local playername = unit:getPlayerName()
        local callsign = unit:getCallsign()
        local timeInZone = timer.getTime() - firstInZone
        veafSanctuary.recordTraceTrespassing(string.format("unitname=%s, playername=%s, callsign=%s", veaf.p(unitname), veaf.p(playername), veaf.p(callsign)))

        local message = string.format("Unit %s is in the %s zone since %d seconds", playername, self:getName(), timeInZone)
        trigger.action.outTextForCoalition(self:getCoalition(), message, veafSanctuary.MESSAGE_TIME)
        veafSanctuary.recordAction(message)
        local groupId = unit:getGroup():getID()
        if self:getDelayInstant() > -1 and timeInZone >= self:getDelayInstant() then
            -- insta-death !
            local message = string.format("Instantly killing unit %s, in zone %s since %d seconds", playername, self:getName(), timeInZone)
            trigger.action.outTextForCoalition(self:getCoalition(), message, veafSanctuary.MESSAGE_TIME)
            veafSanctuary.recordAction(message)
            -- flak the plane - :destroy() does not work for human players and weapons in MP
            veafSpawn.destroyObjectWithFlak(unit, 2, 2)
        elseif self:getDelaySpawn() > -1 and timeInZone >= self:getDelaySpawn() then
            -- spawn defense systems
            self:deployDefenses(position, unit, timeInZone)

            local message = string.format("Spawning defense systems to fend off unit %s, in zone %s since %d seconds", playername, self:getName(), timeInZone)
            veafSanctuary.recordAction(string.format("Issuing a warning to protected coalition : %s", message))
            trigger.action.outTextForCoalition(self:getCoalition(), message, veafSanctuary.MESSAGE_TIME)

            local message = string.format("CRITICAL: %s - %s", playername, self:getMessageSpawn())
            veafSanctuary.recordAction(string.format("Issuing a warning to trespasser : %s", message))
            trigger.action.outTextForGroup(groupId, message, veafSanctuary.MESSAGE_TIME)
        elseif self:getDelayWarning() > -1 and timeInZone >= self:getDelayWarning() then
            -- simple warning
            local delay = self:getDelayInstant()
            if delay < 0 or (self:getDelaySpawn() > 0 and self:getDelaySpawn() < delay) then
                delay = self:getDelaySpawn()
            end
            local message = string.format(self:getMessageWarning(), playername, delay - timeInZone)
            veafSanctuary.recordAction(string.format("Issuing a warning to trespasser : %s", message))
            trigger.action.outTextForGroup(groupId, message, veafSanctuary.MESSAGE_TIME)
        end
    elseif data.firstInZone >= 0 then
        local playername = unit:getPlayerName()
        -- reset the counter
        local message = string.format("%s got out of the zone", veaf.p(playername))
        veafSanctuary.recordAction(message)
        data.firstInZone = -1
    end

end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- core functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- add a zone
function veafSanctuary.addZone(zone)
-- LOGGING DISABLED WHEN COMPILING(string.format("addZone(%s)", veaf.p(zone:getName())))
    table.insert(veafSanctuary.zonesList, zone)
    return zone
end

-- add a zone from a DCS trigger zone
function veafSanctuary.addZoneFromTriggerZone(triggerZoneName)
-- LOGGING DISABLED WHEN COMPILING(string.format("addZoneFromTriggerZone(%s)", veaf.p(triggerZoneName)))
    local triggerZone = trigger.misc.getZone(triggerZoneName)
    if triggerZoneName then
        local zone = VeafSanctuaryZone:new():setName(triggerZoneName):setRadius(triggerZone.radius):setPosition(triggerZone.point)
        return veafSanctuary.addZone(zone)
    end
end

-- Handle world events.
veafSanctuary.eventHandler = {}
function veafSanctuary.eventHandler:onEvent(event)

    if event == nil or event.id == nil then
        return
    end

    if not(
           event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT
        or event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT
        or event.id == world.event.S_EVENT_BIRTH
        or event.id == world.event.S_EVENT_DEAD
        or event.id == world.event.S_EVENT_SHOT
        ) then
        return
    end

    if (event.id == world.event.S_EVENT_SHOT) then -- process shooting events
        veafSanctuary.recordTraceShooting("S_EVENT_SHOT !")
        -- process all zones
        for _, zone in pairs(veafSanctuary.zonesList) do
-- LOGGING DISABLED WHEN COMPILING(string.format("zone:getName()=%s", veaf.p(zone:getName())))
            mist.scheduleFunction(VeafSanctuaryZone.handleWeapon, {zone, event.weapon}, timer.getTime() + veafSanctuary.DESTROY_WEAPONS_AFTER)
        end
    else -- process human players events
        if not event.initiator then
            return
        end

        local eventId = event.id
        if veaf.EVENTMETA[event.id] then
            eventId = veaf.EVENTMETA[event.id].Text
        end

        local _unitname = event.initiator:getName()
-- LOGGING DISABLED WHEN COMPILING(string.format("event initiator unit  = %s", veaf.p(_unitname)))
        if event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT
        or event.id == world.event.S_EVENT_BIRTH and _unitname and veafSanctuary.humanUnits[_unitname]
        then
            veafSanctuary.recordTrace(string.format("event=%s",veaf.p(eventId)))
            if (not veafSanctuary.humanUnitsToFollow[_unitname]) then
                -- register the human unit in the follow-up list when the human gets in the unit
                veafSanctuary.recordTrace(string.format("registering human unit to follow: %s", veaf.p(_unitname)))
                veafSanctuary.humanUnitsToFollow[_unitname] = { firstInZone = -1}
            end
        elseif event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT
        or     event.id == world.event.S_EVENT_DEAD and _unitname and veafSanctuary.humanUnits[_unitname]
        then
            veafSanctuary.recordTrace(string.format("event=%s",veaf.p(eventId)))
            if (veafSanctuary.humanUnitsToFollow[_unitname]) then
                -- unregister the human unit from the follow-up list when the human gets in the unit
                veafSanctuary.recordTrace(string.format("deregistering human unit to follow: %s", veaf.p(_unitname)))
                veafSanctuary.humanUnitsToFollow[_unitname] = nil
            end
        end
    end
end

-- main loop
function veafSanctuary.loop()
-- LOGGING DISABLED WHEN COMPILING("veafSanctuary.loop()")

    -- process all zones
    for _, zone in pairs(veafSanctuary.zonesList) do
-- LOGGING DISABLED WHEN COMPILING(string.format("zone:getName()=%s", veaf.p(zone:getName())))

        zone:cleanupDefenses()

        -- browse all the human units and check if they're in a zone
        for name, data in pairs(veafSanctuary.humanUnitsToFollow) do
-- LOGGING DISABLED WHEN COMPILING(string.format("name=%s", veaf.p(name)))
            local unit = Unit.getByName(name)
            if unit then
                zone:handleUnit(unit, data)
            else
                -- stop following this unit, it's been destroyed
                veafSanctuary.humanUnitsToFollow[name] = nil
            end
        end
    end

    mist.scheduleFunction(veafSanctuary.loop, {}, timer.getTime() + veafSanctuary.DelayBetweenChecks)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafSanctuary.initialize()
    veafSanctuary.recordAction("Initializing module")

    -- prepare humans units
    veafSanctuary.humanUnits = {}
    for name, _ in pairs(mist.DBs.humansByName) do
-- LOGGING DISABLED WHEN COMPILING(string.format("mist.DBs.humansByName[%s]=??", veaf.p(name)))
        veafSanctuary.humanUnits[name] = true
    end

    --- Add the event handler.
    world.addEventHandler(veafSanctuary.eventHandler)

    -- Start the main loop
    mist.scheduleFunction(veafSanctuary.loop, {}, timer.getTime() + veafSanctuary.DelayForStartup)

    veafSanctuary.initialized = true
    veaf.loggers.get(veafSanctuary.Id):info(string.format("Sanctuary system has been initialized"))
end

veaf.loggers.get(veafSanctuary.Id):info(string.format("Loading version %s", veafSanctuary.Version))

------------------ END script veafSanctuary.lua  ------------------


------------------ START script veafSkynetIadsHelper.lua  ------------------

------------------------------------------------------------------
-- VEAF helper for Skynet-IADS
-- By zip (2021)
--
-- Features:
-- ---------
-- * This module offers support for integrating Skynet-IADS in a mission
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafSkynet = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafSkynet.Id = "SKYNET"

--- Version.
veafSkynet.Version = "3.1.1"

-- trace level, specific to this module
--veafSkynet.LogLevel = "trace"

veaf.loggers.new(veafSkynet.Id, veafSkynet.LogLevel)

-- delay before the mission groups are added to the IADS' at start
veafSkynet.DelayForStartup = 1

-- delay before restarting the IADS when adding a single group
veafSkynet.DelayForRestart = 20

-- maximum x or y (z in DCS) between a SAM site and it's point defenses in meters
veafSkynet.MaxPointDefenseDistanceFromSite = 10000

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafSkynet.initialized = false
--flag to know if all units present on the map should be loaded at init or not into their team's main IADS network
veafSkynet.loadAllAtInit = {
    [tostring(coalition.side.BLUE)] = true,
    [tostring(coalition.side.RED)] = true
}
--table containing the default IADS network names initialized for each coalition
veafSkynet.defaultIADS = {
    [tostring(coalition.side.BLUE)] = "blue iads",
    [tostring(coalition.side.RED)] = "red iads",
}
veafSkynet.iadsSamUnitsTypes = {}
veafSkynet.iadsEwrUnitsTypes = {}

veafSkynet.GroupIntegrationModes =
{
    Strict = 0, -- groups will only be included in the skynet networks if they contains only units known to skynet
    Lenient = 1 -- groups will be included in the skynet networks even if some units are not known to skynet
}
veafSkynet.GroupIntegrationMode = veafSkynet.GroupIntegrationModes.Lenient -- lenient by default

-- Management of point defences (Flogas)
-- when active, veafSkynet will attempt to set eligible groups as point defence of nearby defencible groups
-- as per Skynet database types :
--  - "single" groups will be eligible to defend "complex" or "ewr" groups
--  - "complex" groups will be eligible to defend "ewr" groups
--  - "single" groups will never be defended
veafSkynet.PointDefenceModes =
{
    None = 0, -- point defences will not be created
    Skynet = 1, -- point defences will be defined with the skynet logic
    Dcs = 2 -- point defences will be defined as not a part of the skynet network and left to the dcs ai
}
veafSkynet.PointDefenceMode = veafSkynet.PointDefenceModes.None -- no point defences by default

veafSkynet.DynamicSpawn = false -- false by default

veafSkynet.SkynetElementStates =
{
    Autonomous = 0,
    Live = 1,
    Dark = 2
}

--table containing the structure of each IADS network, first level is accessed with the IADS name. This contains the .coalitionID of the network, the IADS network (.iads), the groups added to the network (.groups) stored by groupName, 
--wether this network should appear on the radio menu (.includeInRadio) and lastly if this network is in debug mode (.debugFlag). The groups store whether the group was .forceEwr or .pointDefense.
veafSkynet.structure = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafSkynet.getStringSkynetElement(skynetElement)
    local s = skynetElement.dcsName

    if (not skynetElement.dcsRepresentation:isExist()) then
        return s .. " (dcs object does not exist)"    
    end

    local id = skynetElement.dcsRepresentation:getID()
    local category = getmetatable(skynetElement.dcsRepresentation)
    local sCategory = "unknown"
    if (category == Group) then
        sCategory = "group"
    elseif (category == Unit) then
        sCategory = "unit"
    elseif (category == StaticObject) then
        sCategory = "static"
    end

    if (skynetElement.getNatoName) then
        s = s .. " [" .. skynetElement:getNatoName() .. "]"
    else
        s = s .. " [" .. skynetElement.typeName .. "]"
    end

    s = s .. " [id=" .. id .. "]" .. " [" .. sCategory .. "]"
    return s
end

function veafSkynet.getDcsGroupFromSkynetElement(skynetElement)
    if (skynetElement.dcsRepresentation and skynetElement.dcsRepresentation:isExist()) then
        local category = getmetatable(skynetElement.dcsRepresentation)
        if (category == Group) then
            return skynetElement.dcsRepresentation
        elseif (category == Unit) then
            return Unit.getGroup(skynetElement.dcsRepresentation)
        end
    end
    
    return nil
end

function veafSkynet.getSkynetData(skynetElement)
    local function skynetDatabaseMatchType(skynetElementTypeList, skynetDatabaseTypeList)
        if (skynetDatabaseTypeList and skynetElementTypeList and #skynetElementTypeList > 0) then
            for i = 1, #skynetElementTypeList do
                if (skynetDatabaseTypeList[skynetElementTypeList[i].typeName]) then
                    return true
                end
            end
        end

        return false
    end

    for skynetDataName, skynetData in pairs(SkynetIADS.database) do
        -- first check the launchers as they are the most unique thing
        if(skynetDatabaseMatchType(skynetElement.launchers, skynetData["launchers"])) then
-- LOGGING DISABLED WHEN COMPILING("Matched by launcher : " .. veafSkynet.getStringSkynetElement(skynetElement) .. " > " .. skynetDataName)
            return skynetData
        end

        -- tracking and search radars can be used by multiple sites
        if(skynetDatabaseMatchType(skynetElement.trackingRadars, skynetData["trackingRadar"])) then
-- LOGGING DISABLED WHEN COMPILING("Matched by TR : " .. veafSkynet.getStringSkynetElement(skynetElement) .. " > " .. skynetDataName)
            return skynetData
        end
        if(skynetDatabaseMatchType(skynetElement.searchRadars, skynetData["searchRadar"])) then
-- LOGGING DISABLED WHEN COMPILING("Matched by SR : " .. veafSkynet.getStringSkynetElement(skynetElement) .. " > " .. skynetDataName)
            return skynetData
        end
    end

-- LOGGING DISABLED WHEN COMPILING("No match : " .. veafSkynet.getStringSkynetElement(skynetElement))
    return nil
end

function veafSkynet.removeSkynetElement(skynetElement, veafSkynetNetwork)
    local iads = veafSkynetNetwork.iads

-- LOGGING DISABLED WHEN COMPILING("Remove skynet element [" .. veafSkynet.getStringSkynetElement(skynetElement) .. "]")

    local function _removeSkynetElementFromList(list, skynetElement)
        local iIndex = -1
        if (list and #list > 0) then
            for i = 1, #list do
                if (list[i] == skynetElement) then
                    iIndex = i
                    break
                end
            end
    
            if (iIndex > 0) then
                table.remove(list, iIndex) 
            end
        end
    end

    skynetElement:cleanUp()
    skynetElement:getDCSRepresentation():enableEmission(true)

    local list = iads.samSites
-- LOGGING DISABLED WHEN COMPILING("Sam sites count: " .. #list)

    _removeSkynetElementFromList(list, skynetElement)
    --_removeSkynetElementFromList(iads:getEarlyWarningRadars(), skynetElement)

-- LOGGING DISABLED WHEN COMPILING("Sam sites count: " .. #list) -- not removed here

    local dcsGroup = veafSkynet.getDcsGroupFromSkynetElement(skynetElement)
    veafSkynetNetwork.groups[dcsGroup:getName()] = nil
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- core functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- get the IADS network for a given name ("blue iads", "red iads"")
function veafSkynet.getNetwork(networkName)
    local network = nil
    if networkName then
        network = veafSkynet.structure[networkName]
    end
    return network
end

-- get the IADS object for a given name ("blue iads", "red iads"")
function veafSkynet.getIADS(networkName)
    local iads = nil
    local network = veafSkynet.getNetwork(networkName)
    if network then
        iads = network.iads
    end
    return iads
end

-- calling SkynetIADS:activate() after a delay, to avoid calling it at each time a group is added to the IADS
function veafSkynet.delayedActivate(networkName)
-- LOGGING DISABLED WHEN COMPILING("veafSkynet.delayedActivate(%s)", veaf.p(networkName))
    local network = veafSkynet.structure[networkName]
    if network then
        if network.delayedActivation then
-- LOGGING DISABLED WHEN COMPILING(string.format("IADS %s already has a delayed activation", veaf.p(networkName)))
        else
-- LOGGING DISABLED WHEN COMPILING(string.format("IADS %s will be activated in %d seconds", veaf.p(networkName), veafSkynet.DelayForRestart))
            network.delayedActivation = mist.scheduleFunction(veafSkynet._activateIADS, {networkName}, timer.getTime() + veafSkynet.DelayForRestart)
        end
    end
end

function veafSkynet._activateIADS(networkName)
-- LOGGING DISABLED WHEN COMPILING("veafSkynet._activateIADS(%s)", veaf.p(networkName))

    local network = veafSkynet.structure[networkName]
    if network then
        network.delayedActivation = nil
        local iads = network.iads
        if iads then
-- LOGGING DISABLED WHEN COMPILING("calling iads:activate()")
            iads:activate()
        end
    end
end

function veafSkynet.getIadsOfCoalition(networkName, coa)
    local iads = nil
    if veafSkynet.structure[networkName] and coa == veafSkynet.structure[networkName].coalitionID then
        iads = veafSkynet.structure[networkName].iads
    end
    return iads
end

function veafSkynet.getNearestIADSSite(networkName, dcsGroup)
    
    if not dcsGroup then
-- LOGGING DISABLED WHEN COMPILING("No group to find the nearest IADS site for")
        return false
    end

    local coa = dcsGroup:getCoalition()
-- LOGGING DISABLED WHEN COMPILING(string.format("Ref coalition : %s", veaf.p(coa))) 

    local iads = veafSkynet.getIadsOfCoalition(networkName, coa)
    if not iads then
-- LOGGING DISABLED WHEN COMPILING(string.format("IADS named %s for the coalition of the group %s does not exist", veaf.p(networkName), tostring(dcsGroup:getName())))
        return false
    end
    
    local currentGroup = dcsGroup:getName()
-- LOGGING DISABLED WHEN COMPILING(string.format("networkName : %s", veaf.p(networkName))) 
    local groupPos = veaf.getAveragePosition(dcsGroup)
-- LOGGING DISABLED WHEN COMPILING(string.format("Ref Position : %s", veaf.p(groupPos))) 


    local nearestEWRname = nil
    local minEWRDistance = veafSkynet.MaxPointDefenseDistanceFromSite
    local nearestSAMname = nil
    local minSAMDistance = veafSkynet.MaxPointDefenseDistanceFromSite

    local CoalitionSites = nil

    local searchForGroup = function(CoalitionSites, pos, currentGroupName, ewrFlag)
        local minDistance = veafSkynet.MaxPointDefenseDistanceFromSite
        local FoundGroup = nil

        for site, site_info in pairs(CoalitionSites) do
            local site_name = site_info.dcsName -- For EWRs it looks like this gives the unit's name which would need to be reversed to the group to get position data
-- LOGGING DISABLED WHEN COMPILING(string.format("Checked Site groupName : %s and isEWR : %s", veaf.p(site_name), veaf.p(ewrFlag))) 
            
            if site_name and currentGroupName ~= site_name then
                if ewrFlag then
                    local unit = Unit.getByName(site_name)
                    local group = Unit.getGroup(unit)
                    site_name = Group.getName(group)
                end
-- LOGGING DISABLED WHEN COMPILING(string.format("Checked Site groupName : %s", veaf.p(site_name)))

                local groupAvgPosition = veaf.getAveragePosition(site_name)
-- LOGGING DISABLED WHEN COMPILING(string.format("Checked Site groupAvgPosition : %s", veaf.p(groupAvgPosition)))

                if groupAvgPosition then
                    local distance = math.sqrt((pos.x-groupAvgPosition.x)^2+(pos.z-groupAvgPosition.z)^2)
-- LOGGING DISABLED WHEN COMPILING(string.format("Distance between checked site and pointDefense : %s", veaf.p(distance)))

                    if distance <= minDistance then
-- LOGGING DISABLED WHEN COMPILING("This site is closer")
                        FoundGroup = site_name
                        minDistance = distance
                    end
                end
            end
        end 
       
        return FoundGroup, minDistance
    end

    --start by going through the EWRs
    CoalitionSites = iads:getEarlyWarningRadars()
    nearestEWRname, minEWRDistance = searchForGroup(CoalitionSites, groupPos, currentGroup, true)

    --search for SAM sites
    CoalitionSites = iads:getSAMSites()
    nearestSAMname, minSAMDistance = searchForGroup(CoalitionSites, groupPos, currentGroup)    

    if minEWRDistance <= minSAMDistance then
        return nearestEWRname
    end
    return nearestSAMname
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Management of point defences (Flogas)
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafSkynet.canBePointDefence(skynetData)
    if (skynetData == nil) then
        return false
    end
    if (skynetData["can_engage_harm"] ~= true) then
        return false
    end
    if (skynetData["type"] ~= "complex" and skynetData["type"] ~= "single") then
        return false
    end

    return true
end

function veafSkynet.findSkynetElementToDefend(skynetElementPointDefence, skynetDataPointDefence)
    local dcsGroupPointDefence = veafSkynet.getDcsGroupFromSkynetElement(skynetElementPointDefence)
    local iads = skynetElementPointDefence.iads
    local pointDefenceType = skynetDataPointDefence["type"]

    local pointDefencePosition = veaf.getAveragePosition(dcsGroupPointDefence)
    if (pointDefencePosition == nil) then
        return nil
    end

    local function findClosestSkynetElementInList(skynetElementList)
        if (skynetElementList == nil or #skynetElementList <= 0) then
            return nil
        end

        local iMinDistance = veafSkynet.MaxPointDefenseDistanceFromSite
        local foundElement
        for i = 1, #skynetElementList do
            local skynetElement = skynetElementList[i]
            local skynetData = veafSkynet.getSkynetData(skynetElement)
            if (skynetData and (skynetData["type"] == "ewr" or (skynetData["type"] == "complex" and pointDefenceType == "single"))) then
                -- ewr are always defencible, and complex sam sites only defencible by single groups
                local dcsGroup = veafSkynet.getDcsGroupFromSkynetElement(skynetElementList[i])
                local position = veaf.getAveragePosition(dcsGroup)

                if (position) then
                    local iDistance = math.sqrt((position.x - pointDefencePosition.x)^2 + (position.z - pointDefencePosition.z)^2)
             
                    if (iDistance < iMinDistance) then
                        foundElement = skynetElement
                        iMinDistance = iDistance
                    end
                end
            end
        end

        return foundElement
    end

    local elementToDefend = findClosestSkynetElementInList (iads:getEarlyWarningRadars())
    if (elementToDefend == nil and pointDefenceType == "single") then
        elementToDefend = findClosestSkynetElementInList (iads:getSAMSites())
    end

    if (elementToDefend) then
        local sDescriptorPointDefence = veafSkynet.getStringSkynetElement(skynetElementPointDefence)
        local sDescriptorToDefend = veafSkynet.getStringSkynetElement(elementToDefend)
-- LOGGING DISABLED WHEN COMPILING("Identified [ " .. sDescriptorToDefend .. " ] to be defended by [ " .. sDescriptorPointDefence .. " ]")
    end

    return elementToDefend
end

function veafSkynet.removePointDefencesFromSkynetElement(skynetElement)
    if(skynetElement and skynetElement.pointDefences and #skynetElement.pointDefences > 0) then
        for i = 1, #skynetElement.pointDefences do
            skynetElement.pointDefences[i]:setIsAPointDefence(false)
        end
        skynetElement.pointDefences = {}
    end

    skynetElement.pointDefences = {}
end

function veafSkynet.removePointDefences(iads)
    -- not tested
    local ewrs = iads:getEarlyWarningRadars()
    if (ewrs and #ewrs > 0) then
        for i = 1, #ewrs do
            veafSkynet.removePointDefencesFromSkynetElement(ewrs[i])
        end
    end
    local samSites = iads:getSAMSites()
    if (samSites and #samSites > 0) then
        for i = 1, #samSites do
            veafSkynet.removePointDefencesFromSkynetElement(samSites[i])
        end
    end
end

function veafSkynet.initializePointDefenceSamSite(samSite, veafSkynetNetwork)
    if (samSite:getIsAPointDefence()) then
        return
    end

    local skynetData = veafSkynet.getSkynetData(samSite)
    if (not veafSkynet.canBePointDefence(skynetData)) then
        return
    end

    local elementToDefend = veafSkynet.findSkynetElementToDefend(samSite, skynetData)
    if (elementToDefend) then
        if (veafSkynet.PointDefenceMode == veafSkynet.PointDefenceModes.Skynet) then
-- LOGGING DISABLED WHEN COMPILING("Point defence: add as skynet")
            elementToDefend:addPointDefence(samSite)
        elseif (veafSkynet.PointDefenceMode == veafSkynet.PointDefenceModes.Dcs) then
-- LOGGING DISABLED WHEN COMPILING("Point defence: add as dcs")
            veafSkynet.removeSkynetElement(samSite, veafSkynetNetwork)
        end
    end
end

function veafSkynet.initializePointDefences(veafSkynetNetwork)
    if (veafSkynet.PointDefenceMode ~= veafSkynet.PointDefenceModes.Skynet and veafSkynet.PointDefenceMode ~= veafSkynet.PointDefenceModes.Dcs) then
        return
    end
  
-- LOGGING DISABLED WHEN COMPILING("Analyzing network to create point defences")

    local iads = veafSkynetNetwork.iads
    local samSites = iads:getSAMSites()
    if (samSites and #samSites > 0) then
        for i = 1, #samSites do
            veafSkynet.initializePointDefenceSamSite(samSites[i], veafSkynetNetwork)
        end
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Management of dynamic group spawns (Flogas)
-- Option to integrate late spawned units into the existing networks
-------------------------------------------------------------------------------------------------------------------------------------------------------------
veafSkynet.monitorDynamicSpawnHandlerId = nil

function veafSkynet.OnDynamicSpawn(event)
    if(not veafSkynet.initialized) then
        return
    end
    if(event.id ~= world.event.S_EVENT_BIRTH) then
        return
    end
    if (event.initiator == nil or Object.getCategory(event.initiator) ~= Object.Category.UNIT) then
        return
    end

    -- birth event will be triggered for each unit of a spawned group, but we want to manage the group, so we only work for for the first unit
    local dcsGroup = Unit.getGroup (event.initiator)
    local firstDcsUnit = dcsGroup:getUnit(1)
    if(firstDcsUnit == nil) then
        return
    end
    if(firstDcsUnit:getID() ~= event.initiator:getID()) then
        return 
    end

    local coalition = dcsGroup:getCoalition()
    local networkName = veafSkynet.defaultIADS[tostring(coalition)]

-- LOGGING DISABLED WHEN COMPILING("DYNAMIC SPAWN adding spawned group [" .. dcsGroup:getName() .. "] [id=" .. dcsGroup:getID() .. "] to IADS network [" .. networkName .. "]")
    if (veafSkynet.addGroupToNetwork(networkName, dcsGroup, false, false)) then
        veafSkynet.initializePointDefences(veafSkynet.getNetwork(networkName))
        --iads:buildRadarCoverage()
    end
end

function veafSkynet.monitorDynamicSpawn(bMonitor)
    if (bMonitor) then
        if (veafSkynet.monitorDynamicSpawnHandlerId ~= nil) then
            return -- already active
        end
-- LOGGING DISABLED WHEN COMPILING("DYNAMIC SPAWN monitoring ON")
        veafSkynet.monitorDynamicSpawnHandlerId = mist.addEventHandler(veafSkynet.OnDynamicSpawn)
    else
        if (veafSkynet.monitorDynamicSpawnHandlerId == nil) then
            return -- already inactive
        end
-- LOGGING DISABLED WHEN COMPILING("DYNAMIC SPAWN monitoring OFF")
        mist.removeEventHandler(veafSkynet.monitorDynamicSpawnHandlerId)
        veafSkynet.monitorDynamicSpawnHandlerId = nil
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafSkynet.isGroupUsable(dcsGroup)
    if (veafSkynet.GroupIntegrationMode == veafSkynet.GroupIntegrationModes.Strict) then
        for _, dcsUnit in pairs(dcsGroup:getUnits()) do
            local dcsUnitType = dcsUnit:getTypeName()
            if (not veafSkynet.iadsEwrUnitsTypes[dcsUnitType] and not veafSkynet.iadsSamUnitsTypes[dcsUnitType]) then
                return false
            end
        end

        return true
    else
        for _, dcsUnit in pairs(dcsGroup:getUnits()) do
            local dcsUnitType = dcsUnit:getTypeName()
            if (veafSkynet.iadsEwrUnitsTypes[dcsUnitType] or veafSkynet.iadsSamUnitsTypes[dcsUnitType]) then
                return true
            end
        end

        return false
    end
end

function veafSkynet.addGroupToNetwork(networkName, dcsGroup, forceEwr, pointDefense, alreadyAddedGroups, silent)
-- LOGGING DISABLED WHEN COMPILING("ADD GROUP START [" .. dcsGroup:getName() .. "] [id=" .. dcsGroup:getID() .. "] to IADS network [" .. networkName .. "]")

    if not dcsGroup then
        veaf.loggers.get(veafSkynet.Id):error("No group to find to add to network")
        return false
    end
    
    if (not veafSkynet.isGroupUsable(dcsGroup)) then
-- LOGGING DISABLED WHEN COMPILING("Group is not usable for skynet")
        return false        
    end
    
    local forceEwr = false or forceEwr
    local pointDefense = false or pointDefense
    local silent = false or silent

    local batchMode = (alreadyAddedGroups ~= nil)
    local alreadyAddedGroups = alreadyAddedGroups or {}
    local groupName = dcsGroup:getName()
    local coa = dcsGroup:getCoalition()
    local iads = veafSkynet.getIadsOfCoalition(networkName, coa)
    if not(iads) then
-- LOGGING DISABLED WHEN COMPILING(string.format("IADS named %s for the coalition of the group %s does not exist", veaf.p(networkName), tostring(groupName)))
        return false
    end
    local didSomething = false

-- LOGGING DISABLED WHEN COMPILING(string.format("batchMode=%s forceEwr=%s PointDefense=%s", tostring(batchMode), tostring(forceEwr), tostring(pointDefense)))

    local defended_name = nil
    if pointDefense and not(forceEwr) then
-- LOGGING DISABLED WHEN COMPILING(string.format("SAM is requested as pointDefense"))
        
        if pointDefense == true then 
-- LOGGING DISABLED WHEN COMPILING(string.format("Find nearest site to defend"))
            defended_name = veafSkynet.getNearestIADSSite(networkName, dcsGroup)
        else
            defended_name = pointDefense
            local defended_SAM = iads:getSAMSiteByGroupName(defended_name)
            local defended_EWR = iads:getEarlyWarningRadars(defended_name)

            local defended_site = defended_EWR
            if defended_SAM then
                defended_site = defended_SAM
            end

            if defended_site then
                local defended_pos = veaf.getAvgGroupPos(defended_name)
                local dcsGroup_pos = veaf.getAvgGroupPos(groupName)
                local distance = math.sqrt((dcsGroup_pos.x-defended_pos.x)^2+(dcsGroup_pos.z-defended_pos.z)^2)
-- LOGGING DISABLED WHEN COMPILING(string.format("Distance between requested site and pointDefense : %s", veaf.p(distance)))

                if distance > veafSkynet.MaxPointDefenseDistanceFromSite then
                    defended_name = nil
                    veaf.loggers.get(veafSkynet.Id):info("User requested SAM Site out of reach for point defense")
                end
            else
                defended_name = nil
            end 
        end
    end

    local dcsGroupName = dcsGroup:getName()

    for _, dcsUnit in pairs(dcsGroup:getUnits()) do
        local dcsUnitName = dcsUnit:getName()
        local dcsUnitType = dcsUnit:getTypeName()
        
        local addedSite = nil
        local sLog = string.format(" - iads[%s] unit[%s][%d][%s] group[%s][%d]", iads.name, dcsUnitName, dcsUnit:getID(), dcsUnitType, dcsGroupName, dcsGroup:getID())
    
        if(not veafSkynet.iadsEwrUnitsTypes[dcsUnitType] and not veafSkynet.iadsSamUnitsTypes[dcsUnitType]) then
-- LOGGING DISABLED WHEN COMPILING(sLog .. " => unit type is not eligible for skynet")
        elseif(alreadyAddedGroups[groupName]) then
-- LOGGING DISABLED WHEN COMPILING(sLog .. " => group as already been marked as added by veafSkynet")
            break -- if group is added no need to go check the other units
        else
            local bShouldBeAdded = true
            local sams = iads:getSAMSites()
            for i = 1, #sams do
                local sam = sams[i]
                if (sam.dcsName == dcsGroupName) then
-- LOGGING DISABLED WHEN COMPILING(sLog .. " => already in network as SAM group")
                    bShouldBeAdded = false
                    break
                end
            end
        
            local ewrs = iads:getEarlyWarningRadars()
            for i = 1, #ewrs do
                local ewr = ewrs[i]
                local dcsGroupEwr = veafSkynet.getDcsGroupFromSkynetElement(ewr)

                if (not dcsGroupEwr) then
-- LOGGING DISABLED WHEN COMPILING(sLog .. " => group not found for skynet ewr " .. veafSkynet.getStringSkynetElement(ewr))
                elseif (dcsGroupEwr:getName() == dcsGroupName) then
-- LOGGING DISABLED WHEN COMPILING(sLog .. " => already in network as EWR group")
                    bShouldBeAdded = false
                    break
                end
            end

            if (not bShouldBeAdded) then
                break -- if group is added no need to go check the other units
            end

            if(veafSkynet.iadsSamUnitsTypes[dcsUnitType]) then
                local samsite = iads:addSAMSite(groupName)
                if (samsite) then
                    addedSite = samsite
                    didSomething = true
                    alreadyAddedGroups[groupName] = true
-- LOGGING DISABLED WHEN COMPILING(sLog .. " => added as SAM")
                end
            end

            local ewrFlag = false
            if (addedSite == nil and veafSkynet.iadsEwrUnitsTypes[dcsUnitType]) then
                local ewr = iads:addEarlyWarningRadar(dcsUnitName)
                if (ewr) then 
                    addedSite = ewr
                    didSomething = true
                    ewrFlag = true
-- LOGGING DISABLED WHEN COMPILING(sLog .. " => added as EWR")
                end
            end

            --user requested configuration
            if addedSite and pointDefense and not(forceEwr) and not(ewrFlag) then
                if defended_name then
                    local text = string.format("Point Defense added to site : %s", string.format(defended_name))
                    veaf.loggers.get(veafSkynet.Id):info(text)
                    if not silent then trigger.action.outText(text,10) end
                    if iads:getSAMSiteByGroupName(defended_name) then
-- LOGGING DISABLED WHEN COMPILING(string.format("adding pointDefense to SAM -> OK"))
                        iads:getSAMSiteByGroupName(defended_name):addPointDefence(addedSite)
                    else
-- LOGGING DISABLED WHEN COMPILING(string.format("adding pointDefense to EWR -> OK"))
                        iads:getEarlyWarningRadars(defended_name):addPointDefence(addedSite)

                        --confirm that the addition of point defenses to the EWR which is gathered through it's group name but only the unit name is stored in the structure
                        --local site_info = iads:getEarlyWarningRadars(defended_name)
-- LOGGING DISABLED WHEN COMPILING(string.format("Recovered EWR name : %s", veaf.p(site_info[1].dcsName)))
                        --local pointDefenses = site_info[1].pointDefences
-- LOGGING DISABLED WHEN COMPILING(string.format("Recover pointDefense name : %s", veaf.p(pointDefenses[#pointDefenses].dcsName)))
                    end
                else
                    veaf.loggers.get(veafSkynet.Id):info("Could not find SAM site within range to add point defenses to")
                    if not silent then trigger.action.outText("Could not find SAM site within range to add point defenses to", 15) end
                end
            elseif forceEwr then
-- LOGGING DISABLED WHEN COMPILING(string.format("SAM/EWR is forced EWR"))

                if addedSite then
-- LOGGING DISABLED WHEN COMPILING("Unit Forced as EWR")
                    addedSite:setActAsEW(true)
                end
            end

            if (addedSite) then
                break -- if something has been added for this group no need to check the remaining units
            end
        end
    end

    if didSomething then
        if not(batchMode) and not(forceEwr) and not(pointDefense) then
            -- specific configurations, for each SAM type
-- LOGGING DISABLED WHEN COMPILING("Specific configuration applied")

            iads:getSAMSitesByNatoName('SA-10'):setActAsEW(false)
            iads:getSAMSitesByNatoName('SA-6'):setActAsEW(false)
            iads:getSAMSitesByNatoName('SA-5'):setActAsEW(false)
            iads:getSAMSitesByNatoName('Patriot'):setActAsEW(false)
            iads:getSAMSitesByNatoName('Hawk'):setActAsEW(false)
        end       

        -- reactivate (rebuild coverage) the IADS
-- LOGGING DISABLED WHEN COMPILING("reactivate (rebuild coverage) the IADS")
        veafSkynet.delayedActivate(networkName)

        --add the added site to the structure of the network it was added to
        veafSkynet.structure[networkName].groups[groupName] = { forceEwr = forceEwr, pointDefense = defended_name }
    end

    return didSomething
end

local function initializeIADS(networkName, coa, inRadio, debug)
    local iads = veafSkynet.getIadsOfCoalition(networkName, coa)
    if not(iads) then
-- LOGGING DISABLED WHEN COMPILING(string.format("IADS named %s for coalition %s does not exist", veaf.p(networkName), veaf.p(coa)))
        return false
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("initializeIADS %s",tostring(iads:getCoalitionString())))

    if debug then
-- LOGGING DISABLED WHEN COMPILING("adding debug information")
        local iadsDebug = iads:getDebugSettings()
        iadsDebug.IADSStatus = true
        iadsDebug.radarWentDark = true -- FG iadsDebug.samWentDark = true
        iadsDebug.contacts = true
        iadsDebug.radarWentLive = true
        iadsDebug.noWorkingCommmandCenter = false
        iadsDebug.ewRadarNoConnection = false
        iadsDebug.samNoConnection = false
        iadsDebug.jammerProbability = true
        iadsDebug.addedEWRadar = true
        iadsDebug.hasNoPower = false
        iadsDebug.harmDefence = true
        iadsDebug.samSiteStatusEnvOutput = true
        iadsDebug.earlyWarningRadarStatusEnvOutput = true
    end

    local alreadyAddedGroups = {}
    local dcsGroups = coalition.getGroups(coa)
    for _, dcsGroup in pairs(dcsGroups) do
        if veafSkynet.structure[networkName] then
            local groupName = dcsGroup:getName()
            if groupName then
                local structureData = veafSkynet.structure[networkName].groups[groupName]
                local forceEwr = false
                local pointDefense = false
                if structureData then
                    if structureData.forceEwr then
                        forceEwr = structureData.forceEwr
                    end
                    if structureData.pointDefense then
                        pointDefense = structureData.pointDefense
                    end
                end
                if veafSkynet.loadAllAtInit[tostring(coa)] or structureData then 
                    veafSkynet.addGroupToNetwork(networkName, dcsGroup, forceEwr, pointDefense, alreadyAddedGroups, true)
                end 
            end 
        end
    end

    if veafSkynet.loadAllAtInit[tostring(coa)] then
        veafSkynet.loadAllAtInit[tostring(coa)] = false
    end

-- LOGGING DISABLED WHEN COMPILING("Specific configuration applied")
    -- specific configurations, for each SAM type
    iads:getSAMSitesByNatoName('SA-10'):setActAsEW(false)
    iads:getSAMSitesByNatoName('SA-6'):setActAsEW(false)
    iads:getSAMSitesByNatoName('SA-5'):setActAsEW(false)
    iads:getSAMSitesByNatoName('Patriot'):setActAsEW(false)
    iads:getSAMSitesByNatoName('Hawk'):setActAsEW(false)

    veafSkynet.initializePointDefences(veafSkynet.getNetwork(networkName)) -- Management of point defences (Flogas) - initialization

    if inRadio then
        --activate the radio menu to toggle IADS Status output
        iads:addRadioMenu()
    end

    --activate (build coverage) the IADS
-- LOGGING DISABLED WHEN COMPILING("activate (build coverage) the IADS")
    veafSkynet.delayedActivate(networkName)
end

local function createNetwork(networkName, coa, loadUnits, UserAdd)
   
    local UserAdd = UserAdd or false
    local loadUnits = loadUnits or false

    local networkName = networkName
    if networkName then 
        networkName = tostring(networkName) 
    else
        veaf.loggers.get(veafSkynet.Id):error("networkName is of invalid format")
        return false
    end
    local coa = coa
    if coa then 
        coa = tonumber(coa) 
    else
        veaf.loggers.get(veafSkynet.Id):error("Coalition specified is of invalid format")
        return false
    end
-- LOGGING DISABLED WHEN COMPILING("networkName= %s", veaf.p(networkName))
-- LOGGING DISABLED WHEN COMPILING("CoalitionID= %s", veaf.p(coa))
-- LOGGING DISABLED WHEN COMPILING("loadUnits= %s", veaf.p(loadUnits))
-- LOGGING DISABLED WHEN COMPILING("UserAdd= %s", veaf.p(UserAdd))

    if networkName and coa then
        if (UserAdd and not veafSkynet.structure[networkName]) or not UserAdd then
            local debugFlag = veafSkynet.debugBlue
            local includeInRadio = veafSkynet.includeBlueInRadio

            if coa == coalition.side.RED then
                debugFlag = veafSkynet.debugRed
                includeInRadio = veafSkynet.includeRedInRadio
            end

-- LOGGING DISABLED WHEN COMPILING("creating network...")
            local iads = SkynetIADS:create(networkName)
            iads.coalitionID = coa
            if iads then
                if not veafSkynet.structure[networkName] then
-- LOGGING DISABLED WHEN COMPILING("network is new")
                    veafSkynet.structure[networkName] = {}
                    veafSkynet.structure[networkName].coalitionID = coa
                    veafSkynet.structure[networkName].includeInRadio = includeInRadio
                    veafSkynet.structure[networkName].debugFlag = debugFlag
                    veafSkynet.structure[networkName].groups = {}
                end
                veafSkynet.structure[networkName].iads = iads

                if veaf.loggers.get(veafSkynet.Id):wouldLogTrace() then
-- LOGGING DISABLED WHEN COMPILING("Stored structure for network named %s :", veaf.p(networkName))
                    for index,_ in pairs(veafSkynet.structure[networkName]) do
-- LOGGING DISABLED WHEN COMPILING("-> %s", veaf.p(index))
                    end
-- LOGGING DISABLED WHEN COMPILING("Stored IADS structure for network named %s :", veaf.p(networkName))
                    for index,_ in pairs(veafSkynet.structure[networkName].iads) do
-- LOGGING DISABLED WHEN COMPILING("-> %s", veaf.p(index))
                    end
-- LOGGING DISABLED WHEN COMPILING("CoalitionID for network named %s :", veaf.p(networkName))
-- LOGGING DISABLED WHEN COMPILING("-> %s", veaf.p(veafSkynet.structure[networkName].iads.coalitionID))
-- LOGGING DISABLED WHEN COMPILING("-> %s", veaf.p(veafSkynet.structure[networkName].iads:getCoalitionString()))
                end

                if loadUnits then
                    initializeIADS(networkName, coa, includeInRadio, debugFlag)
                end
                return true
            end
        else
            local text = string.format("The network name \"%s\" already exists", veaf.p(networkName))
            veaf.loggers.get(veafSkynet.Id):info(text)
        end
    end
    return false
end

-- reset an IADS network, useful when many additions are made at once to harmonize the structure
function veafSkynet.reinitializeNetwork(networkName)
    if not veafSkynet.initialized then 
        return false 
    end

    if networkName and veafSkynet.structure[networkName] then
        local networkStructure = veafSkynet.structure[networkName]
        if networkStructure.iads then
-- LOGGING DISABLED WHEN COMPILING("Stored structure for network named %s has IADS, deactivating", veaf.p(networkName))
            if networkStructure.includeInRadio then 
-- LOGGING DISABLED WHEN COMPILING("Removing radio menu...")
                networkStructure.iads:removeRadioMenu()
            end
            networkStructure.iads:deactivate()
        end
        createNetwork(networkName, networkStructure.coalitionID, true)
    end
end

-- reset an IADS networks, useful when many additions/destructions are made at once to harmonize the structures on the skynet side
function veafSkynet.reinitialize()
    if not veafSkynet.initialized then 
        return false 
    end

    for networkName,_ in pairs(veafSkynet.structure) do
        veafSkynet.reinitializeNetwork(networkName)
    end
end

function veafSkynet.initialize(includeRedInRadio, debugRed, includeBlueInRadio, debugBlue)
    veaf.loggers.get(veafSkynet.Id):info(string.format("initializing Skynet in %s seconds", tostring(veafSkynet.DelayForStartup)))
    mist.scheduleFunction(veafSkynet._initialize,{includeRedInRadio, debugRed, includeBlueInRadio, debugBlue}, timer.getTime()+veafSkynet.DelayForStartup)
end

function veafSkynet._initialize(includeRedInRadio, debugRed, includeBlueInRadio, debugBlue)
    veafSkynet.includeRedInRadio = includeRedInRadio or false
    veafSkynet.debugRed = debugRed or false
    veafSkynet.includeBlueInRadio = includeBlueInRadio or false
    veafSkynet.debugBlue = debugBlue or false

    veaf.loggers.get(veafSkynet.Id):info("Initializing module")
    
-- LOGGING DISABLED WHEN COMPILING(string.format("includeRedInRadio=%s",veaf.p(includeRedInRadio)))
-- LOGGING DISABLED WHEN COMPILING(string.format("debugRed=%s",veaf.p(debugRed)))
-- LOGGING DISABLED WHEN COMPILING(string.format("includeBlueInRadio=%s",veaf.p(includeBlueInRadio)))
-- LOGGING DISABLED WHEN COMPILING(string.format("debugBlue=%s",veaf.p(debugBlue)))
    
    -- prepare the list of units supported by Skynet IADS
    for _, groupData in pairs(SkynetIADS.database) do
        for _, listName in pairs({ "searchRadar", "trackingRadar", "launchers", "misc" }) do
            if groupData['type'] ~= 'ewr' then
                local list = groupData[listName]
                if list then
                    for unitType, _ in pairs(list) do
-- LOGGING DISABLED WHEN COMPILING(string.format("-> SAM"))
                        veafSkynet.iadsSamUnitsTypes[unitType] = true
                    end
                end
            end
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSkynet.iadsSamUnitsTypes=%s",veaf.p(veafSkynet.iadsSamUnitsTypes)))
    
    -- add EWR-capable units
    for _, unit in pairs(dcsUnits.DcsUnitsDatabase) do
        if unit then
-- LOGGING DISABLED WHEN COMPILING(string.format("testing unit %s",veaf.p(unit.type)))
            if unit.attribute then
-- LOGGING DISABLED WHEN COMPILING(string.format("unit.attribute = %s",veaf.p(unit.attribute)))
                if (unit.attribute["SAM SR"]) then
                    veafSkynet.iadsEwrUnitsTypes[unit.type] = true
-- LOGGING DISABLED WHEN COMPILING(string.format("-> EWR"))
                elseif (unit.attribute["EWR"]) then
                    veafSkynet.iadsEwrUnitsTypes[unit.type] = true
-- LOGGING DISABLED WHEN COMPILING(string.format("-> EWR"))
                elseif (unit.attribute["AWACS"]) then
                    veafSkynet.iadsEwrUnitsTypes[unit.type] = true
-- LOGGING DISABLED WHEN COMPILING(string.format("-> EWR"))
                end
            end
        end
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("veafSkynet.iadsEwrUnitsTypes=%s",veaf.p(veafSkynet.iadsEwrUnitsTypes)))
    

    veaf.loggers.get(veafSkynet.Id):info("Creating IADS for BLUE")
    createNetwork(veafSkynet.defaultIADS[tostring(coalition.side.BLUE)], coalition.side.BLUE, true)

    veaf.loggers.get(veafSkynet.Id):info("Creating IADS for RED")
    createNetwork(veafSkynet.defaultIADS[tostring(coalition.side.RED)], coalition.side.RED, true)

    veafSkynet.initialized = true

    if (veafSkynet.CommandCentersPreinitialize and #veafSkynet.CommandCentersPreinitialize > 0) then
        for _, commandCenter in pairs(veafSkynet.CommandCentersPreinitialize) do
            veafSkynet.addCommandCenterOfCoalition(commandCenter.CoalitionId, commandCenter.CommandCenterName) 
        end

        veafSkynet.CommandCentersPreinitialize = {}
    end
    
    if(veafSkynet.DynamicSpawn) then
        veafSkynet.monitorDynamicSpawn(true)
    end

    veaf.loggers.get(veafSkynet.Id):info(string.format("Skynet IADS has been initialized"))
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Command centers and network deactivation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
veafSkynet.CommandCentersPreinitialize = {} -- this is to memorize the command centers requested for a coalition, if the network of the coalition does not exist yet

function veafSkynet.addCommandCenter(veafSkynetNetwork, sCommandCenterName)
    local iads = veafSkynetNetwork.iads
    
    local dcsCommandCenterObject = StaticObject.getByName(sCommandCenterName)
    if (not dcsCommandCenterObject) then
        dcsCommandCenterObject = Unit.getByName(sCommandCenterName)
    end
    if (not dcsCommandCenterObject) then
        local dcsCommandCenterGroup = Group.getByName(sCommandCenterName)
        if (dcsCommandCenterGroup) then
            dcsCommandCenterObject = dcsCommandCenterGroup:getUnit(1)
        end
    end

    if (not dcsCommandCenterObject) then
        veaf.loggers.get(veafSkynet.Id):error("Requested command center not found: " .. sCommandCenterName)
        return
    end
    
    iads:addCommandCenter(dcsCommandCenterObject)
-- LOGGING DISABLED WHEN COMPILING("Command center unit added [" .. sCommandCenterName .. "]")
    iads:buildRadarCoverage() -- as command center is added after the network initialisation, coverage should be rebuilt
end

function veafSkynet.addCommandCenterOfCoalition(iCoalitionId, sCommandCenterName)
    if (veafSkynet.initialized) then
        local veafSkynetNetwork = veafSkynet.getNetwork(veafSkynet.defaultIADS[tostring(iCoalitionId)])

        if (veafSkynetNetwork == nil) then
            veaf.loggers.get(veafSkynet.Id):error("Veaf skynet network not found. Please ensure that veafSkynetIadsHelper has been initialized for coalition [" .. iCoalitionId .. "]")
            return    
        end
        
        veafSkynet.addCommandCenter(veafSkynetNetwork, sCommandCenterName)  
    else
-- LOGGING DISABLED WHEN COMPILING("Veaf skynet not initialized. Command center [" .. sCommandCenterName .. "] stored to be added later for [" .. iCoalitionId .. "]")
        table.insert (veafSkynet.CommandCentersPreinitialize, {CoalitionId = iCoalitionId, CommandCenterName = sCommandCenterName})
    end
end

function veafSkynet.destroyCommandCenters(veafSkynetNetwork, iExplosionStrength)
    iExplosionStrength = iExplosionStrength or 200 -- default explosion may not be enough to destroy certain bunkers
    local iads = veafSkynetNetwork.iads

    if (not iads:isCommandCenterUsable()) then
-- LOGGING DISABLED WHEN COMPILING("Network has no usable command center")
        return
    end

    local ccs = iads:getCommandCenters()

    for i = 1, #ccs do
        local cc = ccs[i]

        local dcsObject = cc.dcsRepresentation

        if (dcsObject:isExist()) then
            local category = getmetatable(dcsObject)
            if (category == Group) then
                for _, dcsUnit in pairs(dcsObject:getUnits()) do
-- LOGGING DISABLED WHEN COMPILING("Command center unit exploded: " .. dcsUnit:getName())
                    trigger.action.explosion(dcsUnit:getPosition().p, iExplosionStrength)
                end
            else
-- LOGGING DISABLED WHEN COMPILING("Command center unit exploded: " .. dcsObject:getName())
                trigger.action.explosion(dcsObject:getPosition().p, iExplosionStrength)
            end
        end
    end
end

function veafSkynet.destroyCommandCentersOfCoalition(iCoalitionId, iExplosionStrength)
    local veafSkynetNetwork = veafSkynet.getNetwork(veafSkynet.defaultIADS[tostring(iCoalitionId)])
    veafSkynet.destroyCommandCenters(veafSkynetNetwork, iExplosionStrength)
end

function veafSkynet.deactivateNetwork(veafSkynetNetwork, elementStates)
    local elementState = elementStates or veafSkynet.SkynetElementStates.Live
    local iads = veafSkynetNetwork.iads

    local sElementState = "live"
    if (elementState == veafSkynet.SkynetElementStates.Autonomous) then
        sElementState = "autonomous"
    elseif (elementState == veafSkynet.SkynetElementStates.Dark) then
        sElementState = "dark"
    end
-- LOGGING DISABLED WHEN COMPILING("Deactivating network " .. iads:getCoalitionString() .. ". Network elements will go " .. sElementState)

    local function setGroupState(skynetElement)
        skynetElement:finishHarmDefence()
        skynetElement:cleanUp()

        veafSkynet.removePointDefencesFromSkynetElement(skynetElement)
        if (elementState == veafSkynet.SkynetElementStates.Autonomous) then
            skynetElement:resetAutonomousState()
            skynetElement:goAutonomous()
        elseif (elementState == veafSkynet.SkynetElementStates.Dark) then
            skynetElement:goDark() -- goDark will not always turn the radar off - eg an EWR that is tracking targets will stay on
        else
            skynetElement:goLive() -- goLive will not always turn the radar on - eg a SAM site out of ammo will stay off
        end
    end

    veafSkynet.monitorDynamicSpawn(false)
    iads:deactivate()

    local ewrs = iads:getEarlyWarningRadars()
    for i = 1, #ewrs do
        local ewr = ewrs[i]
        setGroupState(ewr)
    end
    local sams = iads:getSAMSites()
    for i = 1, #sams do
        local sam = sams[i]
        setGroupState(sam)        
    end

    -- Copying the elements and emptying the Skynet lists before doing the state switch to ensure that Skynet does not keep controlling the elements after deactivation.
    -- Does not seem necessary after all, and keeping the lists in the Skynet network allows to reactivate it later if needed.
    -- Note that as it is, reactivating will not rebuild point defences, so it is more of a testing thing still.
    -- Code kept here just in case.
    --[[
    local skynetGroups = {}
    for i = 1, #iads.earlyWarningRadars do
        table.insert(skynetGroups, iads.earlyWarningRadars[i])
    end
    for i = 1, #iads.samSites do
        table.insert(skynetGroups, iads.samSites[i])
    end
    
    iads.earlyWarningRadars = {}
    iads.samSites = {}

    for i = 1, #skynetGroups do
        local skynetGroup = skynetGroups[i]
        setGroupState(skynetGroup)
    end
    ]]
end

function veafSkynet.deactivateNetworkOfCoalition(iCoalitionId, elementStates)
    local veafSkynetNetwork = veafSkynet.getNetwork(veafSkynet.defaultIADS[tostring(iCoalitionId)])
    veafSkynet.deactivateNetwork(veafSkynetNetwork, elementStates)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Load module
-------------------------------------------------------------------------------------------------------------------------------------------------------------
veaf.loggers.get(veafSkynet.Id):info(string.format("Loading version %s", veafSkynet.Version))

------------------ END script veafSkynetIadsHelper.lua  ------------------


------------------ START script veafSkynetIadsMonitor.lua  ------------------

------------------------------------------------------------------
-- VEAF Skynet-IADS contacts monitoring
-- By Flogas (2023)
--
-- Features:
-- ---------
-- * This module offers tools to trigger actions when a Skynet IADS detects a target
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafSkynetMonitor = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafSkynetMonitor.Id = "SKYNET_MONITOR"

--- Version.
veafSkynetMonitor.Version = "1.1.0"

-- trace level, specific to this module
--veafSkynetMonitor.LogLevel = "trace"
veaf.loggers.new(veafSkynetMonitor.Id, veafSkynetMonitor.LogLevel)

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  general tools
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
local function tableContains(tab, element)
    for _, e in pairs(tab) do
        if e == element then
            return true
        end
    end
    return false
end

local function tableRemove(tab, element)
    for i, e in pairs(tab) do
        if e == element then
            table.remove(tab, i)
            return
        end
    end
end

local function isNullOrEmpty(s)
    return (s == nil or (type(s) == "string" and s == ""))
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  VeafSkynetMonitorDescriptor class
---  Provides text descriptions to check an IADS content and structure
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
VeafSkynetMonitorDescriptor = {}

VeafSkynetMonitorDescriptor._staticSring = { Separator = " | ", Indentation = "  " }
VeafSkynetMonitorDescriptor.Option =
{
    -- general
    Ewr = 1,        -- display Early Warning Radars
    Sam = 2,        -- display SAM sites
    Targets = 3,    -- display targets tracked by the IADS
    -- Elements (Sam sites, EWRs)
    ElementTargets = 4, -- display targets tracked by each sam site
    ElementDetail = 5,  -- display static element informations (go live conditions, HARM detection percentages, etc)
    ElementStructure = 6,  -- display units attached to each element (point defences, child radars, etc)
    
    NestedPointDefences = 7 -- the point defences will be displayed as part of the element they defend
}
---------------------------------------------------------------------------------------------------
---  CTOR
function VeafSkynetMonitorDescriptor:Create(iads, options)
    options = options or
        { VeafSkynetMonitorDescriptor.Option.Ewr, VeafSkynetMonitorDescriptor.Option.Sam,
            VeafSkynetMonitorDescriptor.Option.Targets }

    local this =
    {
        Iads = iads,
        Options = options,
        FilterNatoName = {}
    }
    setmetatable(this, self)
    self.__index = self

    return this
end

function VeafSkynetMonitorDescriptor:GetIndentationString(iIndentation)
    iIndentation = iIndentation or 0
    return string.rep(self._staticSring.Indentation, iIndentation)
end

function VeafSkynetMonitorDescriptor:AppendString(s, sAppend)
    sAppend = sAppend or ""

    if (isNullOrEmpty(s)) then
        return sAppend
    elseif (isNullOrEmpty(sAppend)) then
        return s
    else
        return s .. self._staticSring.Separator .. sAppend
    end
end

function VeafSkynetMonitorDescriptor:NewLine(s, iIndentation)
    return s .. "\n" .. self:GetIndentationString(iIndentation)
end

function VeafSkynetMonitorDescriptor:AppendLine(s, sAppend, iIndentation)
    sAppend = sAppend or ""

    if (isNullOrEmpty(s)) then
        return self:GetIndentationString(iIndentation) .. sAppend
    elseif (isNullOrEmpty(sAppend)) then
        return s
    else
        return self:NewLine(s, iIndentation) .. sAppend
    end
end

function VeafSkynetMonitorDescriptor:GetStringSkynetElement(skynetElement)
    local s = veafSkynet.getStringSkynetElement(skynetElement)
    local dcsGroup = veafSkynet.getDcsGroupFromSkynetElement(skynetElement)
    if (dcsGroup == nil) then
        s = s .. " (dcs group not found)"
    else
        local bActive = false
        for _, dcsUnit in pairs(dcsGroup:getUnits()) do
            if (dcsUnit:isActive()) then
                bActive = true
                break
            end
        end

        if (not bActive) then
            s = s .. " (dcs group not active)" 
        end
    end

    return s
end

function VeafSkynetMonitorDescriptor:GetStringElementStructure(details, sDetailType)
    local s = ""

    if (details == nil or #details <= 0) then
        s = self:AppendString(s, "No " .. sDetailType)
    else
        local iMaximumRangeMeters = nil
        for i = 1, #details do
            local detail = details[i]
            if (detail.maximumRange and (iMaximumRangeMeters == nil or iMaximumRangeMeters < detail.maximumRange))then
                iMaximumRangeMeters = detail.maximumRange
            end
        end

        local sMaximumRange = ""
        if (iMaximumRangeMeters) then
            iMaximumRangeMeters = veaf.round(mist.utils.metersToNM(iMaximumRangeMeters), 1)
            sMaximumRange = " range:" .. iMaximumRangeMeters .. "nm"
        end
        s = self:AppendString(s, sDetailType .. ":" .. #details .. sMaximumRange)
    end

    return s
end

function VeafSkynetMonitorDescriptor:GetStringEwr(ewr, iIndentation)
    local s = ""

    -- Site description
    s = self:AppendLine(s, "EWR : " .. VeafSkynetMonitorDescriptor:GetStringSkynetElement(ewr), iIndentation)

    -- Site detail
    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.ElementDetail)) then
        s = self:NewLine(s, iIndentation + 1)

        if (ewr.harmDetectionChance) then
            s = self:AppendString(s, "HARM detection " .. ewr.harmDetectionChance .. "%")
        end
        if (ewr.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI) then
            s = self:AppendString(s, "Autonomous:DCS AI")
        elseif (ewr.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK) then
            s = self:AppendString(s, "Autonomous:dark")
        end
        if (ewr.goLiveRange == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE) then
            s = self:AppendString(s, "Go live:kill zone")
        elseif (ewr.goLiveRange == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_SEARCH_RANGE) then
            s = self:AppendString(s, "Go live:search range")
        end
    end

    -- Site structure
    local pointDefences = ewr:getPointDefences()
    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.ElementStructure)) then
        local radars = ewr:getRadars()

        s = self:NewLine(s, iIndentation + 1)
        s = self:AppendString(s, self:GetStringElementStructure(radars, "Radars"))
        s = self:AppendString(s, self:GetStringElementStructure(pointDefences, "Point defences"))
    end

    -- Site state
    s = self:NewLine(s, iIndentation + 1)
    if (ewr:isActive()) then
        s = self:AppendString(s, "Active")
    else
        s = self:AppendString(s, "Not active")
    end
    if (ewr:getAutonomousState()) then
        s = self:AppendString(s, "Autonomous")
    end
    if (ewr.harmSilenceID ~= nil) then
        s = self:AppendString(s, "Defending HARM (shutdown time " .. ewr.harmShutdownTime .. "s)")
    end

    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.ElementTargets)) then
        local targets = ewr:getDetectedTargets()
        if (targets == nil or #targets <= 0) then
            s = self:AppendLine(s, "No targets", iIndentation + 1)
        else
            s = self:AppendLine(s, "Targets :", iIndentation + 1)

            for i = 1, #targets do
                local target = targets[i]
                s = self:AppendLine(s, target:getName(), iIndentation + 2)
                s = self:AppendString(s, target:getTypeName())
            end
        end
    end

    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.NestedPointDefences)) then
        if (pointDefences and #pointDefences > 0) then
            s = self:AppendLine(s, "Point defences :", iIndentation + 1)

            for i = 1, #pointDefences do
                local pointDefence = pointDefences[i]
                s = self:AppendLine(s, self:GetStringSam(pointDefence, iIndentation + 2))
            end
        end
    end

    return s
end

function VeafSkynetMonitorDescriptor:GetStringSam(samSite, iIndentation)
    local s = ""

    -- Site description
    s = self:AppendLine(s, "Sam site : " .. VeafSkynetMonitorDescriptor:GetStringSkynetElement(samSite), iIndentation)
    if (samSite.isAPointDefence) then
        s = self:AppendString(s, "**Point defence**")
    end

    -- Site detail
    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.ElementDetail)) then
        s = self:NewLine(s, iIndentation + 1)

        if (samSite:getCanEngageAirWeapons()) then
            s = self:AppendString(s, "Can engage air weapons")
        end
        if (samSite:getCanEngageHARM()) then
            s = self:AppendString(s, "Can engage HARMs")
        end
        if (samSite.harmDetectionChance) then
            s = self:AppendString(s, "HARM detection " .. samSite.harmDetectionChance .. "%")
        end
        if (samSite.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DCS_AI) then
            s = self:AppendString(s, "Autonomous:DCS AI")
        elseif (samSite.autonomousBehaviour == SkynetIADSAbstractRadarElement.AUTONOMOUS_STATE_DARK) then
            s = self:AppendString(s, "Autonomous:dark")
        end
        if (samSite.goLiveRange == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_KILL_ZONE) then
            s = self:AppendString(s, "Go live:kill zone " .. samSite.firingRangePercent .. "% of max range")
        elseif (samSite.goLiveRange == SkynetIADSAbstractRadarElement.GO_LIVE_WHEN_IN_SEARCH_RANGE) then
            s = self:AppendString(s, "Go live:search range")
        end
    end

    local iRangeMeters = 0
    local launchers = samSite:getLaunchers()


    -- Site structure
    local pointDefences = samSite:getPointDefences()
    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.ElementStructure)) then
        local searchRadars = samSite:getSearchRadars()
        local trackRadars = samSite:getTrackingRadars()
        local launchers = samSite:getLaunchers()

        s = self:NewLine(s, iIndentation + 1)
        s = self:AppendString(s, self:GetStringElementStructure(searchRadars, "Search radars"))
        s = self:AppendString(s, self:GetStringElementStructure(trackRadars, "Track radars"))
        s = self:AppendString(s, self:GetStringElementStructure(launchers, "Launchers"))
        s = self:AppendString(s, self:GetStringElementStructure(pointDefences, "Point defences"))
    end

    -- Site state
    s = self:NewLine(s, iIndentation + 1)
    if (samSite:isActive()) then
        s = self:AppendString(s, "Active")
    else
        s = self:AppendString(s, "Not active")
    end
    if (samSite:getAutonomousState()) then
        s = self:AppendString(s, "Autonomous")
    end
    if (samSite:getActAsEW()) then
        s = self:AppendString(s, "Acting as EW")
    end
    if (not samSite:hasRemainingAmmo()) then
        s = self:AppendString(s, "No ammo")
    end
    if (samSite.harmSilenceID ~= nil) then
        s = self:AppendString(s, "Defending HARM (shutdown time " .. samSite.harmShutdownTime .. "s)")
    end

    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.ElementTargets)) then
        local targets = samSite:getDetectedTargets()
        if (targets == nil or #targets <= 0) then
            s = self:AppendLine(s, "No targets", iIndentation + 1)
        else
            s = self:AppendLine(s, "Targets :", iIndentation + 1)

            for i = 1, #targets do
                local target = targets[i]
                s = self:AppendLine(s, target:getName(), iIndentation + 2)
                s = self:AppendString(s, target:getTypeName())
            end
        end
    end

    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.NestedPointDefences)) then
        if (pointDefences and #pointDefences > 0) then
            s = self:AppendLine(s, "Point defences :", iIndentation + 1)

            for i = 1, #pointDefences do
                local pointDefence = pointDefences[i]
                s = self:AppendLine(s, self:GetStringSam(pointDefence, iIndentation + 2))
            end
        end
    end

    return s
end

function VeafSkynetMonitorDescriptor:GetStringDescription()
    local s = ""
    s = self:AppendLine("IADS : " .. self.Iads:getCoalitionString())
    local iIndentation = 1

    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.Ewr)) then
        local ewrs = self.Iads:getEarlyWarningRadars()
        if (#ewrs <= 0) then
            s = self:AppendLine(s, "No EWR", iIndentation)
        else
            s = self:AppendLine(s, "EWRs :", iIndentation)
            for i = 1, #ewrs do
                local ewr = ewrs[i]
                
                local bAddEwr = true
                
                if(self.FilterNatoName and #self.FilterNatoName > 0 and not tableContains(self.FilterNatoName, ewr:getNatoName())) then
                    bAddEwr = false
                end

                if (bAddEwr) then
                    s = self:AppendLine(s, self:GetStringEwr(ewr, iIndentation + 1))
                end
            end
        end
    end

    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.Sam)) then
        local samSites = self.Iads:getSAMSites()
        if (#samSites <= 0) then
            s = self:AppendLine(s, "No sam sites", iIndentation)
        else
            s = self:AppendLine(s, "Sam sites :", iIndentation)
            for i = 1, #samSites do
                local samSite = samSites[i]
                
                local bAddSamSite = true

                if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.NestedPointDefences) and samSite.isAPointDefence) then
                    bAddSamSite = false
                end
                
                if(self.FilterNatoName and #self.FilterNatoName > 0 and not tableContains(self.FilterNatoName, samSite:getNatoName())) then
                    bAddSamSite = false
                end

                if (bAddSamSite) then
                    s = self:AppendLine(s, self:GetStringSam(samSite, iIndentation + 1))
                end
            end
        end
    end

    if (tableContains(self.Options, VeafSkynetMonitorDescriptor.Option.Targets)) then

    end

    return s
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  VeafSkynetMonitorTask base class
---  Named monitoring task
---  A monitoring task represents a task to be executed by the monitoring thread
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
VeafSkynetMonitorTask = {}
---------------------------------------------------------------------------------------------------
---  CTOR
function VeafSkynetMonitorTask:Create(sName)
    local this =
    {
        Name = sName
    }
    setmetatable(this, self)
    self.__index = self

    return this
end

function VeafSkynetMonitorTask:ToString()
    return self.Name
end

function VeafSkynetMonitorTask:Execute()
-- LOGGING DISABLED WHEN COMPILING("Executing VeafSkynetMonitorTask")
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  VeafSkynetMonitorTaskContacts class
---  Named monitoring task to check for contacts detected and lost by an iads
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
VeafSkynetMonitorTaskContacts = {}
VeafSkynetMonitorTaskContacts = inheritsFrom(VeafSkynetMonitorTask) -- oop functions are from Skynet
---------------------------------------------------------------------------------------------------
---  CTOR
function VeafSkynetMonitorTaskContacts:Create(sName, iads, unitsToMonitor, onDetectedAction, onLostAction)
    local this = self:superClass():Create(sName, iads)
    setmetatable(this, self)
    self.__index = self

    this.Iads = iads
    this.UnitsToMonitor = unitsToMonitor
    this.TrackedUnits = {}
    this.OnDetectedAction = onDetectedAction
    this.OnLostAction = onLostAction

    return this
end

function VeafSkynetMonitorTaskContacts:ToString()
    return self.Name .. " | IADS [" .. self.Iads:getCoalitionString() .. "]"
end

function VeafSkynetMonitorTaskContacts:Execute()
-- LOGGING DISABLED WHEN COMPILING("Executing VeafSkynetMonitorTaskContacts")
    local currentContacts = self:GetIadsCurrentContacts()
-- LOGGING DISABLED WHEN COMPILING(self:ToString() .. " - currently tracking " .. #self.TrackedUnits .. " monitored units")

    for _, sContactName in pairs(currentContacts) do
        if (self:ContactIsToMonitor(sContactName) and not self:ContactIsTracked(sContactName)) then
-- LOGGING DISABLED WHEN COMPILING("Monitored contact detected: " .. sContactName)
            self:AddTrackedContact(sContactName)
            local err, errmsg = pcall(self.OnDetectedAction, sContactName)
        end
    end

    for _, sContactName in pairs(self.TrackedUnits) do
        if (not tableContains(currentContacts, sContactName)) then
-- LOGGING DISABLED WHEN COMPILING("Monitored contact lost: " .. sContactName)
            self:RemoveTrackedContact(sContactName)
            local err, errmsg = pcall(self.OnLostAction, sContactName)
        end
    end
end

function VeafSkynetMonitorTaskContacts:ContactIsToMonitor(sContactName)
    return self.UnitsToMonitor and tableContains(self.UnitsToMonitor, sContactName)
end

function VeafSkynetMonitorTaskContacts:GetIadsCurrentContacts()
    local currentContacts = {}
    for _, contact in pairs(self.Iads:getContacts()) do
        table.insert(currentContacts, contact:getName())
    end

    return currentContacts
end

function VeafSkynetMonitorTaskContacts:ContactIsTracked(sContactName)
    return tableContains(self.TrackedUnits, sContactName)
end

function VeafSkynetMonitorTaskContacts:AddTrackedContact(sContactName)
    table.insert(self.TrackedUnits, sContactName)
end

function VeafSkynetMonitorTaskContacts:RemoveTrackedContact(sContactName)
    tableRemove(self.TrackedUnits, sContactName)
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  VeafSkynetMonitorTaskDescriptor class
---  Named monitoring task to display informations regarding an iads
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
VeafSkynetMonitorTaskDescriptor = {}
VeafSkynetMonitorTaskDescriptor = inheritsFrom(VeafSkynetMonitorTask) -- oop functions are from Skynet

VeafSkynetMonitorTaskDescriptor.OutputDcsTextRecipients = { All = "*All*", Coalition = "*Coalition*" }
---------------------------------------------------------------------------------------------------
---  CTOR
function VeafSkynetMonitorTaskDescriptor:Create(sName, descriptors, sOutputLogLevel, outputDcsTextRecipients)
    local this = self:superClass():Create(sName)
    setmetatable(this, self)
    self.__index = self

    this.Descriptors = descriptors
    this.OutputLogLevel = sOutputLogLevel
    this.OutputDcsTextRecipients = outputDcsTextRecipients

    return this
end

function VeafSkynetMonitorTaskDescriptor:Output(sInformation)
    if (self.OutputLogLevel == "error") then
        veaf.loggers.get(veafSkynetMonitor.Id):error(sInformation)
    elseif (self.OutputLogLevel == "warning") then
        veaf.loggers.get(veafSkynetMonitor.Id):warning(sInformation)
    elseif (self.OutputLogLevel == "info") then
        veaf.loggers.get(veafSkynetMonitor.Id):info(sInformation)
    elseif (self.OutputLogLevel == "debug") then
-- LOGGING DISABLED WHEN COMPILING(sInformation)
    elseif (self.OutputLogLevel == "trace") then
-- LOGGING DISABLED WHEN COMPILING(sInformation)
    end

    if (self.OutputDcsTextRecipients and type(self.OutputDcsTextRecipients) == "table" and #self.OutputDcsTextRecipients > 0) then
        local iDuration = 4
        for _, recipient in pairs(self.OutputDcsTextRecipients) do
            if (recipient == VeafSkynetMonitorTaskDescriptor.OutputDcsTextRecipients.All) then
                trigger.action.outText(sInformation, iDuration)
            elseif (tableContains (coalition.side, recipient)) then
                trigger.action.outTextForCoalition(recipient, sInformation, iDuration)
            else
                local group = Group.getByName(recipient)
                if (group) then
                    trigger.action.outTextForGroup(group.id_, sInformation, iDuration)
                end
            end
        end
    end
end

function VeafSkynetMonitorTaskDescriptor:Execute()
-- LOGGING DISABLED WHEN COMPILING("Executing VeafSkynetMonitorTaskInformations")

    self:Output("----- Task Descriptor [ " .. self.Name .. " ] -----")
    for _, descriptor in pairs(self.Descriptors) do
        local sInformation = descriptor:GetStringDescription()
        self:Output(sInformation)
    end
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  Skynet monitoring thread management
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
veafSkynetMonitor._monitoringTasks = {}
veafSkynetMonitor._monitoringThreadId = nil
veafSkynetMonitor._interval = 5 -- iads.contactUpdateInterval

function veafSkynetMonitor.AddMonitoringTask(task)
    if (task == nil) then
        return
    end

    if (task.Name == nil or task.Name == "") then
        veaf.loggers.get(veafSkynetMonitor.Id):error("Monitoring task with incorrect name cannot be added")
        return
    end
    if (veafSkynetMonitor._monitoringTasks[task.Name] ~= nil) then
        veaf.loggers.get(veafSkynetMonitor.Id):error("Monitoring task with name [" .. task.Name .. "] already added")
        return
    end

-- LOGGING DISABLED WHEN COMPILING("Adding monitoring task: " .. task:ToString())
    veafSkynetMonitor._monitoringTasks[task.Name] = task

    if (veafSkynetMonitor._monitoringThreadId == nil) then
-- LOGGING DISABLED WHEN COMPILING("Starting mist thread")
        veafSkynetMonitor._monitoringThreadId = mist.scheduleFunction(veafSkynetMonitor.ExecuteMonitoringTasks, {},
            timer.getTime() + veafSkynetMonitor._interval, veafSkynetMonitor._interval, timer.getTime() + 3600)
    end
end

function veafSkynetMonitor.AddMonitoringTaskContacts(sTaskName, iads, unitsToMonitor, onDetectedAction, onLostAction)
    local task = VeafSkynetMonitorTaskContacts:Create(sTaskName, iads, unitsToMonitor, onDetectedAction, onLostAction)
    veafSkynetMonitor.AddMonitoringTask(task)
end

function veafSkynetMonitor.RemoveMonitoringTask(sTaskName)
    sTaskName = sTaskName or ""
    if (sTaskName ~= "" and veafSkynetMonitor._monitoringTasks[sTaskName]) then
-- LOGGING DISABLED WHEN COMPILING("Removing monitoring task: " .. veafSkynetMonitor._monitoringTasks[sTaskName]:ToString())
        veafSkynetMonitor._monitoringTasks[sTaskName] = nil
    end

    if (veaf.length(veafSkynetMonitor._monitoringTasks) <= 0) then
-- LOGGING DISABLED WHEN COMPILING("Nothing to monitor, stopping mist thread")
        mist.removeFunction(veafSkynetMonitor._monitoringThreadId)
    end
end

function veafSkynetMonitor.ExecuteMonitoringTasks()
    for _, task in pairs(veafSkynetMonitor._monitoringTasks) do
        task:Execute()
    end
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  Skynet iads informations
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------

veaf.loggers.get(veafSkynetMonitor.Id):info(string.format("Loading version %s", veafSkynetMonitor.Version))

------------------ END script veafSkynetIadsMonitor.lua  ------------------


------------------ START script veafTime.lua  ------------------

------------------------------------------------------------------
-- VEAF time and date tools
-- By Flogas (2024)
--
-- Features:
-- ---------
-- * Provides a suite of tools to manage date and time information relative to the DCS mission
-- standard lua datetime object is used when appropriate:
-- --> as returned by os.date("*t", 906000490)
-- --> dateTime = { year = 1998, month = 9, day = 16, yday = 259, wday = *unused*, hour = 23, min = 48, sec = 10, isdst = *unused* }
------------------------------------------------------------------
veafTime = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global module settings
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafTime.Id = "TIME"

--- Version.
veafTime.Version = "1.1.3"

-- trace level, specific to this module
--veafTime.LogLevel = "trace"
veaf.loggers.new(veafTime.Id, veafTime.LogLevel)

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Local constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------
local _iSecondsInMinute = 60
local _iSecondsInHour = 3600
local _iSecondsInDay = 86400

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Local tools
-------------------------------------------------------------------------------------------------------------------------------------------------------------
local function _isLeapYear(iYear)
    return iYear % 4 == 0 and (iYear % 100 ~= 0 or iYear % 400 == 0)
end

local function _getDaysInMonth(iMonth, iYear)
    local daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    if iMonth == 2 and _isLeapYear(iYear) then
        return 29
    end
    return daysInMonth[iMonth]
end

local function _getDayOfYear(iDay, iMonth, iYear)
    local iDayOfYear = iDay
    for i = 1, iMonth - 1 do
        iDayOfYear = iDayOfYear + _getDaysInMonth(i, iYear)
    end
    return iDayOfYear
end

local function _adjustDate(dateTime, iDaysOffset)
    local iYear = dateTime.year
    local iMonth = dateTime.month
    local iDay = dateTime.day
    local iDayOfYear = dateTime.yday
    
    -- Handle day changes
    iDay = iDay + iDaysOffset
    iDayOfYear = iDayOfYear + iDaysOffset
    
    -- Handle forward changes
    while true do
        local iDaysInMonth = _getDaysInMonth(iMonth, iYear)
        if (iDay <= iDaysInMonth) then
            break
        end
        iDay = iDay - iDaysInMonth
        iMonth = iMonth + 1
        if (iMonth > 12) then
            iMonth = 1
            iYear = iYear + 1
            -- Reset yday for new year
            iDayOfYear = iDay
        end
    end
    
    -- Handle backward changes
    while (iDay <= 0) do
        iMonth = iMonth - 1
        if (iMonth <= 0) then
            iMonth = 12
            iYear = iYear - 1
        end
        iDay = iDay + _getDaysInMonth(iMonth, iYear)
        -- Adjust yday for previous year
        if (iMonth == 12) then
            iDayOfYear = 365 + (_isLeapYear(iYear) and 1 or 0) + iDay - _getDaysInMonth(iMonth, iYear)
        end
    end
   
    return { year = iYear, month = iMonth, day = iDay, yday = iDayOfYear, hour = dateTime.hour, min = dateTime.min, sec = dateTime.sec }
end

local function _decimalToHoursMinutes(iDecimalHours)
    -- Handle negative hours (can happen with UTC times)
    if (iDecimalHours < 0) then
        iDecimalHours = iDecimalHours + 24
    end
    -- Handle hours >= 24
    iDecimalHours = iDecimalHours % 24

    local iHours = math.floor(iDecimalHours)
    local iMinutes = math.floor((iDecimalHours - iHours) * 60)
    return iHours, iMinutes
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- General date and time tools
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafTime.getMissionDateTime(iAbsTime)
    iAbsTime = iAbsTime or timer.getAbsTime()

    -- Calculate hours, minutes, and remaining seconds
    local iDay = env.mission.date.Day
    local iMonth = env.mission.date.Month
    local iYear = env.mission.date.Year
    local iHour = math.floor(iAbsTime / _iSecondsInHour)
    local iRemainingSeconds = iAbsTime % _iSecondsInHour
    local iMinute = math.floor(iRemainingSeconds / _iSecondsInMinute)
    local iSecond = iRemainingSeconds % _iSecondsInMinute

    -- Handle day rollover
    local iAdditionalDays = math.floor(iHour / 24)
    iHour = iHour % 24

    -- Add the additional days
    iDay = iDay + iAdditionalDays

    -- Handle month and year rollover
    while true do
        local iDaysInCurrentMonth = _getDaysInMonth(iMonth, iYear)

        if (iDay <= iDaysInCurrentMonth) then
            break
        end

        iDay = iDay - iDaysInCurrentMonth
        iMonth = iMonth + 1

        if iMonth > 12 then
            iMonth = 1
            iYear = iYear + 1
        end
    end

    local idayOfYear = _getDayOfYear(iDay, iMonth, iYear)

    return { year = iYear, month = iMonth, day = iDay, yday = idayOfYear, hour = iHour, min = iMinute, sec = iSecond }
end

function veafTime.getMissionAbsTime(dateTime)
    local iMissionStartDay = env.mission.date.Day
    local iMissionStartMonth = env.mission.date.Month
    local iMissionStartYear = env.mission.date.Year
    
    local iDay = dateTime.day
    local iMonth = dateTime.month
    local iYear = dateTime.year
    local iHour = dateTime.hour
    local iMinute = dateTime.min
    local iSecond = dateTime.sec

    local iTotalDays = 0
    
    -- If we're in the same year
    if iMissionStartYear == iYear then
        iTotalDays = _getDayOfYear(iDay, iMonth, iYear) - _getDayOfYear(iMissionStartDay, iMissionStartMonth, iMissionStartYear)
    else
        -- Days remaining in the first year
        iTotalDays = (_isLeapYear(iMissionStartYear) and 366 or 365) - _getDayOfYear(iMissionStartDay, iMissionStartMonth, iMissionStartYear)
        
        -- Add days for full years in between
        for iYear = iMissionStartYear + 1, iYear - 1 do
            iTotalDays = iTotalDays + (_isLeapYear(iYear) and 366 or 365)
        end
        
        -- Add days in the final year
        iTotalDays = iTotalDays + _getDayOfYear(iDay, iMonth, iYear)
    end
    
    -- Calculate total seconds
    local iAbsTime = iSecond +
                    (iMinute * _iSecondsInMinute) +
                    (iHour * _iSecondsInHour) +
                    (iTotalDays * _iSecondsInDay)
    
    return iAbsTime
end

function veafTime.absTimeToDateTime(iAbsTime)
    iAbsTime = iAbsTime or timer.getAbsTime()
    return veafTime.getMissionDateTime(iAbsTime)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Date and time string display tools
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafTime.toStringDate(dateTime)
    return string.format("%02d/%02d/%d", dateTime.day, dateTime.month, dateTime.year)
end

function veafTime.absTimeToStringDate(iAbsTime)
    local dateTime = veafTime.absTimeToDateTime(iAbsTime)
    return veafTime.toStringDate(dateTime)
end

function veafTime.toStringTime(dateTime, bWithSeconds)
    if (bWithSeconds == nil) then bWithSeconds = true end

-- LOGGING DISABLED WHEN COMPILING(veaf.p(dateTime))

    if (bWithSeconds) then
        return string.format("%02d:%02d:%02d", dateTime.hour, dateTime.min, dateTime.sec)
    else
        return string.format("%02d:%02d", dateTime.hour, dateTime.min)
    end
end

function veafTime.absTimeToStringTime(iAbsTime, bWithSeconds)
    local dateTime = veafTime.absTimeToDateTime(iAbsTime)
    return veafTime.toStringTime(dateTime, bWithSeconds)
end

function veafTime.toStringDateTime(dateTime, bWithSeconds)
    return string.format("%s %s", veafTime.toStringDate(dateTime), veafTime.toStringTime(dateTime, bWithSeconds))
end

function veafTime.absTimeToStringDateTime(iAbsTime)
    local dateTime = veafTime.absTimeToDateTime(iAbsTime)
    return veafTime.toStringDateTime(dateTime, bWithSeconds)
end

-- Helper function to determine season based on latitude and month
function veafTime.determineSeason(month, latitude)
    -- Determine if in Northern or Southern Hemisphere
    local isNorthernHemisphere = latitude == nil or latitude >= 0

    -- Season mapping adjusted for hemisphere
    if isNorthernHemisphere then
        -- Northern Hemisphere seasons
        if month >= 3 and month <= 5 then
            return "spring"
        elseif month >= 6 and month <= 8 then
            return "summer"
        elseif month >= 9 and month <= 11 then
            return "autumn"
        else
            return "winter"
        end
    else
        -- Southern Hemisphere seasons (reversed)
        if month >= 3 and month <= 5 then
            return "autumn"
        elseif month >= 6 and month <= 8 then
            return "winter"
        elseif month >= 9 and month <= 11 then
            return "spring"
        else
            return "summer"
        end
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Timezones
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafTime.getTimezone(vec3)
    local nTimezoneOffset = 0
    local sTheatre = env.mission.theatre

    if (vec3) then
        -- Try to approximate for the vec3 - each timezone is roughly 15 degrees wide
        local nLatitude, nLongitude, _ = coord.LOtoLL(vec3)
        nTimezoneOffset = math.floor((nLongitude + 7.5) / 15)
    elseif (sTheatre == veaf.theatreName.Caucasus) then
        nTimezoneOffset = 4
    elseif (sTheatre == veaf.theatreName.PersianGulf) then
        nTimezoneOffset = 4
    elseif (sTheatre == veaf.theatreName.Nevada) then
        nTimezoneOffset = -8 -- Nevada uses DST (UTC-7 march through october) but it is not modeled in DCS
    elseif (sTheatre == veaf.theatreName.Normandy) then
        nTimezoneOffset = 0
    elseif (sTheatre == veaf.theatreName.TheChannel) then
        nTimezoneOffset = 2
    elseif (sTheatre == veaf.theatreName.Syria) then
        nTimezoneOffset = 3
    elseif (sTheatre == veaf.theatreName.MarianaIslands) then
        nTimezoneOffset = 10
    elseif (sTheatre == veaf.theatreName.Falklands) then
        nTimezoneOffset = -3
    elseif (sTheatre == veaf.theatreName.Sinai) then
        nTimezoneOffset = 2
    elseif (sTheatre == veaf.theatreName.Kola) then
        nTimezoneOffset = 3
    elseif (sTheatre == veaf.theatreName.Afghanistan) then
        nTimezoneOffset = 4.5
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("%s - timezone=%f", env.mission.theatre, nTimezoneOffset))
    return nTimezoneOffset
end

function veafTime.toZulu(dateTime, nOffsetHours)
    nOffsetHours = nOffsetHours or veafTime.getTimezone()
    
    -- Create a new table to avoid modifying the original
    local dateTimeZulu = {}
    for k, v in pairs(dateTime) do
        dateTimeZulu[k] = v
    end
    
    -- Convert hours and handle day boundary changes
    local iTotalMinutes = dateTimeZulu.hour * 60 + dateTimeZulu.min - (nOffsetHours * 60)
    local iDays = math.floor(iTotalMinutes / (24 * 60))
    iTotalMinutes = iTotalMinutes % (24 * 60)
    
    -- Update hours and minutes
    dateTimeZulu.hour = math.floor(iTotalMinutes / 60)
    dateTimeZulu.min = iTotalMinutes % 60
    
    -- Handle date changes if needed
    if iDays ~= 0 then
        return _adjustDate(dateTimeZulu, iDays)
    end
    
    return dateTimeZulu
end

function veafTime.toLocal(utcDateTime, nOffsetHours)
    nOffsetHours = nOffsetHours or veafTime.getTimezone()
    return veafTime.toZulu(utcDateTime, -nOffsetHours)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Sun times
-------------------------------------------------------------------------------------------------------------------------------------------------------------
function veafTime.getSunTimesZulu(vec3, iAbsTime)
    local nLatitude, nLongitude, _ = coord.LOtoLL(vec3)
    local date = veafTime.absTimeToDateTime(iAbsTime)

    return veafSunTimes.getSunTimes(nLatitude, nLongitude, date, 0)
end

function veafTime.getSunTimesLocal(vec3, iAbsTime)
    local nLatitude, nLongitude, _ = coord.LOtoLL(vec3)
    local date = veafTime.absTimeToDateTime(iAbsTime)
    
    return veafSunTimes.getSunTimes(nLatitude, nLongitude, date, veafTime.getTimezone())
end

function veafTime.isAeronauticalNight(vec3, iAbsTime)
    local dateTime = veafTime.absTimeToDateTime(iAbsTime)
    local sunTimes = veafTime.getSunTimesLocal(vec3, iAbsTime)
    local sunriseTime = sunTimes.Sunrise
    local sunsetTime = sunTimes.Sunset
-- LOGGING DISABLED WHEN COMPILING(veaf.p(sunriseTime))
-- LOGGING DISABLED WHEN COMPILING(veaf.p(sunsetTime))

    local iCurrentSeconds = dateTime.hour * _iSecondsInHour + dateTime.min * _iSecondsInMinute + dateTime.sec
    local iSunriseSeconds = sunriseTime.hour * _iSecondsInHour + sunriseTime.min * _iSecondsInMinute + sunriseTime.sec - (30 * _iSecondsInMinute) -- sunrise - 30 min
    local iSunsetSeconds = sunsetTime.hour * _iSecondsInHour + sunsetTime.min * _iSecondsInMinute + sunsetTime.sec + (30 * _iSecondsInMinute) -- sunset + 30 min
    
-- LOGGING DISABLED WHEN COMPILING(string.format("iCurrentSeconds=%d  iSunriseSeconds=%d   iSunsetSeconds=%d", iCurrentSeconds, iSunriseSeconds, iSunsetSeconds))
    
    return iCurrentSeconds < iSunriseSeconds or iCurrentSeconds > iSunsetSeconds
end

local dawnAngle, duskAngle = 6, 6
local DR = math.pi / 180
local K1 = 15 * math.pi * 1.0027379 / 180

veafSunTimes = {}
veafSunTimes.__index = veafSunTimes

---------------------------------------------------------------------------------------------------
---  CTOR
function veafSunTimes:create()
    local this = {
        sunRiseSetTimes = {},
        moonRiseSetTimes = {},
        NoSunRise = false,
        NoSunSet = false,
        lat = 0,
        long = 0,
        timeOffset = 0,
        jDateSun = nil
    }

    setmetatable(this, veafSunTimes)
    return this
end

----------------------------------------------------------------------------------------------------
-- Main static method
----------------------------------------------------------------------------------------------------
function veafSunTimes.getSunTimes(nLatitude, nLongitude, date, nTimeZone)
    local sunTimes = veafSunTimes:create()
    sunTimes:compute(nLatitude, nLongitude, nTimeZone, date)

    local sunrise = sunTimes.sunRiseSetTimes[2]
    local sunset = sunTimes.sunRiseSetTimes[3]

    local iSunriseHour, iSunriseMinute = _decimalToHoursMinutes(sunrise)
    local iSunsetHour, iSunsetMinute = _decimalToHoursMinutes(sunset)

    return
    {
        Sunrise = { year = date.year, month = date.month, day = date.day, yday = date.yday, hour = iSunriseHour, min = iSunriseMinute, sec = 0 },
        Sunset =  { year = date.year, month = date.month, day = date.day, yday = date.yday, hour = iSunsetHour, min = iSunsetMinute, sec = 0 }
    }
end

----------------------------------------------------------------------------------------------------
-- Main compute method
----------------------------------------------------------------------------------------------------
function veafSunTimes:compute(nLatitude, nLongitude, nTimeZone, date)
    self.sunRiseSetTimes = {6, 6, 6, 12, 13, 18, 18, 18, 24}
    self.moonRiseSetTimes = {0, 23.9}
    self.NoSunRise = false
    self.NoSunSet = false
    self.lat = nLatitude
    self.long = nLongitude
    self.timeOffset = nTimeZone
    self.jDateSun = self:julian(date.year, date.month, date.day) - (nLongitude / (15 * 24))

    -- sun time calculations
    self:calcSunRiseSet()
    if self.NoSunRise or self.NoSunSet then
        -- adjust times to solar noon
        self.sunRiseSetTimes[2] = (self.sunRiseSetTimes[2] - 12)
        if self.NoSunRise then
            self.sunRiseSetTimes[3] = self.sunRiseSetTimes[2] + 0.0001
        else
            self.sunRiseSetTimes[3] = (self.sunRiseSetTimes[2] - 0.0001)
        end
        self.sunRiseSetTimes[1] = 0
        self.sunRiseSetTimes[4] = 0
    end

    -- debugging
    -- print("dawn = "       .. TimeString(sunRiseSetTimes[1],  nTimeLZero, nTimeStyle) .. " (" .. sunRiseSetTimes[1]  ..")")
    -- print("sunrise = "    .. TimeString(sunRiseSetTimes[2],  nTimeLZero, nTimeStyle) .. " (" .. sunRiseSetTimes[2]  ..")")
    -- print("sunset = "     .. TimeString(sunRiseSetTimes[3],  nTimeLZero, nTimeStyle) .. " (" .. sunRiseSetTimes[3]  ..")")
    -- print("twilight = "   .. TimeString(sunRiseSetTimes[4],  nTimeLZero, nTimeStyle) .. " (" .. sunRiseSetTimes[4]  ..")")
end

----------------------------------------------------------------------------------------------------
-- Following code kept as close as possible to https://gist.github.com/eDave56/6dfae1b62c4cf743afe0ad61e300f091
----------------------------------------------------------------------------------------------------

------------------------------------ [ sun time calculations ] -------------------------------------

function veafSunTimes:midDay(Ftime)
    local eqt = self:sunPosition(self.jDateSun + Ftime, 0)
    local noon = veafSunTimes.DMath.fixHour(12 - eqt)
    return noon
end -- function midDay

function veafSunTimes:sunAngleTime(angle, Ftime, direction)
    --
    -- time at which sun reaches a specific angle below horizon
    --
    local decl = self:sunPosition(self.jDateSun + Ftime, 1)
    local noon = self:midDay(Ftime)
    local t = (-veafSunTimes.DMath.Msin(angle) - veafSunTimes.DMath.Msin(decl) * veafSunTimes.DMath.Msin(self.lat)) / (veafSunTimes.DMath.Mcos(decl) * veafSunTimes.DMath.Mcos(self.lat))

    if t > 1 then
        -- the sun doesn't rise today
        self.NoSunRise = 1
        return noon
    elseif t < -1 then
        -- the sun doesn't set today
        self.NoSunSet = 1
        return noon
    end

    t = 1 / 15 * veafSunTimes.DMath.arccos(t)
    return noon + ((direction == "CCW") and -t or t)
end -- function sunAngleTime

function veafSunTimes:sunPosition(jd, Declination)
    --
    -- compute declination angle of sun
    --
    local D = jd - 2451545
    local g = veafSunTimes.DMath.fixAngle(357.529 + 0.98560028 * D)
    local q = veafSunTimes.DMath.fixAngle(280.459 + 0.98564736 * D)
    local L = veafSunTimes.DMath.fixAngle(q + 1.915 * veafSunTimes.DMath.Msin(g) + 0.020 * veafSunTimes.DMath.Msin(2 * g))
    local R = 1.00014 - 0.01671 * veafSunTimes.DMath.Mcos(g) - 0.00014 * veafSunTimes.DMath.Mcos(2 * g)
    local e = 23.439 - 0.00000036 * D
    local RA = veafSunTimes.DMath.arctan2(veafSunTimes.DMath.Mcos(e) * veafSunTimes.DMath.Msin(L), veafSunTimes.DMath.Mcos(L)) / 15
    local eqt = q / 15 - veafSunTimes.DMath.fixHour(RA)
    local decl = veafSunTimes.DMath.arcsin(veafSunTimes.DMath.Msin(e) * veafSunTimes.DMath.Msin(L))

    if Declination == 1 then
        return decl
    else
        return eqt
    end
end -- function sunPosition

function veafSunTimes:julian(year, month, day)
    --
    -- convert Gregorian date to Julian day
    --
    if (month <= 2) then
        year = year - 1
        month = month + 12
    end
    local A = math.floor(year / 100)
    local B = 2 - A + math.floor(A / 4)
    local JD = math.floor(365.25 * (year + 4716)) + math.floor(30.6001 * (month + 1)) + day + B - 1524.5
    return JD
end -- function julian

function veafSunTimes:setTimes(sunRiseSetTimes)
    local Ftimes = self:dayPortion(sunRiseSetTimes)
    local dawn = self:sunAngleTime(dawnAngle, Ftimes[2], "CCW")
    local sunrise = self:sunAngleTime(self:riseSetAngle(), Ftimes[3], "CCW")
    local sunset = self:sunAngleTime(self:riseSetAngle(), Ftimes[8], "CW")
    local dusk = self:sunAngleTime(duskAngle, Ftimes[7], "CW")
    return {dawn, sunrise, sunset, dusk}
end -- function setTimes

function veafSunTimes:calcSunRiseSet()
    self.sunRiseSetTimes = self:setTimes(self.sunRiseSetTimes)
    return self:adjustTimes(self.sunRiseSetTimes)
end      

function veafSunTimes:adjustTimes(sunRiseSetTimes)
    for i = 1, #sunRiseSetTimes do
        sunRiseSetTimes[i] = sunRiseSetTimes[i] + (self.timeOffset - self.long / 15)
    end
    sunRiseSetTimes = self:adjustHighLats(sunRiseSetTimes)
    return sunRiseSetTimes
end -- function adjustTimes

function veafSunTimes:riseSetAngle()
    --
    -- sun angle for sunset/sunrise
    --
    -- local angle = 0.0347 * math.sqrt( elv )
    local angle = 0.0347
    return 0.833 + angle
end -- function riseSetAngle

function veafSunTimes:adjustHighLats(sunRiseSetTimes)
    --
    -- adjust times for higher latitudes
    --
    local nightTime = self:timeDiff(sunRiseSetTimes[3], sunRiseSetTimes[2])
    sunRiseSetTimes[1] = self:refineHLtimes(sunRiseSetTimes[1], sunRiseSetTimes[2], (dawnAngle), nightTime, "CCW")
    return sunRiseSetTimes
end -- function adjustHighLats

function veafSunTimes:refineHLtimes(Ftime, base, angle, night, direction)
    --
    -- refine time for higher latitudes
    --
    local portion = night / 2
    local FtimeDiff = (direction == "CCW") and self:timeDiff(Ftime, base) or self:timeDiff(base, Ftime)
    if not ((Ftime * 2) > 2) or (FtimeDiff > portion) then
        Ftime = base + ((direction == "CCW") and -portion or portion)
    end
    return Ftime
end -- function refineHLtimes

function veafSunTimes:dayPortion(sunRiseSetTimes)
    --
    --  convert hours to day portions
    --
    for i = 1, #sunRiseSetTimes do
        sunRiseSetTimes[i] = sunRiseSetTimes[i] / 24
    end
    return sunRiseSetTimes
end -- function dayPortion

function veafSunTimes:timeDiff(time1, time2)
    --
    --  difference between two times
    --
    return veafSunTimes.DMath.fixHour(time2 - time1)
end -- function timeDiff

----------------------------------- [ moon time calaculations ] ------------------------------------
-- VEAF not retained
------------------------------------- [ other odds and sods ] --------------------------------------
-- VEAF not retained

---------------------------------------- [ math functions ] ----------------------------------------

function veafSunTimes.fix(a, b)
    a = a - b * (math.floor(a / b))
    return (a < 0) and a + b or a
end

function veafSunTimes.dtr(d)
    return (d * math.pi) / 180
end
function veafSunTimes.rtd(r)
    return (r * 180) / math.pi
end

veafSunTimes.DMath = {
    Msin = function(d)
        return math.sin(veafSunTimes.dtr(d))
    end,
    Mcos = function(d)
        return math.cos(veafSunTimes.dtr(d))
    end,
    Mtan = function(d)
        return math.tan(veafSunTimes.dtr(d))
    end,
    arcsin = function(d)
        return veafSunTimes.rtd(math.asin(d))
    end,
    arccos = function(d)
        return veafSunTimes.rtd(math.acos(d))
    end,
    arctan = function(d)
        return veafSunTimes.rtd(math.atan(d))
    end,
    arccot = function(x)
        return veafSunTimes.rtd(math.atan(1 / x))
    end,
    arctan2 = function(y, x)
        return veafSunTimes.rtd(math.atan2(y, x))
    end,
    fixAngle = function(a)
        return veafSunTimes.fix(a, 360)
    end,
    fixHour = function(a)
        return veafSunTimes.fix(a, 24)
    end
}

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
-------------------- TEST STUFF--------------------------------------------------------------------
--[[
local _TEST_LOCATIONS =
{
    ["BATUMI"] = {41.6167547, 41.6367455, 4},
    ["DAMASCUS"] = {34.802075, 38.996815, 3},
    ["SAIPAN"] = {15.0979, 145.6739, 10},
    ["LAS VEGAS"] = {36.176, -115.137, -8},
    ["DUBAI"] = {23.424076, 53.847818, 4}
}

local function _TEST_SUN_TIMES(sLocation, date, sExpectedRise, sExpectedSet)
    local nLatitude = _TEST_LOCATIONS[sLocation][1]
    local nLongitude = _TEST_LOCATIONS[sLocation][2]
    local iTimezoneOffset = veafTime.getTimezone()
    
    if (date == nil) then
        date = veafTime.absTimeToDateTime()
        sExpectedRise = "no data"
        sExpectedSet = "no data"
    else
        date = {day = date[1], month = date[2], year = date[3]}
    end

    local sunTimes = veafSunTimes.getSunTimes(nLatitude, nLongitude, date, iTimezoneOffset)

    local sunrise = sunTimes.Sunrise
    local sunset = sunTimes.Sunset
-- LOGGING DISABLED WHEN COMPILING("%s ; %02d/%02d/%d : sunrise=%02d:%02d, sunset=%02d:%02d ( expected rise=%s, set=%s )", sLocation, date.day, date.month, date.year, sunrise.hour, sunrise.min, sunset.hour, sunset.min, sExpectedRise, sExpectedSet)
end

-- LOGGING DISABLED WHEN COMPILING("%s >> TESTS TESTS TESTS", veafTime.Id)
local sTheatre = string.lower(env.mission.theatre)
local sLoc
if (sTheatre == "caucasus") then
    sLoc = "BATUMI"
    _TEST_SUN_TIMES(sLoc, 1, 1, 2024, "08:40", "17:53")
    _TEST_SUN_TIMES(sLoc, 1, 4, 2024, "06:54", "19:39")
    _TEST_SUN_TIMES(sLoc, 1, 7, 2024, "05:43", "20:51")
    _TEST_SUN_TIMES(sLoc, 1, 10, 2024, "07:11", "18:54")
elseif (sTheatre == "persiangulf") then
    sLoc = "DUBAI"
    _TEST_SUN_TIMES(sLoc, {1, 1, 2024}, "07:06", "17:49")
    _TEST_SUN_TIMES(sLoc, {1, 4, 2024}, "06:16", "18:40")
    _TEST_SUN_TIMES(sLoc, {1, 7, 2024}, "05:42", "19:15")
    _TEST_SUN_TIMES(sLoc, {1, 10, 2024}, "06:16", "18:11")    
elseif (sTheatre == "nevada") then
    sLoc = "LAS VEGAS"
    _TEST_SUN_TIMES(sLoc)
    _TEST_SUN_TIMES(sLoc, {1, 1, 2024}, "06:50", "16:37")
    _TEST_SUN_TIMES(sLoc, {1, 4, 2024}, "06:24 (05:24 w/o DST)", "19:04 (18:04 w/o DST)")
    _TEST_SUN_TIMES(sLoc, {1, 7, 2024}, "05:25 (04:25 w/o DST)", "20:14 (19:14 w/o DST)")
    _TEST_SUN_TIMES(sLoc, {1, 10, 2024}, "06:43 (05:43 w/o DST)", "18:26 (17:25 w/o DST)")    
elseif (sTheatre == "normandy") then
    sLoc = ""
elseif (sTheatre == "thechannel") then
    sLoc = ""
elseif (sTheatre == "syria") then
    sLoc = "DAMASCUS"
    _TEST_SUN_TIMES(sLoc, {1, 1, 2024}, "07:38", "17:37")
    _TEST_SUN_TIMES(sLoc, {1, 4, 2024}, "06:22", "18:56")
    _TEST_SUN_TIMES(sLoc, {1, 7, 2024}, "05:28", "19:46")
    _TEST_SUN_TIMES(sLoc, {1, 10, 2024}, "06:29", "18:18")    
elseif (sTheatre == "marianaislands") then
    sLoc = "SAIPAN"
    _TEST_SUN_TIMES(sLoc, {1, 1, 2024}, "06:42", "17:57")
    _TEST_SUN_TIMES(sLoc, {1, 4, 2024}, "06:12", "18:29")
    _TEST_SUN_TIMES(sLoc, {1, 7, 2024}, "05:51", "18:51")
    _TEST_SUN_TIMES(sLoc, {1, 10, 2024}, "06:07", "18:06")    
elseif (sTheatre == "falklands") then
    sLoc = ""
elseif (sTheatre == "sinaiMap") then
    sLoc = ""
elseif (sTheatre == "kola") then
    sLoc = ""
elseif (sTheatre == "afghanistan") then
    sLoc = ""
end
]]

------------------ END script veafTime.lua  ------------------


------------------ START script veafTransportMission.lua  ------------------

------------------------------------------------------------------
-- VEAF transport mission command and functions for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Listen to marker change events and creates a transport training mission, with optional parameters
-- * Possibilities :
-- *    - create a zone with cargo to pick up, another with friendly troops awaiting their cargo, and optionaly enemy units on the way
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafTransportMission = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafTransportMission.Id = "TRANSPORTMISSION"

--- Version.
veafTransportMission.Version = "1.7.0"

-- trace level, specific to this module
--veafTransportMission.LogLevel = "trace"

veaf.loggers.new(veafTransportMission.Id, veafTransportMission.LogLevel)

--- Key phrase to look for in the mark text which triggers the command.
veafTransportMission.Keyphrase = "_transport"

veafTransportMission.CargoTypes = {"ammo_cargo", "barrels_cargo", "m117_cargo", "oiltank_cargo", "uh1h_cargo" } --, "container_cargo", "fueltank_cargo" }

--- Number of seconds between each check of the friendly group ADF loop function
veafTransportMission.SecondsBetweenAdfLoops = 30

--- Number of seconds between each check of the friendly group watchdog function
veafTransportMission.SecondsBetweenWatchdogChecks = 15

--- Number of seconds between each smoke request on the target
veafTransportMission.SecondsBetweenSmokeRequests = 180

--- Number of seconds between each flare request on the target
veafTransportMission.SecondsBetweenFlareRequests = 120

--- Name of the friendly group that waits for the cargo
veafTransportMission.BlueGroupName = "Transport - Allied Group"

--- Name of the cargo units
veafTransportMission.BlueCargoName = "Cargo - Cargo unit"

--- Name of the enemy group that defends the way to the friendlies
veafTransportMission.RedDefenseGroupName = "Cargo - Enemy Air Defense Group"

--- Name of the enemy group that blocades the friendlies
veafTransportMission.RedBlocadeGroupName = "Cargo - Enemy Blocade Group"

veafTransportMission.RadioMenuName = "TRANSPORT MISSION"

veafTransportMission.AdfRadioSound = "l10n/DEFAULT/beacon.ogg"

veafTransportMission.AdfFrequency = 550000 -- in hz

veafTransportMission.AdfPower = 1000 -- in Watt

veafTransportMission.DoRadioTransmission = false -- set to true when radio transmissions will work

-- minimum authorized route distance ; missions shorter than this will not be authorized
veafTransportMission.MinimumRouteDistance = 15000 -- 15 km

-- size of the safe zone (no enemy group before this distance, in % of the total distance)
veafTransportMission.SafeZoneDistance = 0.6 -- 60%

-- size of the sqfe zone near drop zone (no enemy group after this distance from the drop zone)
veafTransportMission.DropZoneSafeZoneDistance = 5000 -- 5 km

-- an enemy group every xxx meters of the way (randomized)
veafTransportMission.EnemyDefenseDistanceStep = 3000 

-- enemies groups generated along the way are offset to xxx meters max (left or right, randomized)
veafTransportMission.LeftOrRightMaxOffset = 1500

-- enemies groups generated along the way are offset to xxx meters min (left or right, randomized)
veafTransportMission.LeftOrRightMinOffset = 500

-- enemies groups generated far from the way are offset to xxx meters max (left or right, randomized)
veafTransportMission.LeftOrRightMaxFarOffset = 7000

-- enemies groups generated far from the way are offset to xxx meters min (left or right, randomized)
veafTransportMission.LeftOrRightMinFarOffset = 3000

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- Friendly group watchdog function id
veafTransportMission.friendlyGroupAliveCheckTaskID = 'none'

-- Friendly group ADF transmission loop function id
veafTransportMission.friendlyGroupAdfLoopTaskID = 'none'

--- Radio menus paths
veafTransportMission.targetMarkersPath = nil
veafTransportMission.targetInfoPath = nil
veafTransportMission.rootPath = nil

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafTransportMission.onEventMarkChange(eventPos, event)
    -- Check if marker has a text and the veafTransportMission.keyphrase keyphrase.
    if event.text ~= nil and event.text:lower():find(veafTransportMission.Keyphrase) then

        -- Analyse the mark point text and extract the keywords.
        local options = veafTransportMission.markTextAnalysis(event.text)

        if options then
            -- Check options commands
            if options.transportmission then
                -- check security
                if not veafSecurity.checkSecurity_L1(options.password) then return end
                -- create the mission
                veafTransportMission.generateTransportMission(eventPos, options.size, options.defense, options.blocade, options.from)
            end
        else
            -- None of the keywords matched.
            return
        end

        -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
        trigger.action.removeMark(event.idx)
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Analyse the mark text and extract keywords.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Extract keywords from mark text.
function veafTransportMission.markTextAnalysis(text)

    -- Option parameters extracted from the mark text.
    local switch = {}
    switch.transportmission = false

    -- size ; number of cargo to be transported
    switch.size = 1

    -- defense [1-5] : air defense cover on the way (1 = light, 5 = heavy)
    switch.defense = 0

    -- blocade [1-5] : enemy blocade around the drop zone (1 = light, 5 = heavy)
    switch.blocade = 0

    -- start position, named point
    switch.from = nil

    -- password
    switch.password = nil

    -- Check for correct keywords.
    if text:lower():find(veafTransportMission.Keyphrase) then
        switch.transportmission = true
    else
        return nil
    end

    -- keywords are split by ","
    local keywords = veaf.split(text, ",")

    for _, keyphrase in pairs(keywords) do
        -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
        local str = veaf.breakString(veaf.trim(keyphrase), " ")
        local key = str[1]
        local val = str[2]

        if key:lower() == "password" then
            -- Unlock the command
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword password", val))
            switch.password = val
        end

        if switch.transportmission and key:lower() == "size" then
            -- Set size.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword size = %d", val))
            local nVal = tonumber(val)
            if nVal <= 5 and nVal >= 1 then
                switch.size = nVal
            end
        end

        if switch.transportmission and key:lower() == "defense" then
            -- Set defense.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword defense = %d", val))
            local nVal = tonumber(val)
            if nVal <= 5 and nVal >= 0 then
                switch.defense = nVal
            end
        end

        if switch.transportmission and key:lower() == "blocade" then
            -- Set blocade.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword blocade = %d", val))
            local nVal = tonumber(val)
            if nVal <= 5 and nVal >= 0 then
                switch.blocade = nVal
            end
        end

        if switch.transportmission and key:lower() == "from" then
            -- Set armor.
-- LOGGING DISABLED WHEN COMPILING(string.format("Keyword from = %s", val))
            switch.from = val
        end
    end

    return switch
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- CAS target group generation and management
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafTransportMission.doRadioTransmission(groupName)
-- LOGGING DISABLED WHEN COMPILING("doRadioTransmission("..groupName..")")
    local group = Group.getByName(groupName)
    if group then
-- LOGGING DISABLED WHEN COMPILING("Group is transmitting")
        local averageGroupPosition = veaf.getAveragePosition(groupName)
-- LOGGING DISABLED WHEN COMPILING("averageGroupPosition=" .. veaf.vecToString(averageGroupPosition))
        trigger.action.radioTransmission(veafTransportMission.AdfRadioSound, averageGroupPosition, 0, false, veafTransportMission.AdfFrequency, veafTransportMission.AdfPower)
    end
    
    veafTransportMission.friendlyGroupAdfLoopTaskID = mist.scheduleFunction(veafTransportMission.doRadioTransmission, { groupName }, timer.getTime() + veafTransportMission.SecondsBetweenAdfLoops)
end

function veafTransportMission.generateFriendlyGroup(groupPosition)
    veafSpawn.doSpawnGroup(groupPosition, 0, "US infgroup", "USA", 0, 0, 0, veafTransportMission.BlueGroupName, true, false, true, true)

    if veafTransportMission.DoRadioTransmission then
        veafTransportMission.doRadioTransmission(veafTransportMission.BlueGroupName)
    end
end

--- Generates an enemy defense group on the way to the drop zone
--- defenseLevel = 1 : 3-7 soldiers, GAZ-3308 transport
--- defenseLevel = 2 : 3-7 soldiers, BTR-80 APC
--- defenseLevel = 3 : 3-7 soldiers, chance of BMP-1 IFV, chance of Igla manpad
--- defenseLevel = 4 : 3-7 soldiers, big chance of BMP-1 IFV, big chance of Igla-S manpad, chance of ZU-23 on a truck
--- defenseLevel = 5 : 3-7 soldiers, BMP-1 IFV, big chance of Igla-S manpad, chance of ZSU-23-4 Shilka
function veafTransportMission.generateEnemyDefenseGroup(groupPosition, groupName, defenseLevel)
    local groupDefinition = {
            disposition = { h = 6, w = 6},
            units = {},
            description = groupName,
            groupName = groupName,
        }

    -- generate an infantry group
    local groupCount = math.random(3, 7)
    for _ = 1, groupCount do
        local rand = math.random(3)
        local unitType = nil
        if rand == 1 then
            unitType = 'Soldier RPG'
        elseif rand == 2 then
            unitType = 'Soldier AK'
        else
            unitType = 'Infantry AK'
        end
        table.insert(groupDefinition.units, { unitType })
    end

    -- add a transport vehicle or an APC/IFV
    if defenseLevel > 4 or (defenseLevel > 3 and math.random(100) > 33) or (defenseLevel > 2 and math.random(100) > 66) then
        table.insert(groupDefinition.units, { "BMP-1", cell=11, random = true })
    elseif defenseLevel > 1 then
        table.insert(groupDefinition.units, { "BTR-80", cell=11, random = true })
    else
        table.insert(groupDefinition.units, { "GAZ-3308", cell=11, random = true })
    end

    -- add manpads if needed
    if defenseLevel > 3 and math.random(100) > 33 then
        -- for defenseLevel = 4-5, spawn a modern Igla-S team
        table.insert(groupDefinition.units, { "SA-18 Igla-S comm", random = true })
        table.insert(groupDefinition.units, { "SA-18 Igla-S manpad", random = true })
    elseif defenseLevel > 2 and math.random(100) > 66 then
        -- for defenseLevel = 3, spawn an older Igla team
        table.insert(groupDefinition.units, { "SA-18 Igla comm", random = true })
        table.insert(groupDefinition.units, { "SA-18 Igla manpad", random = true })
    else
        -- for defenseLevel = 0, don't spawn any manpad
    end

    -- add an air defenseLevel vehicle
    if defenseLevel > 4 and math.random(100) > 66 then
        -- defenseLevel = 3-5 : add a Shilka
        table.insert(groupDefinition.units, { "ZSU-23-4 Shilka", cell = 3, random = true })
    elseif defenseLevel > 3 and math.random(100) > 66 then
        -- defenseLevel = 1 : add a ZU23 on a truck
        table.insert(groupDefinition.units, { "Ural-375 ZU-23", cell = 3, random = true })
    end

    groupDefinition = veafUnits.processGroup(groupDefinition)
    veafSpawn.doSpawnGroup(groupPosition, 0, groupDefinition, "RUSSIA", 0, math.random(359), math.random(3,6), groupName, true, false, true, true)
end


--- Generates a transport mission
function veafTransportMission.generateTransportMission(targetSpot, size, defense, blocade, from)
-- LOGGING DISABLED WHEN COMPILING("generateTransportMission(size = %s, defense=%s, blocade=%d, from=%s)", veaf.p(size), veaf.p(defense), veaf.p(blocade), veaf.p(from))
-- LOGGING DISABLED WHEN COMPILING("generateTransportMission: targetSpot ", veaf.p(targetSpot))

    if veafTransportMission.friendlyGroupAliveCheckTaskID ~= 'none' then
        trigger.action.outText("A transport mission already exists !", 5)
        return
    end

    if not from then
        trigger.action.outText("The \"from\" keyword is mandatory !", 5)
        return
    end

    local startPoint = veafNamedPoints.getPoint(from)
    if not(startPoint) then
        trigger.action.outText("A point named "..from.." cannot be found !", 5)
        return
    end
    
    local friendlyUnits = {}
    local routeDistance = 0

    -- generate a friendly group around the target target spot
    local groupPosition = veaf.findPointInZone(targetSpot, 100, false)
    if groupPosition ~= nil then
-- LOGGING DISABLED WHEN COMPILING("groupPosition=" .. veaf.vecToString(groupPosition))
        groupPosition = { x = groupPosition.x, z = groupPosition.y, y = 0 }
        groupPosition = veaf.placePointOnLand(groupPosition)
-- LOGGING DISABLED WHEN COMPILING("groupPosition on land=" .. veaf.vecToString(groupPosition))

        -- compute player route to friendly group
        local vecAB = {x = groupPosition.x +- startPoint.x, y = 0, z = groupPosition.z - startPoint.z}
        routeDistance = mist.vec.mag(vecAB)
-- LOGGING DISABLED WHEN COMPILING("routeDistance="..routeDistance)
        if routeDistance < veafTransportMission.MinimumRouteDistance then
            trigger.action.outText("This drop zone is too close ; you have to place it at least " .. veafTransportMission.MinimumRouteDistance / 1000 .. " km away from point "..from.." !", 5)
            return
        end

        veafTransportMission.generateFriendlyGroup(groupPosition)
    else
        veaf.loggers.get(veafTransportMission.Id):info("cannot find a suitable position for friendly group")
        return
    end

    -- generate cargo to be picked up near the player helo
-- LOGGING DISABLED WHEN COMPILING("Generating cargo")
    local startPosition = veaf.placePointOnLand(startPoint)
-- LOGGING DISABLED WHEN COMPILING("startPosition=" .. veaf.vecToString(startPosition))
    for i = 1, size do
        local spawnSpot = { x = startPosition.x + 50, z = startPosition.z + i * 10, y = startPosition.y }
-- LOGGING DISABLED WHEN COMPILING("spawnSpot=" .. veaf.vecToString(spawnSpot))
        local cargoType = veafTransportMission.CargoTypes[math.random(#veafTransportMission.CargoTypes)]
        local cargoName = veafTransportMission.BlueCargoName .. " #" .. i
        veafSpawn.doSpawnCargo(spawnSpot, 0, cargoType, "USA")
    end
-- LOGGING DISABLED WHEN COMPILING("Done generating cargo")

    -- generate enemy air defense on the way
    if defense > 0 then
-- LOGGING DISABLED WHEN COMPILING("Generating air defense")

         -- place groups on the way
         local startingDistance = routeDistance * veafTransportMission.SafeZoneDistance -- enemy presence start after the safe zone
         local defendedDistance = routeDistance - veafTransportMission.DropZoneSafeZoneDistance - startingDistance
         local distanceStep = veafTransportMission.EnemyDefenseDistanceStep
         local nbSteps = math.floor(defendedDistance / distanceStep) 
         local groupNum = 1
         for stepNum = 1, nbSteps do
            local distanceFromStartingPoint = startingDistance + stepNum * distanceStep + math.random(distanceStep/5, 4*distanceStep/5)
-- LOGGING DISABLED WHEN COMPILING("distanceFromStartingPoint="..distanceFromStartingPoint)

            -- place an enemy defense group along the way
            local offset = math.random(veafTransportMission.LeftOrRightMinOffset, veafTransportMission.LeftOrRightMaxOffset)
            if math.random(100) < 51 then 
               offset = -offset 
            end
-- LOGGING DISABLED WHEN COMPILING("offset="..offset)
            local spawnPoint = veaf.computeCoordinatesOffsetFromRoute(startPoint, groupPosition, distanceFromStartingPoint, offset)
            local groupName = veafTransportMission.RedDefenseGroupName .. " #"  .. groupNum
            veafTransportMission.generateEnemyDefenseGroup(spawnPoint, groupName, defense)
            groupNum = groupNum + 1

            -- place a random number of defense groups further away
            local nbFarGroups = math.random(0,1)
            if defense > 4 then 
                nbFarGroups = math.random(1,3)
            end
            for _ = 1, nbFarGroups do
                local offset = math.random(veafTransportMission.LeftOrRightMinFarOffset, veafTransportMission.LeftOrRightMaxFarOffset)
                if math.random(100) < 51 then 
                   offset = -offset 
                end
-- LOGGING DISABLED WHEN COMPILING("offset="..offset)
                local spawnPoint = veaf.computeCoordinatesOffsetFromRoute(startPoint, groupPosition, distanceFromStartingPoint, offset)
                local groupName = veafTransportMission.RedDefenseGroupName .. " #"  .. groupNum
                veafTransportMission.generateEnemyDefenseGroup(spawnPoint, groupName, defense)
                groupNum = groupNum + 1
            end

         end

-- LOGGING DISABLED WHEN COMPILING("Done generating air defense")
    end

    -- generate enemy blocade forces
    if blocade > 0 then
-- LOGGING DISABLED WHEN COMPILING("Generating blocade")
        -- TODO
-- LOGGING DISABLED WHEN COMPILING("Done generating blocade")
    end

    -- add radio menu for drop zone information (by player group)
    veafRadio.addCommandToSubmenu('Drop zone information', veafTransportMission.rootPath, veafTransportMission.reportTargetInformation, nil, veafRadio.USAGE_ForGroup)

    -- add radio menus for commands
    veafRadio.addSecuredCommandToSubmenu('Skip current objective', veafTransportMission.rootPath, veafTransportMission.skip)
    veafTransportMission.targetMarkersPath = veafRadio.addSubMenu("Drop zone markers", veafTransportMission.rootPath)
    veafRadio.addCommandToSubmenu('Request smoke on drop zone', veafTransportMission.targetMarkersPath, veafTransportMission.smokeTarget)
    veafRadio.addCommandToSubmenu('Request illumination flare over drop zone', veafTransportMission.targetMarkersPath, veafTransportMission.flareTarget)

    local message = "See F10 radio menu for details\n" -- TODO
    trigger.action.outText(message,5)
    
    veafRadio.refreshRadioMenu()
    
    -- start checking for targets destruction
    veafTransportMission.friendlyGroupWatchdog()
end

--- Checks if the friendly group is still alive, and if not announces the failure of the transport mission
function veafTransportMission.friendlyGroupWatchdog() 
    local nbVehicles, nbInfantry = veafUnits.countInfantryAndVehicles(veafTransportMission.BlueGroupName)
    if nbVehicles + nbInfantry > 0 then
-- LOGGING DISABLED WHEN COMPILING("Group is still alive with "..nbVehicles.." vehicles and "..nbInfantry.." soldiers")
        veafTransportMission.friendlyGroupAliveCheckTaskID = mist.scheduleFunction(veafTransportMission.friendlyGroupWatchdog,{},timer.getTime()+veafTransportMission.SecondsBetweenWatchdogChecks)
    else
        trigger.action.outText("Friendly group has been destroyed! The mission is a failure!", 5)
        veafTransportMission.cleanupAfterMission()
    end
end

function veafTransportMission.reportTargetInformation(unitName)
    -- generate information dispatch
    local nbVehicles, nbInfantry = veafUnits.countInfantryAndVehicles(veafTransportMission.BlueGroupName)

    local message =      "DROP ZONE : ressuply a group of " .. nbVehicles .. " vehicles and " .. nbInfantry .. " soldiers.\n"
    message = message .. "\n"
    if veafTransportMission.DoRadioTransmission then 
        message = message .. "NAVIGATION: They will transmit on 550 kHz every " .. veafTransportMission.SecondsBetweenAdfLoops .. " seconds.\n"
    end

    -- add coordinates and position from bullseye
    local averageGroupPosition = veaf.getAveragePosition(veafTransportMission.BlueGroupName)
    local lat, lon = coord.LOtoLL(averageGroupPosition)
    local mgrsString = mist.tostringMGRS(coord.LLtoMGRS(lat, lon), 3)
    local bullseye = mist.utils.makeVec3(mist.DBs.missionData.bullseye.blue, 0)
    local vec = {x = averageGroupPosition.x - bullseye.x, y = averageGroupPosition.y - bullseye.y, z = averageGroupPosition.z - bullseye.z}
    local dir = mist.utils.round(mist.utils.toDegree(mist.utils.getDir(vec, bullseye)), 0)
    local dist = mist.utils.get2DDist(averageGroupPosition, bullseye)
    local distMetric = mist.utils.round(dist/1000, 0)
    local distImperial = mist.utils.round(mist.utils.metersToNM(dist), 0)
    local fromBullseye = string.format('%03d', dir) .. ' for ' .. distMetric .. 'km /' .. distImperial .. 'nm'

    message = message .. "LAT LON (decimal): " .. mist.tostringLL(lat, lon, 2) .. ".\n"
    message = message .. "LAT LON (DMS)    : " .. mist.tostringLL(lat, lon, 0, true) .. ".\n"
    message = message .. "MGRS/UTM         : " .. mgrsString .. ".\n"
    message = message .. "FROM BULLSEYE    : " .. fromBullseye .. ".\n"
    message = message .. "\n"

    -- get altitude, qfe and wind information
    local altitude = veaf.getLandHeight(averageGroupPosition)
    --local qfeHp = mist.utils.getQFE(averageGroupPosition, false)
    --local qfeinHg = mist.utils.getQFE(averageGroupPosition, true)
    local windDirection, windStrength = veaf.getWind(veaf.placePointOnLand(averageGroupPosition))

    message = message .. 'DROP ZONE ALT       : ' .. altitude .. " meters.\n"
    --message = message .. 'TARGET QFW       : ' .. qfeHp .. " hPa / " .. qfeinHg .. " inHg.\n"
    local windText =     'no wind.\n'
    if windStrength > 0 then
        windText = string.format(
                         'from %s at %s m/s.\n', windDirection, windStrength)
    end
    message = message .. 'WIND OVER DROP ZONE : ' .. windText

    -- send message only for the unit
    veaf.outTextForUnit(unitName, message, 30)
end

--- add a smoke marker over the drop zone
function veafTransportMission.smokeTarget()
-- LOGGING DISABLED WHEN COMPILING("smokeTarget()")
    veafSpawn.spawnSmoke(veaf.getAveragePosition(veafTransportMission.BlueGroupName), trigger.smokeColor.Green)
	trigger.action.outText('Copy smoke requested, GREEN smoke marks the drop zone!',5)
    veafRadio.delCommand(veafTransportMission.targetMarkersPath, 'Request smoke on drop zone')
    veafRadio.addCommandToSubmenu('Drop zone is marked with GREEN smoke', veafTransportMission.targetMarkersPath, veaf.emptyFunction)
    veafTransportMission.smokeResetTaskID = mist.scheduleFunction(veafTransportMission.smokeReset,{},timer.getTime()+veafTransportMission.SecondsBetweenSmokeRequests)
    veafRadio.refreshRadioMenu()
end

--- Reset the smoke request radio menu
function veafTransportMission.smokeReset()
-- LOGGING DISABLED WHEN COMPILING("smokeReset()")
    veafRadio.delCommand(veafTransportMission.targetMarkersPath, 'Drop zone is marked with GREEN smoke')
    veafRadio.addCommandToSubmenu('Request smoke on drop zone', veafTransportMission.targetMarkersPath, veafTransportMission.smokeTarget)
    trigger.action.outText('Smoke marker over drop zone available',5)
    veafRadio.refreshRadioMenu()
end

--- add an illumination flare over the target area
function veafTransportMission.flareTarget()
-- LOGGING DISABLED WHEN COMPILING("flareTarget()")
    veafSpawn.spawnIlluminationFlare(veaf.getAveragePosition(veafTransportMission.BlueGroupName))
	trigger.action.outText('Copy illumination flare requested, illumination flare over target area!',5)
    veafRadio.delCommand(veafTransportMission.targetMarkersPath, 'Request illumination flare over drop zone')
    veafRadio.addCommandToSubmenu('Drop zone is lit with illumination flare', veafTransportMission.targetMarkersPath, veaf.emptyFunction)
    veafTransportMission.flareResetTaskID = mist.scheduleFunction(veafTransportMission.flareReset,{},timer.getTime()+veafTransportMission.SecondsBetweenFlareRequests)
    veafRadio.refreshRadioMenu()
end

--- Reset the flare request radio menu
function veafTransportMission.flareReset()
-- LOGGING DISABLED WHEN COMPILING("flareReset()")
    veafRadio.delCommand(veafTransportMission.targetMarkersPath, 'Drop zone is lit with illumination flare')
    veafRadio.addCommandToSubmenu('Request illumination flare over drop zone', veafTransportMission.targetMarkersPath, veafTransportMission.flareTarget)
    trigger.action.outText('Illumination flare over drop zone available',5)
    veafRadio.refreshRadioMenu()
end


--- Called from the "Skip delivery" radio menu : remove the current transport mission
function veafTransportMission.skip()
    veafTransportMission.cleanupAfterMission()
    trigger.action.outText("Transport mission cleaned up.", 5)
end

--- Cleanup after either mission is ended or aborted
function veafTransportMission.cleanupAfterMission()
-- LOGGING DISABLED WHEN COMPILING("cleanupAfterMission()")

    -- destroy groups
-- LOGGING DISABLED WHEN COMPILING("destroy friendly group")
    local group = Group.getByName(veafTransportMission.BlueGroupName)
    if group and group:isExist() == true then
        group:destroy()
    end

-- LOGGING DISABLED WHEN COMPILING("destroy cargos")
    local unitNum = 1
    local doIt = true
    while doIt do
        local cargo = StaticObject.getByName(veafTransportMission.BlueCargoName.." #"..unitNum)
        if cargo and cargo:isExist() == true then
            cargo:destroy()
            unitNum = unitNum + 1
        else
            doIt = false
        end
    end

-- LOGGING DISABLED WHEN COMPILING("destroy enemy defense group")
    local groupNum = 1
    local doIt = true
    while doIt do
        group = Group.getByName(veafTransportMission.RedDefenseGroupName.." #"..groupNum)
        if group and group:isExist() == true then
            group:destroy()
            groupNum = groupNum + 1
        else
            doIt = false
        end
    end

-- LOGGING DISABLED WHEN COMPILING("destroy enemy blocade group")
    group = Group.getByName(veafTransportMission.RedBlocadeGroupName)
    if group and group:isExist() == true then
        group:destroy()
    end

    -- remove the watchdog function
-- LOGGING DISABLED WHEN COMPILING("remove the watchdog function")
    if veafTransportMission.friendlyGroupAliveCheckTaskID ~= 'none' then
        mist.removeFunction(veafTransportMission.friendlyGroupAliveCheckTaskID)
    end
    veafTransportMission.friendlyGroupAliveCheckTaskID = 'none'

    -- remove the watchdog function
-- LOGGING DISABLED WHEN COMPILING("remove the adf loop function")
    if veafTransportMission.friendlyGroupAdfLoopTaskID ~= 'none' then
        mist.removeFunction(veafTransportMission.friendlyGroupAdfLoopTaskID)
    end
    veafTransportMission.friendlyGroupAdfLoopTaskID = 'none'

    veafRadio.delCommand(veafTransportMission.rootPath, 'Skip current objective')
    veafRadio.delCommand(veafTransportMission.rootPath, 'Get current objective situation')
    veafRadio.delCommand(veafTransportMission.rootPath, 'Drop zone markers')
    veafRadio.delSubmenu(veafTransportMission.targetMarkersPath, veafTransportMission.rootPath)

    veafRadio.refreshRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("cleanupAfterMission DONE")

end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Radio menu and help
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Build the initial radio menu
function veafTransportMission.buildRadioMenu()
    veafTransportMission.rootPath = veafRadio.addSubMenu(veafTransportMission.RadioMenuName)
    veafRadio.addCommandToSubmenu("HELP", veafTransportMission.rootPath, veafTransportMission.help, nil, veafRadio.USAGE_ForGroup)
    -- TODO add this command when the respawn will work (see veafTransportMission.resetAllCargoes)
    -- missionCommands.addCommand('Respawn all cargoes', veafTransportMission.rootPath, veafTransportMission.resetAllCargoes)
end

function veafTransportMission.help(unitName)
    local text =
        'Create a marker and type "_transport" in the text\n' ..
        'This will create a default friendly group awaiting cargo that you need to transport\n' ..
        'You can add options (comma separated) :\n' ..
        '   "defense [0-5]" to specify air defense cover on the way (1 = light, 5 = heavy)\n' ..
        '        defense = 1 : 3-7 soldiers, GAZ-3308 transport\n' ..
        '        defense = 2 : 3-7 soldiers, BTR-80 APC\n' ..
        '        defense = 3 : 3-7 soldiers, chance of BMP-1 IFV, chance of Igla manpad\n' ..
        '        defense = 4 : 3-7 soldiers, big chance of BMP-1 IFV, big chance of Igla-S manpad, chance of ZU-23 on a truck\n' ..
        '        defense = 5 : 3-7 soldiers, BMP-1 IFV, big chance of Igla-S manpad, chance of ZSU-23-4 Shilka\n' ..
        '   "size [1-5]" to change the number of cargo items to be transported (1 per participating helo, usually)\n' ..
        '   "blocade [0-5]" to specify enemy blocade around the drop zone (1 = light, 5 = heavy)'

    veaf.outTextForUnit(unitName, text, 30)
end


function veafTransportMission.endTransportOfCargo(cargoName)
    local text = 
    'Congratulations on a job well done ! Cargo ' .. cargoName .. ' has been delivered safely'
    trigger.action.outText(text, 15)
    -- TODO reset cargo position
    -- mist.respawnGroup(cargoName, 15) 
    -- does not work yet because 1. the unit name is changed by mist and 2. the trigger zone condition does not work with the new unit (maybe bc of 1. ?)
end

function veafTransportMission.resetAllCargoes()
    -- does not work yet (see veafTransportMission.endTransportOfCargo)
    local lunits = mist.DBs.unitsByNum
    if lunits then
        for i = 1, #lunits do
            if lunits[i] and lunits[i].unitName and lunits[i].unitName:lower():find('cargo - ') then 
                local name = lunits[i].unitName
                -- destroy cargo static unit
                local c = StaticObject.getByName(name)
                if c then
                    StaticObject.destroy(c)
                end
                mist.respawnGroup(name, true)
            end
        end
    end
    trigger.action.outText("All cargoes have been respawned", 15)
end

function veafTransportMission.initializeAllHelosInCTLD()
    veaf.loggers.get(veafTransportMission.Id):warn("Please use ctld.autoInitializeAllHumanTransports - it's automatically run by the veaf.lua script")
end

function veafTransportMission.initializeAllLogisticInCTLD()
    veaf.loggers.get(veafTransportMission.Id):warn("Please use ctld.autoInitializeAllLogistic - it's automatically run by the veaf.lua script")
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- initialisation
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafTransportMission.initialize()
    veafTransportMission.buildRadioMenu()
    veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafTransportMission.onEventMarkChange)
end

veaf.loggers.get(veafTransportMission.Id):info(string.format("Loading version %s", veafTransportMission.Version))

--- Enable/Disable error boxes displayed on screen.
env.setErrorMessageBoxEnabled(false)


------------------ END script veafTransportMission.lua  ------------------


------------------ START script veafUnits.lua  ------------------

------------------------------------------------------------------
-- VEAF groups and units database for DCS World
-- By zip (2018)
--
-- Features:
-- ---------
-- * Contains all the units aliases and groups definitions used by the other VEAF scripts
--
-- See the documentation : https://veaf.github.io/documentation/
------------------------------------------------------------------

veafUnits = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the root VEAF constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafUnits.Id = "UNITS"

--- Version.
veafUnits.Version = "1.14.0"

-- trace level, specific to this module
--veafUnits.LogLevel = "trace"

veaf.loggers.new(veafUnits.Id, veafUnits.LogLevel)

--- If no unit is spawned in a cell, it will default to this width
veafUnits.DefaultCellWidth = 10

--- If no unit is spawned in a cell, it will default to this height
veafUnits.DefaultCellHeight = 10

--- Group format that will be spawned then destroyed from a convoy to fix the AI's dumb pathfinding as of 17/08/2022
veafUnits.DefaultPathfindingUnitType = "TZ-22_KrAZ"
veafUnits.DefaultPathfindingGroup = {}
veafUnits.DefaultPathfindingGroup =
{
    disposition = {h=1, w=1},
    units = {
        {veafUnits.DefaultPathfindingUnitType, random = true}
    },
    groupName = "Pathfinder",
    description = "Plz Fix ED"
}
--- delay before the pathfinding fix unit is destroyed
veafUnits.delayBeforePathfindingFix = 5

--- if true, the groups and units lists will be printed to the logs, so they can be saved to the documentation files
veafUnits.OutputListsForDocumentation = false

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Utility methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafUnits.traceGroup(group, cells)
    if group and veafUnits.Trace then
-- LOGGING DISABLED WHEN COMPILING("")
-- LOGGING DISABLED WHEN COMPILING(" Group : " .. group.description)
-- LOGGING DISABLED WHEN COMPILING("")
        local nCols = group.disposition.w
        local nRows = group.disposition.h

        local line1 = "|    |"
        local line2 = "|----|"

        for nCol = 1, nCols do
            line1 = line1 .. "                ".. string.format("%02d", nCol) .."              |"
            line2 = line2 .. "--------------------------------|"
        end
-- LOGGING DISABLED WHEN COMPILING(line1)
-- LOGGING DISABLED WHEN COMPILING(line2)

        local unitCounter = 1
        for nRow = 1, nRows do
            local line1 = "|    |"
            local line2 = "| " .. string.format("%02d", nRow) .. " |"
            local line3 = "|    |"
            local line4 = "|----|"
            for nCol = 1, nCols do
                local cellNum = (nRow - 1) * nCols + nCol
                local cell = cells[cellNum]
                local left = "        "
                local top = "        "
                local right = "        "
                local bottom = "        "
                local bottomleft = "                      "
                local center = "                "

                if cell then

                    local unit = cell.unit
                    if unit then
                        local unitName = unit.typeName
                        if unitName:len() > 11 then
                            unitName = unitName:sub(1,11)
                        end
                        unitName = string.format("%02d", unitCounter) .. "-" .. unitName
                        local spaces = 14 - unitName:len()
                        for i=1, math.floor(spaces/2) do
                            unitName = " " .. unitName
                        end
                        for i=1, math.ceil(spaces/2) do
                            unitName = unitName .. " "
                        end
                        center = " " .. unitName .. " "

                        bottomleft = string.format("               %03d    ", mist.utils.toDegree(unit.spawnPoint.hdg))

                        unitCounter = unitCounter + 1
                    end

                    left = string.format("%08d",math.floor(cell.left))
                    top = string.format("%08d",math.floor(cell.top))
                    right = string.format("%08d",math.floor(cell.right))
                    bottom = string.format("%08d",math.floor(cell.bottom))
                end

                line1 = line1 .. "  " .. top .. "                      " .. "|"
                line2 = line2 .. "" .. left .. center .. right.. "|"
                line3 = line3 .. bottomleft  .. bottom.. "  |"
                line4 = line4 .. "--------------------------------|"

            end
-- LOGGING DISABLED WHEN COMPILING(line1)
-- LOGGING DISABLED WHEN COMPILING(line2)
-- LOGGING DISABLED WHEN COMPILING(line3)
-- LOGGING DISABLED WHEN COMPILING(line4)
        end
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Core methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Browse all the units in a group and counts the infantry and vehicles remaining
function veafUnits.countInfantryAndVehicles(groupname)
    local nbVehicles = 0
    local nbInfantry = 0
    local group = Group.getByName(groupname)
    if group and group:isExist() == true and #group:getUnits() > 0 then
        for _, u in pairs(group:getUnits()) do
            local typeName = u:getTypeName()
            if typeName then
                local unit = veafUnits.findUnit(typeName)
                if unit then
                    if unit.vehicle then
                        nbVehicles = nbVehicles + 1
                    elseif unit.infantry then
                        nbInfantry = nbInfantry + 1
                    end
                end
            end
        end
    end
    return nbVehicles, nbInfantry
end

--- searches the DCS database for a unit having this type (case insensitive)
function veafUnits.findDcsUnit(unitType)
-- LOGGING DISABLED WHEN COMPILING("veafUnits.findDcsUnit(unitType=" .. unitType .. ")")

    -- find the desired unit in the DCS units database
    local unit = nil
    for _, u in pairs(dcsUnits.DcsUnitsDatabase) do
        if      (u and u.type and unitType:lower() == u.type:lower())
            or  (u and u.name and unitType:lower() == u.name:lower())
        then
            unit = u
            break
        end
    end

    return unit
end

--- process a group definition and return a usable group table
function veafUnits.processGroup(group)
    local result = {}

    -- initialize result table and copy metadata
    result.disposition = {}
    result.disposition.h = group.disposition.h
    result.disposition.w = group.disposition.w
    result.description = group.description
    result.groupName = group.groupName
    result.units = {}
-- LOGGING DISABLED WHEN COMPILING("group="..veaf.p(group))
    local unitNumber = 1
    -- replace all units with a simplified structure made from the DCS unit metadata structure
    for i = 1, #group.units do
        local unitType
        local cell = nil
        local number = nil
        local size = nil
        local hdg = nil
        local random = false
        local fitToUnit = false
        local u = group.units[i]
-- LOGGING DISABLED WHEN COMPILING("u="..veaf.p(u))
        if type(u) == "string" then
            -- information was skipped using simplified syntax
            unitType = u
        else
            unitType = u.typeName
            if not unitType then
                unitType = u[1]
            end
-- LOGGING DISABLED WHEN COMPILING("unitType="..veaf.p(unitType))
            cell = u.cell
            number = u.number
            size = u.size
            hdg = u.hdg
            if type(size) == "number" then
                size = {}
                size.width = u.size
                size.height = u.size
            end
            if u.random then
                random = true
            end
            if u.fitToUnit then
                fitToUnit = true
            end
        end
        if not(number) then
          number = 1
        end
        if type(number) == "table" then
            -- create a random number of units
            local min = number.min
            local max = number.max
            if not(min) then min = 1 end
            if not(max) then max = 1 end
            number = math.random(min, max)
        end
        if not(hdg) then
            hdg = math.random(0, 359) -- default heading is random
        end
-- LOGGING DISABLED WHEN COMPILING(string.format("hdg=%d",hdg))
        for numUnit = 1, number do
-- LOGGING DISABLED WHEN COMPILING("searching for unit [" .. unitType .. "] listed in group [" .. group.groupName .. "]")
            local unit = veafUnits.findUnit(unitType)
            if not(unit) then
                veaf.loggers.get(veafUnits.Id):info("cannot find unit [" .. unitType .. "] listed in group [" .. group.groupName .. "]")
            else
                unit.cell = cell
                unit.hdg = hdg
                unit.random = random
                unit.fitToUnit = fitToUnit
                unit.size = size
                result.units[unitNumber] = unit
                unitNumber = unitNumber + 1
            end
        end
    end

    -- check group type (WARNING : unit types should not be mixed !)
    for _, unit in pairs(result.units) do
        if unit.naval then
            result.naval = true
            break
        end
        if unit.air then
            result.air = true
            break
        end
    end

-- LOGGING DISABLED WHEN COMPILING("result="..veaf.p(result))

    return result
end


--- searches the database for a group having this alias (case insensitive)
function veafUnits.findGroup(groupAlias)
-- LOGGING DISABLED WHEN COMPILING("veafUnits.findGroup(groupAlias=" .. groupAlias .. ")")

    -- find the desired group in the groups database
    local result = nil

    for _, g in pairs(veafUnits.GroupsDatabase) do
        for _, alias in pairs(g.aliases) do
            if alias:lower() == groupAlias:lower() then
                result = veafUnits.processGroup(g.group)
                break
            end
        end
    end

    return result
end

--- searches the database for a unit having this alias (case insensitive)
function veafUnits.findUnit(unitAlias)
-- LOGGING DISABLED WHEN COMPILING("veafUnits.findUnit(unitAlias=" .. unitAlias .. ")")

    -- find the desired unit in the units database
    local unit = nil

    for _, u in pairs(veafUnits.UnitsDatabase) do
        for _, alias in pairs(u.aliases) do
            if alias:lower() == unitAlias:lower() then
                unit = u
                break
            end
        end
    end

    if unit then
        unit = veafUnits.findDcsUnit(unit.unitType)
    else
        unit = veafUnits.findDcsUnit(unitAlias)
    end
    if not(unit) then
        veaf.loggers.get(veafUnits.Id):info("cannot find unit [" .. unitAlias .. "]")
    else
        unit = veafUnits.makeUnitFromDcsStructure(unit, 1)
    end

    return unit
end

--- Creates a simple structure from DCS complex metadata structure
function veafUnits.makeUnitFromDcsStructure(dcsUnit, cell)
    local result = {}
    if not(dcsUnit) then
        return nil
    end
--[[
        [9] = 
    {
        ["type"] = "Vulcan",
        ["name"] = "AAA Vulcan M163",
        ["category"] = "Air Defence",
        ["vehicle"] = true,
        ["description"] = "AAA Vulcan M163",
        ["aliases"] = 
        {
            [1] = "M163 Vulcan",
        }, -- end of ["aliases"]
    }, -- end of [9]
]]
    result.category = dcsUnit.category
    result.typeName = dcsUnit.type
    result.displayName = dcsUnit.description
    result.naval = (dcsUnit.naval)
    result.air = (dcsUnit.air)

    if (not(dcsUnit.naval) and not(dcsUnit.air) and not(dcsUnit.infantry) and not(dcsUnit.vehicle) and (dcsUnit.attribute==nil or dcsUnit.attribute.Fortifications==nil)) then
        result.static = true
    end

    result.infantry = (dcsUnit.infantry)
    result.vehicle = (dcsUnit.vehicle)
    --[[
    result.size = { x = veaf.round(dcsUnit.desc.box.max.x - dcsUnit.desc.box.min.x, 1), y = veaf.round(dcsUnit.desc.box.max.y - dcsUnit.desc.box.min.y, 1), z = veaf.round(dcsUnit.desc.box.max.z - dcsUnit.desc.box.min.z, 1)}
    result.width = result.size.z
    result.length= result.size.x
    -- invert if width > height
    if result.width > result.length then
        local width = result.width
        result.width = result.length
        result.length = width
    end
    ]]
    result.cell = cell

    return result
end

--- checks if position is correct for the unit type
function veafUnits.checkPositionForUnit(spawnPosition, unit)
-- LOGGING DISABLED WHEN COMPILING("checkPositionForUnit()")
-- LOGGING DISABLED WHEN COMPILING("spawnPosition=%s", spawnPosition)
    local vec2 = { x = spawnPosition.x, y = spawnPosition.z }
-- LOGGING DISABLED WHEN COMPILING("vec2=%s", vec2)
-- LOGGING DISABLED WHEN COMPILING("unit=%s", unit)
    local landType = land.getSurfaceType(vec2)

    local IsNavalStatic = false --offshore static (list in dcsUnits.lua) flag
    if unit.static and veaf.findInTable(dcsUnits.NavalStatics, unit.typeName) then
-- LOGGING DISABLED WHEN COMPILING("Is Naval Static")
        IsNavalStatic = true
    end

    if landType == land.SurfaceType.WATER then
-- LOGGING DISABLED WHEN COMPILING("landType = WATER")
    else
-- LOGGING DISABLED WHEN COMPILING("landType = GROUND")
    end
    if spawnPosition then
        if unit.air then -- if the unit is a plane or helicopter
            if spawnPosition.z <= 10 then -- if lower than 10m don't spawn unit
                return false
            end
        elseif unit.naval or IsNavalStatic then -- if the unit is a naval unit or an offshore static
            if landType ~= land.SurfaceType.WATER then -- don't spawn over anything but water
                return false
            end
        else
            if landType == land.SurfaceType.WATER then -- don't spawn over water
                return false
            end
        end
    end
    return true
end

--- Adds a placement point to every unit of the group, centering the whole group around the spawnPoint, and adding an optional spacing
function veafUnits.placeGroup(group, spawnPoint, spacing, hdg, hasDest)
-- LOGGING DISABLED WHEN COMPILING(string.format("group = %s",veaf.p(group)))
    if not(hdg) then
        hdg = 0 -- default north
    end

    local hasDest = false or hasDest
-- LOGGING DISABLED WHEN COMPILING(string.format("hasDest = %s", veaf.p(hasDest)))

    if not(group.disposition) then
        -- default disposition is a square
        local l = math.ceil(math.sqrt(#group.units))
        group.disposition = { h = l, w = l}
    end

    local nRows = nil
    local nCols = nil

    if hasDest then
        local pathfindingFixer = veafUnits.processGroup(veafUnits.DefaultPathfindingGroup) --insert a unit (structured into a group) that will be destroyed just after the convoy is spawned, this is to fix the AI weird pathfinding
        table.insert(group.units, pathfindingFixer.units[1]) --insert the unit that has all of the necessary info into the group that's being placed
        nRows = #group.units
        nCols = 1
    else
        nRows = group.disposition.h
        nCols = group.disposition.w
    end

    -- sort the units by occupied cell
    local fixedUnits = {}
    local freeUnits = {}
    for _, unit in pairs(group.units) do
        if unit.cell and not hasDest then --if the group has a destination, programmer defined patterns do not apply anymore as the convoy is spawned in a line
            table.insert(fixedUnits, unit)
        else
            table.insert(freeUnits, unit)
        end
    end

    local cells = {}
    local allCells = {}
    for cellNum = 1, nRows*nCols do
        allCells[cellNum] = cellNum
    end

    -- place fixed units in their designated cells
    for i = 1, #fixedUnits do
        local unit = fixedUnits[i]
        cells[unit.cell] = {}
        cells[unit.cell].unit = unit

        -- remove this cell from the list of available cells
        for cellNum = 1, #allCells do
            if allCells[cellNum] == unit.cell then
                table.remove(allCells, cellNum)
                break
            end
        end
    end

    -- randomly place non-fixed units in the remaining cells
    for i = 1, #freeUnits do
        local randomCellNum = allCells[math.random(1, #allCells)]
        local unit = freeUnits[i]
        unit.cell = randomCellNum
        cells[unit.cell] = {}
        cells[randomCellNum].unit = unit

        -- remove this cell from the list of available cells
        for cellNum = 1, #allCells do
            if allCells[cellNum] == unit.cell then
                table.remove(allCells, cellNum)
                break
            end
        end
    end

    if hasDest then
        local cellGreater = function(unit1, unit2)
            if unit1 and unit2 and unit1.cell < unit2.cell then
                return true
            else
                return false
            end
        end

        table.sort(group.units, cellGreater)
    end

    -- compute the size of the cells, rows and columns
    local cols = {}
    local rows = {}
    for nRow = 1, nRows do
        for nCol = 1, nCols do
            local cellNum = (nRow - 1) * nCols + nCol
            local cell = cells[cellNum]
            local colWidth = 0
            local rowHeight = 0
            if cols[nCol] then
                colWidth = cols[nCol].width
            end
            if rows[nRow] then
                rowHeight = rows[nRow].height
            end
            if cell then
                cell.width = veafUnits.DefaultCellWidth + (spacing * veafUnits.DefaultCellWidth)
                cell.height = veafUnits.DefaultCellHeight + (spacing * veafUnits.DefaultCellHeight)
                local unit = cell.unit
                if unit then
                    unit.cell = cellNum
                    if unit.width and unit.width > 0 then
                        cell.width = unit.width + (spacing * unit.width)
                    end
                    if unit.length and unit.length > 0 then
                        cell.height = unit.length + (spacing * unit.length)
                    end
                    if unit.size then
                        cell.width = unit.size.width + (spacing * unit.size.width)
                        cell.height = unit.size.height + (spacing * unit.size.height)
                    end
                end
                if not unit.fitToUnit then
                    -- make the cell square
                    if cell.width > cell.height then
                        cell.height = cell.width
                    elseif cell.width < cell.height then
                        cell.width = cell.height
                    end
                end
                if cell.width > colWidth then
                    colWidth = cell.width
                end
                if cell.height > rowHeight then
                    rowHeight = cell.height
                end
            end
            cols[nCol] = {}
            cols[nCol].width = colWidth
            rows[nRow] = {}
            rows[nRow].height = rowHeight
        end
    end

    -- compute the size of the grid
    local totalWidth = 0
    local totalHeight = 0
    for nCol = 1, #cols do
        totalWidth = totalWidth + cols[nCol].width
    end
    for nRow = 1, #rows do -- bottom -> up
        totalHeight = totalHeight + rows[#rows-nRow+1].height
    end
-- LOGGING DISABLED WHEN COMPILING(string.format("totalWidth = %d",totalWidth))
-- LOGGING DISABLED WHEN COMPILING(string.format("totalHeight = %d",totalHeight))
    -- place the grid
    local currentColLeft = spawnPoint.z - totalWidth/2
    local currentColTop = spawnPoint.x - totalHeight/2
    for nCol = 1, #cols do
-- LOGGING DISABLED WHEN COMPILING(string.format("currentColLeft = %d",currentColLeft))
        cols[nCol].left = currentColLeft
        cols[nCol].right= currentColLeft + cols[nCol].width
        currentColLeft = cols[nCol].right
    end
    for nRow = 1, #rows do -- bottom -> up
-- LOGGING DISABLED WHEN COMPILING(string.format("currentColTop = %d",currentColTop))
        rows[#rows-nRow+1].bottom = currentColTop
        rows[#rows-nRow+1].top = currentColTop + rows[#rows-nRow+1].height
        currentColTop = rows[#rows-nRow+1].top
    end

    -- compute the centers and extents of the cells
    for nRow = 1, nRows do
        for nCol = 1, nCols do
            local cellNum = (nRow - 1) * nCols + nCol
            local cell = cells[cellNum]
            if cell then
                cell.top = rows[nRow].top
                cell.bottom = rows[nRow].bottom
                cell.left = cols[nCol].left
                cell.right = cols[nCol].right
                cell.center = {}
                cell.center.x = cell.left + math.random((cell.right - cell.left) / 10, (cell.right - cell.left) - ((cell.right - cell.left) / 10))
                cell.center.y = cell.top + math.random((cell.bottom - cell.top) / 10, (cell.bottom - cell.top) - ((cell.bottom - cell.top) / 10))
            end
        end
    end

    --find the heading offset relative to the group's heading to spawn the units perpendicular to the road
    -- local convoyHDGoffset = 90
    -- if hasDest then
    --     local road_x, road_z = land.getClosestPointOnRoads('roads',spawnPoint.x, spawnPoint.z)
    --     local roadPoint = veaf.placePointOnLand({x = road_x, y = 0, z = road_z})
    --     local nearestRoadHDG = mist.utils.getHeadingPoints(spawnPoint, roadPoint,false) * 180 / math.pi
-- LOGGING DISABLED WHEN COMPILING(string.format("HDG to nearest road : %s", veaf.p(nearestRoadHDG)))
-- LOGGING DISABLED WHEN COMPILING(string.format("Group HDG : %s", veaf.p(hdg)))
    --     if nearestRoadHDG then
    --         nearestRoadHDG = nearestRoadHDG - hdg
    --         if nearestRoadHDG < 0 then
    --             nearestRoadHDG = nearestRoadHDG + 360
    --         end

    --         if nearestRoadHDG >= 180 then
    --             convoyHDGoffset = 270
    --         end
    --     end
    -- end

    -- randomly place the units
    for _, cell in pairs(cells) do
-- LOGGING DISABLED WHEN COMPILING(string.format("cell = %s",veaf.p(cell)))
        local unit = cell.unit
        if unit then
            unit.spawnPoint = {}
            if not cell.center then
                veaf.loggers.get(veafUnits.Id):error(string.format("Cannot find cell.center !"))
                veaf.loggers.get(veafUnits.Id):error(string.format("cell = %s",veaf.p(cell)))
                veaf.loggers.get(veafUnits.Id):error(string.format("group = %s",veaf.p(group)))
            end
            unit.spawnPoint.z = cell.center.x
            if unit.random and spacing > 0 then
                unit.spawnPoint.z = unit.spawnPoint.z + math.random(-((spacing-1) * (unit.width or veafUnits.DefaultCellWidth))/2, ((spacing-1) * (unit.width or veafUnits.DefaultCellWidth))/2)
            end
            unit.spawnPoint.x = cell.center.y
            if unit.random and spacing > 0 then
                unit.spawnPoint.x = unit.spawnPoint.x + math.random(-((spacing-1) * (unit.length or veafUnits.DefaultCellHeight))/2, ((spacing-1) * (unit.length or veafUnits.DefaultCellHeight))/2)
            end
            unit.spawnPoint.y = spawnPoint.y

            -- take into account group rotation, if needed
            if hdg > 0 then
                local angle = mist.utils.toRadian(hdg)
                local x = unit.spawnPoint.z - spawnPoint.z
                local y = unit.spawnPoint.x - spawnPoint.x
                local x_rotated = x * math.cos(angle) + y * math.sin(angle)
                local y_rotated = -x * math.sin(angle) + y * math.cos(angle)
                unit.spawnPoint.z = x_rotated + spawnPoint.z
                unit.spawnPoint.x = y_rotated + spawnPoint.x
            end

            -- unit heading
            if hasDest then --apply the offset when the group has a destination, 0 will make them spawn in line, 90 or 270 perpendicular to the group's hdg (the road if the group's hdg was set properly) etc.
                unit.hdg = 0 --convoyHDGoffset
            end

            if unit.hdg then
                local unitHeading = unit.hdg + hdg -- don't forget to add group heading
                if unitHeading > 360 then
                    unitHeading = unitHeading - 360
                end
                unit.spawnPoint.hdg = mist.utils.toRadian(unitHeading)
            else
                unit.spawnPoint.hdg = 0 -- due north
            end
        end
    end

    return group, cells
end

function veafUnits.removePathfindingFixUnit(groupName)
    local group = Group.getByName(groupName)

    if group then
        local units = group:getUnits()
        if units then
            for _,unit in pairs(units) do
                if unit then
                    local unitType = unit:getTypeName()
                    if unitType and unitType == veafUnits.DefaultPathfindingUnitType then
                        unit:destroy()
                        break
                    end
                end
            end
        end
    end
end

function veafUnits.logGroupsListInMarkdown()
    local function _sortGroupNameCaseInsensitive(g1,g2)
        if g1 and g1.group and g1.group.groupName and g2 and g2.group and g2.group.groupName then
            return string.lower(g1.group.groupName) < string.lower(g2.group.groupName)
        else
            return string.lower(g1) < string.lower(g2)
        end
    end

    local text = [[
This goes in [documentation\content\Mission maker\references\group-list.md]:

|Name|Description|Aliases|
|--|--|--|
]]
    veaf.loggers.get(veafUnits.Id):info(text)

    -- make a copy of the table
    local groupsCopy = {}
    for _, g in pairs(veafUnits.GroupsDatabase) do
        if not g.hidden then
            table.insert(groupsCopy, g)
        end
    end
    -- sort the copy
    table.sort(groupsCopy, _sortGroupNameCaseInsensitive)
    -- use the keys to retrieve the values in the sorted order
    for _, g in pairs(groupsCopy) do
        text = "|" .. g.group.groupName .. "|" .. g.group.description .. "|" .. table.concat(g.aliases, ", ") .. "|\n"
        veaf.loggers.get(veafUnits.Id):info(text)
    end
end

function veafUnits.logUnitsListInMarkdown()
    local function _sortUnitNameCaseInsensitive(u1,u2)
        if u1 and u1.name and u2 and u2.name then
            return string.lower(u1.name) < string.lower(u2.name)
        else
            return string.lower(u1) < string.lower(u2)
        end
    end

    local text = [[
This goes in [documentation\content\Mission maker\references\units-list.md]:

|Name|Description|Aliases|
|--|--|--|
]]
    veaf.loggers.get(veafUnits.Id):info(text)
    -- make a copy of the table
    local units = {}
    for k, data in pairs(dcsUnits.DcsUnitsDatabase) do
        local u = { name = k }
        for _, aliasData in pairs(veafUnits.UnitsDatabase) do
            if aliasData and aliasData.unitType and string.lower(aliasData.unitType) == string.lower(k) then
                u.aliases = aliasData.aliases
            end
        end
        if data then
            u.description = data.description
            u.typeName = data.type
        end
        table.insert(units, u)
    end
    -- sort the copy
    table.sort(units, _sortUnitNameCaseInsensitive)
    -- use the keys to retrieve the values in the sorted order
    for _, u in pairs(units) do  -- serialize its fields
        text = "|" .. u.name .. "|"
        if u.description then
            text = text .. u.description
        end
        text = text .. "|"
        if u.aliases then
            text = text .. table.concat(u.aliases, ", ")
        end
        text = text .. "|"
        veaf.loggers.get(veafUnits.Id):info(text)
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Units databases
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veafUnits.UnitsDatabase = {
    {
        aliases = {"hq7"},
        unitType = "HQ-7_LN_SP",
    },
    {
        aliases = {"hq7eo"},
        unitType = "HQ-7_LN_EO",
    },
    {
        aliases = {"sa8", "sa-8"},
        unitType = "Osa 9A33 ln",
    },
    {
        aliases = {"sa9", "sa-9"},
        unitType = "Strela-1 9P31"
    },
    {
        aliases = {"sa13", "sa-13"},
        unitType = "Strela-10M3",
    },
    {
        aliases = {"sa15", "sa-15"},
        unitType = "Tor 9A331",
    },
    {
        aliases = {"sa18", "sa-18", "manpad"},
        unitType = "SA-18 Igla-S manpad",
    },
    {
        aliases = {"dogear"},
        unitType = "Dog Ear radar",
    },
    {
        aliases = {"shilka"},
        unitType = "ZSU-23-4 Shilka",
    },
    {
        aliases = {"tarawa"},
        unitType = "LHA_Tarawa",
    },
    {
        aliases = {"blue-ewr"},
        unitType = "FPS-117",
    },
    {
        aliases = {"red-ewr"},
        unitType = "1L13 EWR",
    },
    {
        aliases = {"avenger"},
        unitType = "M1097 Avenger",
    },
}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Groups databases
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Syntax :
------------
-- 
-- aliases : list of aliases which can be used to designate this group, case insensitive
-- disposition : height and width (in cells) of the group layout template (see explanation of group layouts below)
-- units : list of all the units composing the group. Each unit in the list is composed of :
--      alias : alias of the unit in the VEAF units database, or actual DCS type name in the DCS units database
--      cell : preferred layout cell ; the unit will be spawned in this cell, in the layout defined in the *layout* field. (see explanation of group layouts below) ; when nothing else is  specified, a number after the unit alias is considered to be the *cell* parameter
--      size : fixes the cell size (in meters), instead of relying on the contained unit size (modified with the *spacing* parameter) ; can be either a table with width and height, or a number for square cells
--      number : either a number, which will be the quantity of this unit type spawned ; or a table, with *min* and *max* values that will be used to spawn a random quantity of this unit typ
--      hdg : the unit heading will mean that, if the group is spawned facing north, this unit will be facing this heading (in degrees). If not set, units will face the group heading
--      random : if set, the unit will be placed randomly in the cell, leaving a one unit size margin around.
--      fitToUnit : if set, the cell around the unit will not be a square but a rectangle of the unit's exact size (plus the spacing, if set)
-- description = human-friendly name for the group
-- groupName   = name used when spawning this group (will be flavored with a numerical suffix)
--
-- empty cells measure 10m x 10m

veafUnits.GroupsDatabase = {
    --China
    {
        aliases = {"hq7"},
        group = {
            disposition = { h= 5, w= 5},
            units = {
                --STR
                {"HQ-7_STR_SP", cell = 8},
                --LN
                {"HQ-7_LN_SP", cell = 1, hdg = 300}, {"HQ-7_LN_SP", cell = 5, hdg = 60}, {"HQ-7_LN_SP", cell = 23, hdg = 180},
                --supply truck
                {"Ural-375", random=true},
            },
            description = "HQ-7 SAM site",
            groupName = "HQ-7"
        },
    },
    {
        aliases = {"hq7_single"},
        group = {
            disposition = { h= 3, w= 3},
            units = {
                --LN
                {"HQ-7_LN_SP", cell = 1},
                --supply truck
                {"Ural-375", random=true},
            },
            description = "HQ-7 SAM site",
            groupName = "HQ-7"
        },
    },
    {
        aliases = {"hq7-noew"},
        group = {
            disposition = { h= 5, w= 5},
            units = {{"HQ-7_LN_SP", cell = 1, hdg = 300}, {"HQ-7_LN_SP", cell = 5, hdg = 60}, {"HQ-7_LN_SP", cell = 23, hdg = 180}, {"Ural-375", random=true},},
            description = "HQ-7 SAM site without STR",
            groupName = "HQ-7"
        },
    },
    {
        aliases = {"hq7eo"},
        group = {
            disposition = { h= 5, w= 5},
            units = {
                --STR
                {"HQ-7_STR_SP", cell = 8},
                --LN
                {"HQ-7_LN_EO", cell = 1, hdg = 300}, {"HQ-7_LN_EO", cell = 5, hdg = 60}, {"HQ-7_LN_EO", cell = 23, hdg = 180},
                --supply truck
                {"Ural-375", random=true},
            },
            description = "HQ-7EO SAM site",
            groupName = "HQ-7"
        },
    },
    {
        aliases = {"hq7eo_single"},
        group = {
            disposition = { h= 3, w= 3},
            units = {
                --LN
                {"HQ-7_LN_EO", cell = 1},
                --supply truck
                {"Ural-375", random=true},
            },
            description = "HQ-7EO SAM site",
            groupName = "HQ-7"
        },
    },
    {
        aliases = {"hq7eo-noew"},
        group = {
            disposition = { h= 5, w= 5},
            units = {{"HQ-7_LN_EO", cell = 1, hdg = 300}, {"HQ-7_LN_EO", cell = 5, hdg = 60}, {"HQ-7_LN_EO", cell = 23, hdg = 180}, {"Ural-375", random=true},},
            description = "HQ-7EO SAM site without STR",
            groupName = "HQ-7"
        },
    },
    --Warsaw Pact
    {
        aliases = {"sa2", "sa-2", "fs"},
        group = {
            disposition = { h= 6, w= 8},
            units = {
                {"SNR_75V", cell = 20}, {"p-19 s-125 sr", cell = 48}, {"S_75M_Volhov", cell = 2, hdg = 315}, {"S_75M_Volhov", cell = 6, hdg = 45}, {"S_75M_Volhov", cell = 17, hdg = 270}, {"S_75M_Volhov", cell = 24, hdg = 90}, {"S_75M_Volhov", cell = 34, hdg = 225}, {"S_75M_Volhov", cell = 38, hdg = 135},
                {"ZSU_57_2", number = {min=1, max=2}, random=true},
                {"S-60_Type59_Artillery", number = {min=1, max=2}, random=true},
                {"ZIL-135", number = {min = 1, max = 2}, random=true},
            },
            description = "SA-2 SAM site",
            groupName = "SA2"
        },
    },
    {
        aliases = {"sa3", "sa-3", "lb"},
        group = {
            disposition = { h= 7, w= 9},
            units = {
                {"p-19 s-125 sr", cell = 1}, {"snr s-125 tr", cell = 33}, {"5p73 s-125 ln", cell = 18, hdg = 30}, {"5p73 s-125 ln", cell = 30, hdg = 270}, {"5p73 s-125 ln", cell = 61, hdg = 150},
                {"ZSU_57_2", number = {min=1, max=2}, random=true},
                {"S-60_Type59_Artillery", number = {min=1, max=2}, random=true},
                {"ZIL-135", number = {min = 1, max = 2}, random=true},
            },
            description = "SA-3 SAM site",
            groupName = "SA3"
        },
    },
    {
        -- Sa-5 SAM site (2 launcher battery configuration, IRL 5 maximum could be seen deployed on one site). Note: doesn't come out exactly as planned when spawned, but close enough
        aliases = {"sa5", "sa-5", "S-200", "S200", "s200", "s-200"},
        group = {
            disposition = {h= 155, w= 120},
            units = {
                -- Search radar unit
                {"RLS_19J6", cell = 17614, size = 10},
                -- Track radar unit
                {"RPC_5N62V", cell = 15118, size = 10},
                --generator units
                {"generator_5i57", hdg = 80, cell = 15358, size = 10}, {"generator_5i57", hdg = 75, cell = 17854, size = 10}, --Track Radar, Search Radar generator

                -- launchers
                --battery 1
                {"S-200_Launcher", hdg = 2, cell = 676, size = 10}, {"S-200_Launcher", hdg = 4, cell = 702, size = 10}, {"S-200_Launcher", hdg = 358, cell = 2749, size = 10}, {"S-200_Launcher", hdg = 358, cell = 3069, size = 10}, {"S-200_Launcher", hdg = 358, cell = 4786, size = 10}, {"S-200_Launcher", hdg = 355, cell = 5115, size = 10},
                --battery 2
                {"S-200_Launcher", hdg = 347, cell = 7231, size = 10}, {"S-200_Launcher", hdg = 343, cell = 8045, size = 10}, {"S-200_Launcher", hdg = 341, cell = 8935, size = 10}, {"S-200_Launcher", hdg = 355, cell = 9962, size = 10}, {"S-200_Launcher", hdg = 350, cell = 10734, size = 10}, {"S-200_Launcher", hdg = 340, cell = 11781, size = 10},

                -- missile loading trucks (emulated, 1 per launcher for simplicity, 2 IRL)
                --battery 1
                {"ZIL-135", hdg = 174, cell = 76, size = 10}, {"ZIL-135", hdg = 181, cell = 102, size = 10}, {"ZIL-135", hdg = 194, cell = 2149, size = 10}, {"ZIL-135", hdg = 179, cell = 2469, size = 10}, {"ZIL-135", hdg = 165, cell = 4186, size = 10}, {"ZIL-135", hdg = 183, cell = 4515, size = 10},
                --battery 2
                {"ZIL-135", hdg = 157, cell = 6631, size = 10}, {"ZIL-135", hdg = 175, cell = 7445, size = 10}, {"ZIL-135", hdg = 165, cell = 8335, size = 10}, {"ZIL-135", hdg = 175, cell = 9362, size = 10}, {"ZIL-135", hdg = 172, cell = 10134, size = 10}, {"ZIL-135", hdg = 179, cell = 11181, size = 10},

                -- C2 units
                --battery 1
                {"ZIL-131 KUNG", hdg = 192, cell = 2730, size = 10},
                --battery 2
                {"ZIL-131 KUNG", hdg = 170, cell = 9262, size = 10},
                --site wide
                {"ZIL-131 KUNG", cell = 14616, size = 10}
            },
            description = "S200 SAM site",
            groupName = "S200"
        },
    },
    {
        aliases = {"sa6", "sa-6", "06"},
        group = {
            disposition = { h= 7, w= 7},
            units = {
                {"Kub 1S91 str", cell = 25}, {"Kub 2P25 ln", cell = 4, hdg = 180}, {"Kub 2P25 ln", cell = 22, hdg = 90}, {"Kub 2P25 ln", cell = 28, hdg = 270}, {"Kub 2P25 ln", cell = 46, hdg = 0},
                {"ZSU-23-4 Shilka", number = {min=1, max=2}, random=true},
                {"ATZ-5", random=true},
                {"ZIL-135", number = {min = 1, max = 2}, random=true},
                {"Ural-375 PBU", random=true}
            },
            description = "SA-6 SAM site",
            groupName = "SA6"
        },
    },
    {
        aliases = {"sa8_squad"},
        group = {
            disposition = { h= 4, w= 4},
            units = {{"Osa 9A33 ln", random = true}, {"GAZ-66", random=true}},
            description = "Sa-8 SAM site",
            groupName = "SA8"
        },
    },
    {
        aliases = {"sa9_squad"},
        group = {
            disposition = { h= 4, w= 4},
            units = {{"Strela-1 9P31", random = true}, {"GAZ-66", random=true}},
            description = "Sa-9 SAM site",
            groupName = "SA9"
        },
    },
    {
        aliases = {"sa10", "s300", "bb"},
        group = {
            disposition = { h= 10, w= 13},
            units = {
                {"S-300PS 40B6MD sr", cell = 130},
                {"S-300PS 40B6M tr", cell = 7},
                {"S-300PS 5P85C ln", cell = 29},
                {"S-300PS 5P85D ln", cell = 37},
                {"S-300PS 5P85D ln", cell = 43},
                {"S-300PS 5P85C ln", cell = 49},
                {"S-300PS 5P85C ln", cell = 57},
                {"S-300PS 5P85D ln", cell = 61},
                {"S-300PS 5P85D ln", cell = 71},
                {"S-300PS 5P85C ln", cell = 73},
                {"S-300PS 64H6E sr", cell = 98},
                {"S-300PS 54K6 cp", cell = 118},
                {"ZSU-23-4 Shilka", number = {min=1, max=2}, random=true},
                {"2S6 Tunguska", random=true},
            },
            description = "S300 SAM site",
            groupName = "S300"
        },
    },
    {
        aliases = {"sa11", "sa-11", "sd"},
        group = {
            disposition = { h= 9, w= 9},
            units = {
                {"SA-11 Buk SR 9S18M1", cell = 42}, {"SA-11 Buk CC 9S470M1", cell = 39}, {"SA-11 Buk LN 9A310M1", cell = 1}, {"SA-11 Buk LN 9A310M1", cell = 5}, {"SA-11 Buk LN 9A310M1", cell = 9}, {"SA-11 Buk LN 9A310M1", cell = 72}, {"SA-11 Buk LN 9A310M1", cell = 76}, {"SA-11 Buk LN 9A310M1", cell = 81},
                {"ZSU-23-4 Shilka", number = {min=1, max=2}, random=true},
                {"ATZ-5", random=true},
                {"Ural-375", number = {min = 1, max = 2}, random=true},
            },
            description = "SA-11 SAM site",
            groupName = "SA11"
        },
    },
    {
        aliases = {"sa13_squad"},
        group = {
            disposition = { h= 4, w= 4},
            units = {{"Strela-10M3", random = true}, {"GAZ-66", random=true}},
            description = "Sa-13 SAM site",
            groupName = "SA13"
        },
    },
    {
        aliases = {"sa15_squad"},
        group = {
            disposition = { h= 4, w= 4},
            units = {{"Tor 9A331", random = true}, {"GAZ-66", random=true}},
            description = "Sa-15 SAM site",
            groupName = "SA15"
        },
    },
    {
        -- insurgent sa18 squad
        aliases = {"insurgent_manpad_squad", "ins_manpad"},
        group = {
            disposition = {h= 3, w= 3},
            units = {
                -- IglaS Command Unit
                {"MANPADS SA-18 Igla \"Grouse\" C2", random=true},
                -- IglaS
                {"Igla manpad INS", random=true},
                -- Troops
                {"Infantry AK Ins", number = {min=2, max=3}, random=true},
                -- Transport
                {"HL_DSHK", random=true}
            },
            description = "Insurgent Sa-18 Manpad Squad",
            groupName = "Insurgent Manpad Squad"
        },
    },
    {
        -- sa18 squad
        aliases = {"sa18_squad"},
        group = {
            disposition = {h= 3, w= 3},
            units = {
                -- IglaS Command Unit
                {"MANPADS SA-18 Igla \"Grouse\" C2", random=true},
                -- IglaS
                {"SA-18 Igla manpad", number = {min=1, max=2}, random=true},
                -- Troops
                {"Infantry AK", number = {min=2, max=4}, random=true},
                --Transport
                {"UAZ-469", random=true}
            },
            description = "Sa-18 Manpad Squad",
            groupName = "Red Manpad Squad"
        },
    },
    {
        -- sa18s squad
        aliases = {"sa18s_squad"},
        group = {
            disposition = {h= 4, w= 4},
            units = {
                -- IglaS Command Unit
                {"MANPADS SA-18 Igla-S \"Grouse\" C2", random=true},
                -- IglaS
                {"SA-18 Igla-S manpad", number = {min=1, max=2}, random=true},
                -- Troops
                {"Infantry AK ver2", number = {min=2, max=6}, random=true},
                --Transport
                {"Tigr_233036", random=true}
            },
            description = "Sa-18S Manpad Squad",
            groupName = "Red Modern Manpad Squad"
        },
    },
    {
        aliases = {"sa19_squad"},
        group = {
            disposition = { h= 4, w= 4},
            units = {{"2S6 Tunguska", random = true}, {"Ural-375", random=true}},
            description = "Sa-19 SAM site",
            groupName = "SA19"
        },
    },
    {
        -- red ewr position
        aliases = {"red_ewr", "ewr"},
        group = {
            disposition = {h= 7, w= 7},
            units = {
                -- Radar unit
                {"55G6 EWR", cell = 25},
                -- IglaS Command Unit
                {"MANPADS SA-18 Igla \"Grouse\" C2", random=true},
                -- IglaS
                {"SA-18 Igla manpad", random=true},
                -- C2
                {"Ural-375 PBU", random=true}
            },
            description = "Red EWR",
            groupName = "Red EWR"
        },
    },
    --NATO 
    {
        aliases = {"rapier_optical", "rpo"},
        group = {
            disposition = { h= 5, w= 5},
            units = {
                {"rapier_fsa_optical_tracker_unit", cell = 9},
                {"rapier_fsa_optical_tracker_unit", cell = 17},
                {"rapier_fsa_launcher", cell = 1, hdg = 315},
                {"rapier_fsa_launcher", cell = 5, hdg = 45},
                {"rapier_fsa_launcher", cell = 21, hdg = 225},
                {"rapier_fsa_launcher", cell = 25, hdg = 135},

                --supply/crew truck
                {"Land_Rover_101_FC", number = {min=1, max = 2}, random = true},
            },
            description = "Rapier SAM site",
            groupName = "Rapier"
        },
    },
    {
        aliases = {"rapier_radar", "rpr"},
        group = {
            disposition = { h= 5, w= 5},
            units = {
                {"rapier_fsa_blindfire_radar", cell = 13},
                {"rapier_fsa_optical_tracker_unit", cell = 9},
                {"rapier_fsa_optical_tracker_unit", cell = 17},
                {"rapier_fsa_launcher", cell = 1, hdg = 315},
                {"rapier_fsa_launcher", cell = 5, hdg = 45},
                {"rapier_fsa_launcher", cell = 21, hdg = 225},
                {"rapier_fsa_launcher", cell = 25, hdg = 135},

                --supply/crew truck
                {"Land_Rover_101_FC", number = {min=1, max = 2}, random = true},
            },
            description = "Rapier SAM site with radar",
            groupName = "Rapier-radar"
        },
    },
    {
        -- Stinger Squad
        aliases = {"stinger_squad"},
        group = {
            disposition = {h= 4, w= 4},
            units = {
                -- Stinger Command Unit
                {"MANPADS Stinger C2", random=true}, {"MANPADS Stinger C2", random=true},
                -- Stinger
                {"Soldier stinger", random=true}, {"Soldier stinger", random=true},
                -- Troops
                {"Soldier M4 GRG", number = {min=3, max=4}, random=true},
                --Transport
                {"Hummer", random=true}, {"Hummer", random=true}
            },
            description = "Stinger Manpad Squad",
            groupName = "Blue Manpad Squad"
        },
    },
    {
        aliases = {"avenger_squad"},
        group = {
            disposition = { h= 4, w= 4},
            units = {{"M1097 Avenger", random = true}, {"M 818", random=true}},
            description = "Avenger SAM site",
            groupName = "Avenger"
        },
    },
    {
        aliases = {"roland"},
        group = {
            disposition = { h= 5, w= 5},
            units = {{"Roland Radar", cell = 8}, {"Roland ADS", cell = 1 , hdg = 300}, {"Roland ADS", cell = 5, hdg = 60}, {"Roland ADS", cell = 23, hdg = 180}, {"M 818", random=true}},
            description = "Roland SAM site",
            groupName = "Roland"
        },
    },
    {
        aliases = {"roland-noew"},
        group = {
            disposition = { h= 5, w= 5},
            units = {{"Roland ADS", cell = 1 , hdg = 300}, {"Roland ADS", cell = 5, hdg = 60}, {"Roland ADS", cell = 23, hdg = 180}, {"M 818", random=true}},
            description = "Roland SAM site",
            groupName = "Roland"
        },
    },
    {
        -- NASAMS SHORAD system with 120C
        aliases = {"nasams_c", "nasams", "NASAMS", "NASAMS_C"},
        group = {
            disposition = { h= 7, w= 11},
            units = {
                -- Search radar unit
                {"NASAMS_Radar_MPQ64F1", cell = 41},
                -- launchers
                {"NASAMS_LN_C", hdg = 315, cell = 4}, {"NASAMS_LN_C", hdg = 45, cell = 11}, {"NASAMS_LN_C", hdg = 225, cell = 70}, {"NASAMS_LN_C", hdg = 135, cell = 77},
                -- C2
                {"NASAMS_Command_Post", cell = 34},
                -- a supply truck or three
                {"M 818", number = {min=1, max=3}, random=true},
                --IR defense
                {"M1097 Avenger", number = {min=1, max=2}, random=true}
            },
            description = "NASAMS C battery",
            groupName = "NASAMS C battery"
        },
    },
    {
        -- NASAMS SHORAD system with 120B
        aliases = {"nasams_b", "NASAMS_B"},
        group = {
            disposition = { h= 7, w= 11},
            units = {
                -- Search radar unit
                {"NASAMS_Radar_MPQ64F1", cell = 41},
                -- launchers
                {"NASAMS_LN_B", hdg = 315, cell = 4}, {"NASAMS_LN_B", hdg = 45, cell = 11}, {"NASAMS_LN_B", hdg = 225, cell = 70}, {"NASAMS_LN_B", hdg = 135, cell = 77},
                -- C2
                {"NASAMS_Command_Post", cell = 34},
                -- a supply truck or three
                {"M 818", number = {min=1, max=3}, random=true},
                --IR defense
                {"M1097 Avenger", number = {min=1, max=2}, random=true}
            },
            description = "NASAMS B battery",
            groupName = "NASAMS B battery"
        },
    },
    {
        aliases = {"hawk", "ha", "mim-23"},
        group = {
            disposition = { h= 40, w= 40},
            units = {
                {"Hawk sr", cell = 816},
                {"Hawk cwar", cell = 826},
                {"Hawk cwar", cell = 1100},

                {"Hawk tr", cell = 381, hdg = 0},
                {"Hawk tr", cell = 1208, hdg = 240},
                {"Hawk tr", cell = 1231, hdg = 120},

                {"Hawk pcp", cell = 941},

                {"Hawk ln", cell = 22, hdg = 0}, {"Hawk ln", cell = 297, hdg = 300 }, {"Hawk ln", cell = 306, hdg = 60},
                {"Hawk ln", cell = 1042, hdg = 300}, {"Hawk ln", cell = 1401, hdg = 240 }, {"Hawk ln", cell = 1568, hdg = 180},
                {"Hawk ln", cell = 1080, hdg = 60}, {"Hawk ln", cell = 1440, hdg = 120 }, {"Hawk ln", cell = 1588, hdg = 180},

                -- a supply truck or three
                {"M 818", number = {min=4, max=6}, random=true},
                --AAA defense
                {"Vulcan", number = {min=2, max=3}, random=true},
                --IR defense
                {"M1097 Avenger", random=true},
            },
            description = "Hawk SAM site",
            groupName = "Hawk"
        },
    },
    {
        aliases = {"patriot", "pa", "mim-104"},
        group = {
            disposition = { h= 7, w= 12},
            units = {
                {"Patriot str", cell = 66, hdg = 0},
                {"Patriot cp" , cell = 78},
                {"Patriot AMG", cell = 79},
                {"Patriot ECS", cell = 67},
                {"Patriot EPP", cell = 68},

                {"Patriot ln", cell = 1, hdg = 280},
                {"Patriot ln", cell = 2, hdg = 300},

                {"Patriot ln", cell = 28, hdg = 20},
                {"Patriot ln", cell = 29, hdg = 40},
                {"Patriot ln", cell = 32, hdg = 330},
                {"Patriot ln", cell = 33, hdg = 310},

                {"Patriot ln", cell = 11, hdg = 60},
                {"Patriot ln", cell = 12, hdg = 80},

                --supply trucks
                {"M 818", number = {min=2, max=5}, random=true},

                --AAA defense
                {"Vulcan", number = {min=0, max=1}, cell = 60},
                {"Vulcan", number = {min=0, max=1}, cell = 48},
                {"Vulcan", cell = 54},
            },
            description = "Patriot SAM site",
            groupName = "Patriot"
        },
    },
    {
        -- blue ewr position
        aliases = {"blue_ewr"},
        group = {
            disposition = {h= 7, w= 7},
            units = {
                -- Radar unit
                {"FPS-117 Dome", cell = 25},
                -- IR Defense
                {"M1097 Avenger", random=true}
            },
            description = "Blue EWR",
            groupName = "Blue EWR"
        },
    },
    --infantry
    {
        aliases = {"infantry section", "infsec"},
        group = {
            disposition = { h= 10, w= 4},
            units = {{"IFV BTR-80", cell=38, random=true},{"IFV BTR-80", cell=39, random=true},{"INF Soldier AK", number = {min=12, max=30}, random=true}, {"SA-18 Igla manpad", number = {min=0, max=2}, random=true}},
            description = "Mechanized infantry section with APCs",
            groupName = "Mechanized infantry section"
        },
    },
    {
        aliases = {"US infgroup"},
        group = {
            disposition = { h = 5, w = 5},
            units = {{"Hummer", number = {min=1, max=2}, random=true},{"Soldier M249", number = {min=1, max=2}, random=true},{"Soldier M4", number = {min=2, max=4}, random=true},{"Soldier M4 GRG", number = {min=6, max=15}, random=true}},
            description = "US infantry group",
            groupName = "US infantry group",
        },
    },
    --artillery
    {
        aliases = {"insurgent_arty"},
        group = {
            disposition = { h = 2, w = 4},
            units = { {"tt_B8M1", number = {min=1, max=2}}, {"HL_B8M1", number = {min=1, max=2}} },
            description = "Insurgent artillery battery",
            groupName = "Insurgent artillery battery",
        },
    },
    {
        aliases = {"mortar"},
        group = {
            disposition = { h = 2, w = 4},
            units = { {"2B11 mortar", number = 4} },
            description = "2B11 Mortar team",
            groupName = "2B11 Mortar team",
        },
    },
    {
        aliases = {"M-109"},
        group = {
            disposition = { h = 4, w = 4},
            units = { {"M-109", number = 8}, {"MLRS FDDM", number = 1}, {"M 818", number = 1} },
            description = "M-109 artillery battery",
            groupName = "M-109 artillery battery",
        },
    },
    {
        aliases = {"PLZ05"},
        group = {
            disposition = { h = 2, w = 3},
            units = { {"PLZ05", number = 3}, {"Grad_FDDM", number = 1}, {"Ural-375", number = 1} },
            description = "PLZ05 artillery battery",
            groupName = "PLZ05 artillery battery",
        },
    },
    {
        aliases = {"Msta"},
        group = {
            disposition = { h = 2, w = 3},
            units = { {"SAU Msta", number = 3}, {"Grad_FDDM", number = 1}, {"Ural-375", number = 1} },
            description = "Msta artillery battery",
            groupName = "Msta artillery battery",
        },
    },
    {
        aliases = {"MLRS"},
        group = {
            disposition = { h = 2, w = 4},
            units = { {"MLRS", number = 4}, {"MLRS FDDM", number = 1}, {"M 818", number = 1} },
            description = "M270 MLRS artillery battery",
            groupName = "M270 MLRS artillery battery",
        },
    },
    {
        aliases = {"SmerchCM"},
        group = {
            disposition = { h = 2, w = 4},
            units = { {"Smerch", number = 4}, {"Grad_FDDM", number = 1}, {"ZIL-135", number = 2} },
            description = "Smerch (CM) MLRS artillery battery",
            groupName = "Smerch (CM) MLRS artillery battery",
        },
    },
    {
        aliases = {"SmerchHE"},
        group = {
            disposition = { h = 2, w = 4},
            units = { {"Smerch_HE", number = 4}, {"Grad_FDDM", number = 1}, {"ZIL-135", number = 2} },
            description = "Smerch (HE) MLRS artillery battery",
            groupName = "Smerch (HE) MLRS artillery battery",
        },
    },
    {
        aliases = {"Uragan"},
        group = {
            disposition = { h = 2, w = 4},
            units = { {"Uragan_BM-27", number = 4}, {"Grad_FDDM", number = 1}, {"ZIL-135", number = 2} },
            description = "Uragan MLRS artillery battery",
            groupName = "Uragan MLRS artillery battery",
        },
    },
    {
        aliases = {"Grad"},
        group = {
            disposition = { h = 2, w = 4},
            units = { {"Grad-URAL", number = 4}, {"Grad_FDDM", number = 1}, {"Ural-375", number = 2} },
            description = "Grad MLRS artillery battery",
            groupName = "Grad MLRS artillery battery",
        },
    },
    --convoys
    {
        aliases = {"US supply convoy","blueconvoy"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"Hummer", number = {min=2, max=4}, random=true},
                {"Truck M 818", number = {min=3, max=6}, random=true},
                {"Truck M978 HEMTT Tanker", number = {min=0, max=3}, random=true},
                {"Truck Predator GCS", number = {min=0, max=2}, random=true},
                {"Truck Predator TrojanSpirit", number = {min=0, max=2}, random=true}
            },
            description = "US supply convoy",
            groupName = "US supply convoy",
        },
    },
    {
        aliases = {"RU supply convoy with defense","redconvoy-def"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"2S6 Tunguska", number = {min=0, max=1}, random=true},
                {"Strela-10M3", number = {min=0, max=1}, random=true},
                {"Strela-1 9P31", number = {min=0, max=1}, random=true},
                {"ZSU-23-4 Shilka", number = {min=0, max=2}, random=true},
                {"ZSU_57_2", number = {min=0, max=1}, random=true},
                {"S-60_Type59_Artillery", number = {min=0, max=1}, random=true},
                {"Ural-375 ZU-23", number = {min=0, max=2}, random=true},
                {"UAZ-469", number = {min=2, max=4}, random=true},
                {"Truck SKP-11", number = {min=1, max=3}, random=true},
                {"Ural-375 PBU", number = {min=1, max=3}, random=true},
                {"Truck Ural-375", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320 APA-5D", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320-31", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320T", number = {min=1, max=3}, random=true},
                {"Truck ZiL-131 APA-80", number = {min=1, max=3}, random=true},
                {"Truck ZIL-131 KUNG", number = {min=1, max=3}, random=true}
            },
            description = "RU supply convoy with defense",
            groupName = "RU supply convoy with defense",
        },
    },
    {
        aliases = {"RU supply convoy with light defense","redconvoy-lightdef"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"ZSU-23-4 Shilka", number = {min=0, max=2}, random=true},
                {"ZSU_57_2", number = {min=0, max=1}, random=true},
                {"S-60_Type59_Artillery", number = {min=0, max=1}, random=true},
                {"Ural-375 ZU-23", number = {min=0, max=2}, random=true},
                {"UAZ-469", number = {min=2, max=4}, random=true},
                {"Truck SKP-11", number = {min=1, max=3}, random=true},
                {"Ural-375 PBU", number = {min=1, max=3}, random=true},
                {"Truck Ural-375", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320 APA-5D", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320-31", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320T", number = {min=1, max=3}, random=true},
                {"Truck ZiL-131 APA-80", number = {min=1, max=3}, random=true},
                {"Truck ZIL-131 KUNG", number = {min=1, max=3}, random=true}
            },
            description = "RU supply convoy with light defense",
            groupName = "RU supply convoy with light defense",
        },
    },
    {
        aliases = {"RU supply convoy with no defense","redconvoy-nodef"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"UAZ-469", number = {min=2, max=4}, random=true},
                {"Truck SKP-11", number = {min=1, max=3}, random=true},
                {"Ural-375 PBU", number = {min=1, max=3}, random=true},
                {"Truck Ural-375", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320 APA-5D", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320-31", number = {min=1, max=3}, random=true},
                {"Truck Ural-4320T", number = {min=1, max=3}, random=true},
                {"Truck ZiL-131 APA-80", number = {min=1, max=3}, random=true},
                {"Truck ZIL-131 KUNG", number = {min=1, max=3}, random=true}
            },
            description = "RU supply convoy with no defense",
            groupName = "RU supply convoy with no defense",
        },
    },
    {
        aliases = {"RU small supply convoy with defense","redsmallconvoy-def"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"2S6 Tunguska", number = {min=0, max=1}, random=true},
                {"Strela-10M3", number = {min=0, max=1}, random=true},
                {"Strela-1 9P31", number = {min=0, max=1}, random=true},
                {"ZSU-23-4 Shilka", number = {min=0, max=2}, random=true},
                {"ZSU_57_2", number = {min=0, max=1}, random=true},
                {"S-60_Type59_Artillery", number = {min=0, max=1}, random=true},
                {"Ural-375 ZU-23", number = {min=0, max=2}, random=true},
                {"UAZ-469", number = {min=1, max=2}, random=true},
                {"Truck SKP-11", number = {min=1, max=2}, random=true},
                {"Ural-375 PBU", number = {min=0, max=2}, random=true},
                {"Truck Ural-375", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320 APA-5D", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320-31", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320T", number = {min=0, max=2}, random=true},
                {"Truck ZiL-131 APA-80", number = {min=0, max=2}, random=true},
                {"Truck ZIL-131 KUNG", number = {min=0, max=2}, random=true}
            },
            description = "RU small supply convoy with defense",
            groupName = "RU small supply convoy with defense",
        },
    },
    {
        aliases = {"RU small supply convoy with light defense","redsmallconvoy-lightdef"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"ZSU-23-4 Shilka", number = {min=0, max=2}, random=true},
                {"ZSU_57_2", number = {min=0, max=1}, random=true},
                {"S-60_Type59_Artillery", number = {min=0, max=1}, random=true},
                {"Ural-375 ZU-23", number = {min=0, max=2}, random=true},
                {"UAZ-469", number = {min=1, max=2}, random=true},
                {"Truck SKP-11", number = {min=1, max=2}, random=true},
                {"Ural-375 PBU", number = {min=0, max=2}, random=true},
                {"Truck Ural-375", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320 APA-5D", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320-31", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320T", number = {min=0, max=2}, random=true},
                {"Truck ZiL-131 APA-80", number = {min=0, max=2}, random=true},
                {"Truck ZIL-131 KUNG", number = {min=0, max=2}, random=true}
            },
            description = "RU small supply convoy with light defense",
            groupName = "RU small supply convoy with light defense",
        },
    },
    {
        aliases = {"RU small supply convoy with no defense","redsmallconvoy-nodef","redconvoy","convoy"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"UAZ-469", number = {min=1, max=2}, random=true},
                {"Truck SKP-11", number = {min=1, max=2}, random=true},
                {"Ural-375 PBU", number = {min=0, max=2}, random=true},
                {"Truck Ural-375", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320 APA-5D", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320-31", number = {min=0, max=2}, random=true},
                {"Truck Ural-4320T", number = {min=0, max=2}, random=true},
                {"Truck ZiL-131 APA-80", number = {min=0, max=2}, random=true},
                {"Truck ZIL-131 KUNG", number = {min=0, max=2}, random=true}
            },
            description = "RU small supply convoy with no defense",
            groupName = "RU small supply convoy with no defense",
        },
    },
    {
        -- High value offensive convoy potentially defended by Sa-15, Sa-19, Sa-13 and armor
        aliases = {"hv_convoy_red"},
        group = {
            disposition = { h= 4, w= 4},
            units = {
                --Radar defense
                {"Tor 9A331", number = {min=0, max=1}, random=true, hdg=0},
                {"2S6 Tunguska", number = {min=0, max=1}, random=true, hdg=0},

                -- scud units
                {"Scud_B", number = {min=0, max=2}, random=true, hdg=0},

                -- armor
                {"T-72B3", random=true, hdg=0},
                {"BTR-82A", random=true, hdg=0},

                --supply truck and C2
                {"ZIL-135", random=true, hdg=0},
                {"Tigr_233036", random=true, hdg=0},

                --IR defense
                {"Strela-10M3", number = {min=0, max=1}, random=true, hdg=0}
            },
            description = "High Value Attack convoy red",
            groupName =  "High Value Attack convoy red"
       },
    },
    {
        -- Offensive convoy potentially defended by Sa-15, Shilka, Sa-13 and armor
        aliases = {"attack_convoy_red"},
        group = {
            disposition = { h= 4, w= 4},
            units = {
                --Radar defense
                {"Tor 9A331", number = {min=0, max=1}, random=true},
                {"ZSU-23-4 Shilka", random=true},

                -- armor
                {"T-72B3", random=true},
                {"BTR-82A", random=true},

                --supply truck
                {"ZIL-135", random=true},

                --IR defense
                {"Strela-10M3", random=true}
            },
            description = "Attack convoy red",
            groupName =  "Attack convoy red"
       },
    },
    {
        -- Quick reaction convoy potentially defended by Roland and armor
        aliases = {"QRC_red"},
        group = {
            disposition = { h= 4, w= 4},
            units = {
                --Radar defense
                {"Roland ADS", number = {min=0, max=1}, random=true},

                -- armor
                {"BTR-82A", number = {min=0, max=1}, random=true},
                {"BTR-82A", random=true},

                --ATGM
                {"VAB_Mephisto", random=true},
                {"VAB_Mephisto", random=true},
            },
            description = "Quick reaction convoy red",
            groupName =  "Quick reaction convoy red"
       },
    },
    {
        -- Offensive convoy potentially defended by Sa-15, Sa-19, Sa-13 and armor
        aliases = {"civilian_convoy_red"},
        group = {
            disposition = {h= 3, w= 3},
            units = {
                -- buses
                {"LAZ Bus", number = {min=1,max=3}, random=true},
                {"LiAZ Bus", number = {min=1,max=3}, random=true},
            },
            description = "Civilian convoy red",
            groupName =  "Civilian convoy red"
       },
    },
    {
        -- Quick reaction convoy potentially defended by Roland and armor
        aliases = {"QRC_blue"},
        group = {
            disposition = { h= 4, w= 4},
            units = {
                --Radar defense
                {"Roland ADS", number = {min=0, max=1}, random=true},

                -- armor
                {"M1128 Stryker MGS", number = {min=0, max=1}, random=true},
                {"M1128 Stryker MGS", random=true},

                --ATGM
                {"M1134 Stryker ATGM", random=true},
                {"M1134 Stryker ATGM", random=true},
            },
            description = "Quick reaction convoy blue",
            groupName =  "Quick reaction convoy blue"
       },
    },
    --ships
    {
        aliases = {"cargoships-nodef", "cargoships"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"Dry-cargo ship-1", number = {min=1, max=3}, random=true, size=150},
                {"Dry-cargo ship-2", number = {min=1, max=3}, random=true, size=150},
                {"ELNYA", number = {min=1, max=3}, random=true, size=150}
            },
            description = "Cargo ships with no defense",
            groupName = "Cargo ships with no defense",
        },
    },
    {
        aliases = {"cargoships-escorted"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"Dry-cargo ship-1", number = {min=1, max=3}, random=true, size=150},
                {"Dry-cargo ship-2", number = {min=1, max=3}, random=true, size=150},
                {"ELNYA", number = {min=1, max=3}, random=true, size=150},
                {"MOLNIYA", number = {min=1, max=2}, random=true, size=150},
                {"ALBATROS", number = {min=1, max=2}, random=true, size=150},
                {"NEUSTRASH", number = {min=0, max=1}, random=true, size=150}
            },
            description = "Cargo ships with escort",
            groupName = "Cargo ships with escort",
        },
    },
    {
        aliases = {"combatships"},
        group = {
            disposition = { h = 20, w = 20},
            units = {
                {"MOLNIYA", number = {min=2, max=3}, random=true, size=150},
                {"ALBATROS", number = {min=2, max=3}, random=true, size=150},
                {"NEUSTRASH", number = {min=1, max=2}, random=true, size=150}
            },
            description = "Combat ships with possible FFG defense",
            groupName = "Combat ships",
        },
    },
    ---
    --- groups made for dynamic group spawning (veafCasMission.generateAirDefenseGroup)
    ---
    {
        aliases = {"generateAirDefenseGroup-BLUE-5"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- hawk battery
                {"Hawk sr", cell = 8}, {"Hawk pcp", cell = 13}, {"Hawk tr", cell = 15}, {"Hawk ln", cell = 1, hdg = 225}, {"Hawk ln", cell = 3, hdg = 0 }, {"Hawk ln", cell = 21, hdg = 135},
                -- Some M48 Chaparral
                {"M48 Chaparral", number = {min=2, max=4}, random=true},
                -- Some Gepards
                {"Gepard", number = {min=2, max=4}, random=true},
                -- a supply truck or three
                {"M 818", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-BLUE-5",
            groupName = "generateAirDefenseGroup-BLUE-5",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-BLUE-4"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- Roland battery
                {"Roland Radar", random=true}, {"Roland ADS", random=true, hdg = 0}, {"Roland ADS", random=true, hdg = 225}, {"Roland ADS", random=true, hdg = 135},
                -- Some M48 Chaparral
                {"M48 Chaparral", number = {min=2, max=4}, random=true},
                -- Some Gepards
                {"Gepard", number = {min=2, max=4}, random=true},
                -- a supply truck or three
                {"M 818", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-BLUE-4",
            groupName = "generateAirDefenseGroup-BLUE-4",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-BLUE-3"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- Some Gepards
                {"Gepard", number = {min=2, max=4}, random=true},
                -- M6 Linebacker battery
                {"M6 Linebacker", hdg = 0, random=true}, {"M6 Linebacker", hdg = 90, random=true}, {"M6 Linebacker", hdg = 180, random=true}, {"M6 Linebacker", hdg = 270, random=true},
                -- Some M1097 Avenger
                {"M1097 Avenger", number = {min=2, max=4}, random=true},
                -- a supply truck or three
                {"M 818", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-BLUE-3",
            groupName = "generateAirDefenseGroup-BLUE-3",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-BLUE-2"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- Some Vulcans
                {"Vulcan", number = {min=2, max=4}, random=true},
                -- Some M1097 Avenger
                {"M1097 Avenger", number = {min=2, max=4}, random=true},
                -- a supply truck or three
                {"M 818", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-BLUE-2",
            groupName = "generateAirDefenseGroup-BLUE-2",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-BLUE-1"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- Some Vulcans
                {"Vulcan", number = {min=1, max=3}, random=true},
                -- Some M1097 Avenger
                {"M1097 Avenger", number = {min=0, max=1}, random=true},
                -- a supply truck or three
                {"M 818", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-BLUE-1",
            groupName = "generateAirDefenseGroup-BLUE-1",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-BLUE-0"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- Some AAV7
                {"AAV7", number = {min=1, max=3}, random=true},
                -- a supply truck or three
                {"M 818", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-BLUE-0",
            groupName = "generateAirDefenseGroup-BLUE-0",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-RED-5"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- the search radar
                {"Dog Ear radar", random=true},
                -- Tor battery
                {"Tor 9A331", hdg = 180, number = 1, random=true},
                -- SA-8 battery                
                {"Osa 9A33 ln", number = {min=1, max=2}, random=true},
                -- Some SA13
                {"Strela-10M3", number = {min=1, max=2}, random=true},
                -- Some Tunguskas
                {"2S6 Tunguska", number = {min=1, max=2}, random=true},
                -- Some ZU-57-2
                {"ZSU_57_2", number = 1, random=true},
                -- Some S-60
                {"S-60_Type59_Artillery", number = 1, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-RED-5",
            groupName = "generateAirDefenseGroup-RED-5",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-RED-4"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- the search radar
                {"Dog Ear radar", random=true},
                -- SA-8 battery                
                {"Osa 9A33 ln", number = {min=1, max=2}, random=true},
                -- Some SA13
                {"Strela-10M3", number = {min=1, max=2}, random=true},
                -- Some Shilkas
                {"ZSU-23-4 Shilka", number = {min=1, max=2}, random=true},
                -- Some ZU-57-2
                {"ZSU_57_2", number = {min=1, max=1}, random=true},
                -- Some S-60
                {"S-60_Type59_Artillery", number = {min=0, max=1}, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-RED-4",
            groupName = "generateAirDefenseGroup-RED-4",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-RED-3"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- the search radar
                {"Dog Ear radar", random=true},
                -- SA13 battery
                {"Strela-10M3", number = {min=1, max=2}, random=true},
                -- Some SA9
                {"Strela-1 9P31", number = {min=1, max=2}, random=true},
                -- Some Shilkas
                {"ZSU-23-4 Shilka", number = {min=1, max=2}, random=true},
                -- Some ZU-57-2
                {"ZSU_57_2", number = {min=0, max=1}, random=true},
                -- Some S-60
                {"S-60_Type59_Artillery", number = {min=0, max=1}, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-RED-3",
            groupName = "generateAirDefenseGroup-RED-3",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-RED-2"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- the search radar
                {"Dog Ear radar", random=true},
                -- SA9 battery
                {"Strela-1 9P31", number = {min=1, max=2}, random=true},
                -- Some Shilkas
                {"ZSU-23-4 Shilka", number = {min=1, max=2}, random=true},
                -- Some ZU-57-2
                {"ZSU_57_2", number = {min=0, max=1}, random=true},
                -- Some S-60
                {"S-60_Type59_Artillery", number = {min=0, max=1}, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-RED-2",
            groupName = "generateAirDefenseGroup-RED-2",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-RED-1"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- the search radar
                {"Dog Ear radar", random=true},
                -- Some Shilkas
                {"ZSU-23-4 Shilka", number = {min=1, max=2}, random=true},
                -- Some ZU-57-2
                {"ZSU_57_2", number = {min=0, max=1}, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-RED-1",
            groupName = "generateAirDefenseGroup-RED-1",
        },
    },
    {
        aliases = {"generateAirDefenseGroup-RED-0"},
        hidden = true,
        group = {
            disposition = { h= 7, w= 7},
            units = {
                -- Some Ural-375 ZU-23
                {"Ural-375 ZU-23", number = {min=0, max=1}, random=true},
                -- Some S-60
                {"S-60_Type59_Artillery", number = {min=0, max=1}, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true}
            },
            description = "generateAirDefenseGroup-RED-0",
            groupName = "generateAirDefenseGroup-RED-0",
        },
    },
    ---
    --- Seemingly realistic russian air defense batteries
    ---
    {
        aliases = {"RU-SAM-Shilka-Battery", "shilka-battery"},
        group = {
            disposition = { h= 5, w= 5},
            units = {
                -- the search radar
                {"Dog Ear radar", cell = 13},
                -- the actual air defense units
                {"ZSU-23-4 Shilka", hdg = 0, random=true}, {"ZSU-23-4 Shilka", hdg = 90, random=true}, {"ZSU-23-4 Shilka", hdg = 180, random=true}, {"ZSU-23-4 Shilka", hdg = 270, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true},
            },
            description = "ZSU-23-4 battery",
            groupName = "ZSU-23-4 battery"
        },
    },
    {
        aliases = {"RU-SAM-S60-Battery", "s60-battery"},
        group = {
            disposition = { h= 5, w= 5},
            units = {
                -- the search radar
                {"Dog Ear radar", cell = 13},
                -- the actual air defense units
                {"S-60_Type59_Artillery", hdg = 0, random=true}, {"S-60_Type59_Artillery", hdg = 90, random=true}, {"S-60_Type59_Artillery", hdg = 180, random=true}, {"S-60_Type59_Artillery", hdg = 270, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true}
            },
            description = "S-60 battery",
            groupName = "S-60 battery"
        },
    },
    {
        aliases = {"RU-SAM-SA9-Battery", "sa9-battery"},
        group = {
            disposition = { h= 5, w= 5},
            units = {
                -- the search radar
                {"Dog Ear radar", cell = 13},
                -- the actual air defense units
                {"Strela-1 9P31", hdg = 0, random=true}, {"Strela-1 9P31", hdg = 90, random=true}, {"Strela-1 9P31", hdg = 180, random=true}, {"Strela-1 9P31", hdg = 270, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true}
            },
            description = "SA-9 battery",
            groupName = "SA-9 battery"
        },
    },
    {
        aliases = {"RU-SAM-SA13-Battery", "sa13-battery"},
        group = {
            disposition = { h= 5, w= 5},
            units = {
                -- the search radar
                {"Dog Ear radar", cell = 13},
                -- the actual air defense units
                {"Strela-10M3", hdg = 0, random=true}, {"Strela-10M3", hdg = 90, random=true}, {"Strela-10M3", hdg = 180, random=true}, {"Strela-10M3", hdg = 270, random=true},
                -- a supply truck or three
                {"Ural-4320-31", number = {min=1, max=3}, random=true},
            },
            description = "SA-13 battery",
            groupName = "SA-13 battery"
        },
    },
    {
        aliases = {"US-refuel", "refuel-group"},
        group = {
            disposition = { h= 1, w= 4},
            units = {
                {"M978 HEMTT Tanker", cell = 1, fitToUnit},
                {"M1043 HMMWV Armament", cell = 2, fitToUnit},
                {"Truck M939 Heavy", cell = 3, fitToUnit},
                {"Diesel Power Station 5I57A", cell = 4, fitToUnit},
            },
            description = "US refuel group",
            groupName = "US refuel group"
        },
    },
}

veaf.loggers.get(veafUnits.Id):info(string.format("Loading version %s", veafUnits.Version))

if veafUnits.OutputListsForDocumentation then
    veafUnits.logGroupsListInMarkdown()
    veafUnits.logUnitsListInMarkdown()
end

------------------ END script veafUnits.lua  ------------------


------------------ START script veafGroundAI.lua  ------------------

------------------------------------------------------------------
-- VEAF Ground AI (a.k.a. Slightly Less Dumb Ground AI) for DCS World
-- By Zip (2024-25)
--
-- Features:
-- ---------
-- * DCS groups can be managed by the mission maker (API calls, radio menus) and by the pilots (radio menus, markers, remote commands)
--
-- See the documentation : https://veaf.github.io/documentation/mission-maker/groundAI.html
------------------------------------------------------------------

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global settings. Stores the script constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafGroundAI = {}

--- Identifier. All output in the log will start with this.
veafGroundAI.Id = "GROUNDAI - "

--- Version.
veafGroundAI.Version = "1.0.0"

-- trace level, specific to this module
--veafGroundAI.LogLevel = "trace"

--- Key phrase to look for in the mark text which triggers the spawn command.
veafGroundAI.MarkerKeyphrase = "_ground"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Do not change anything below unless you know what you are doing!
-------------------------------------------------------------------------------------------------------------------------------------------------------------

veaf.loggers.new(veafGroundAI.Id, veafGroundAI.LogLevel)

veafGroundAI.handlers = {}

veafGroundAI.WATCHDOG_DELAY = 1

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- GroundUnitHandler class
-------------------------------------------------------------------------------------------------------------------------------------------------------------

GroundUnitHandler = {}
GroundUnitHandler.CLASS_NAME = "GroundUnitHandler"

GroundUnitHandler.DEFAULT_MESSAGE_STOP = "Ground unit %s has stopped executing and awaiting orders."
GroundUnitHandler.DEFAULT_MESSAGE_START = "Ground unit %s is executing or awaiting orders."

function GroundUnitHandler.init(object)
  -- technical name (GroundUnitHandler instance name)
  object.name = nil
  -- draw the position and orders of the unit on screen
  object.draw = false
  -- player units (only they are concerned by the messages)
  object.playerUnitsNames = {}
  -- DCS group
  object.dcsGroup = nil
  -- orders for the ground unit
  object.orders = {}
  -- index of the currently executed order
  object.currentOrderIndex = 1
  -- silent means no message is emitted
  object.silent = false
  -- the drawing objects that has been used to draw the situation
  object.zoneDrawings = {}
  -- the scheduled state of the :check() function
  object.checkFunctionSchedule = nil
  -- status, from one of the GroundUnitHandler.STATUS_xxx constants
  object.status = GroundUnitHandler.STATUS_READY
  -- message when the ground unit starts executing orders
  object.messageStart = GroundUnitHandler.DEFAULT_MESSAGE_START
  -- event when the ground unit starts executing orders
  object.onStart = nil
  -- message when the ground unit stops executing orders
  object.messageStop = GroundUnitHandler.DEFAULT_MESSAGE_STOP
  -- event when the ground unit stops executing orders
  object.onStop = nil
end

function GroundUnitHandler.statusToString(status)
  if status == GroundUnitHandler.STATUS_READY then return "STATUS_READY" end
  if status == GroundUnitHandler.STATUS_ACTIVE then return "STATUS_ACTIVE" end
  if status == GroundUnitHandler.STATUS_OVER then return "STATUS_OVER" end
  return ""
end

GroundUnitHandler.STATUS_READY = 1
GroundUnitHandler.STATUS_ACTIVE = 2
GroundUnitHandler.STATUS_OVER = 4

function GroundUnitHandler:new(objectToCopy)
-- LOGGING DISABLED WHEN COMPILING(GroundUnitHandler.CLASS_NAME .. ":new()")
  local objectToCreate = objectToCopy or {} -- create object if user does not provide one
  setmetatable(objectToCreate, self)
  self.__index = self

  -- init the new object
  GroundUnitHandler.init(objectToCreate)

  return objectToCreate
end

-- technical name (GroundUnitHandler instance name)
function GroundUnitHandler:setName(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[]:setName(%s)", veaf.p(value))
  self.name = value
  return veafGroundAI.add(self) -- add the handler to the list as soon as a name is available to index it
end

-- technical name (GroundUnitHandler instance name)
function GroundUnitHandler:getName()
  return self.name or self.description
end

-- description for the messages
function GroundUnitHandler:getDescription()
  local result = self:getName()
  if self:getDcsGroup() then
    result = result .. " is handling DCS group " .. self:getDcsGroup():getName() .. ")"
  end
  return result
end

-- draw the position and orders of the unit on screen
function GroundUnitHandler:setDraw(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:setDraw(%s)", veaf.p(self:getName()), veaf.p(value))
  self.draw = value
  return self
end

-- draw the position and orders of the unit on screen
function GroundUnitHandler:getDraw()
  return self.draw
end

-- coalitions of the players (only human units from these coalitions will be monitored)
function GroundUnitHandler:setPlayerCoalitions(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:setPlayerCoalitions(%s)", veaf.p(self:getName()), veaf.p(value))
  self.playerCoalitions = value
  return self
end

-- player units (only they are concerned by the messages)
function GroundUnitHandler:setPlayerUnitsNames(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:setPlayerUnitsNames(%s)", veaf.p(self:getName()), veaf.p(value))
  self.playerUnitsNames = value
  return self
end

-- player units (only they are concerned by the messages)
function GroundUnitHandler:getPlayerUnitsNames()
  return self.playerUnitsNames
end

-- DCS group
function GroundUnitHandler:setDcsGroup(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:setDcsGroup(%s)", veaf.p(self:getName()), veaf.p(value))
  self.dcsGroup = value
  return self
end

-- DCS group
function GroundUnitHandler:getDcsGroup()
  return self.dcsGroup
end

-- current orders for the ground unit
function GroundUnitHandler:setOrders(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:setOrders(%s)", veaf.p(self:getName()), veaf.p(value))
  self.orders = value
  return self
end

-- orders for the ground unit
function GroundUnitHandler:addOrder(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:addOrder(%s)", veaf.p(self:getName()), veaf.p(value))
  if value then
    table.insert(self.orders, value)
  end
  return self
end

-- orders for the ground unit
function GroundUnitHandler:getOrders()
  return self.orders
end

-- orders for the ground unit
function GroundUnitHandler:clearOrders()
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:clearOrders()", veaf.p(self:getName()))
  self.orders = {}
  return self
end

-- get the current order
function GroundUnitHandler:getCurrentOrder()
  if self.orders then
    return self.orders[1]
  else
    return nil
  end
end

-- complete an order (pop it from the start of the list)
function GroundUnitHandler:completeOrder()
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:completeOrder()", veaf.p(self:getName()))
  if self.orders and #self.orders > 0 then
    table.remove(self.orders, 1)
  end
  return self
end

-- silent means no message is emitted
function GroundUnitHandler:setSilent(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:setSilent(%s)", veaf.p(self:getName()), veaf.p(value))
  self.silent = value
  return self
end

-- silent means no message is emitted
function GroundUnitHandler:getSilent()
  return self.silent
end

-- the drawing objects that has been used to draw the situation
function GroundUnitHandler:setZoneDrawings(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:setZoneDrawings(%s)", veaf.p(self:getName()), veaf.p(value))
  self.zoneDrawings = value
  return self
end

-- the drawing objects that has been used to draw the situation
function GroundUnitHandler:getZoneDrawings()
  return self.zoneDrawings
end

-- the scheduled state of the :check() function
function GroundUnitHandler:setCheckFunctionSchedule(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME.."[%s]:setCheckFunctionSchedule(%s)", veaf.p(self:getName()), veaf.p(value))
  self.checkFunctionSchedule = value
  return self
end

-- the scheduled state of the :check() function
function GroundUnitHandler:getCheckFunctionSchedule()
  return self.checkFunctionSchedule
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- METHODS

function GroundUnitHandler:handleOrder(order)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:handleOrder(%s)", veaf.p(self:getName()),veaf.p(order))
  -- do nothing clever, all is done in the inheriting classes
  self:completeOrder()
end

function GroundUnitHandler:check()
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME.."[%s]:check()", veaf.p(self:getName()))

  -- consider the orders in the orders list
  local currentOrder = self:getCurrentOrder()
  if currentOrder then
    -- do something with the order
    self:handleOrder(currentOrder)
  end

  -- reschedule the check function
  self:setCheckFunctionSchedule(mist.scheduleFunction(GroundUnitHandler.check, { self },
    timer.getTime() + veafGroundAI.WATCHDOG_DELAY))
end

function GroundUnitHandler:start()
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:start()", veaf.p(self:getName()))
  self.status = GroundUnitHandler.STATUS_ACTIVE
  if not self.silent then
    trigger.action.outText(string.format(self.messageStart, self:getName()), 10)
  end
  if self.onStart then
    self.onStart(self)
  end
  self:check()
end

function GroundUnitHandler:stop()
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:stop()", veaf.p(self:getName()))
  self.status = GroundUnitHandler.STATUS_READY
  if not self.silent then
    trigger.action.outText(string.format(self.messageStop, self:getName()), 10)
  end
  if self.onStop then
    self.onStop(self)
  end
  if self.checkFunctionSchedule then
    mist.removeFunction(self.checkFunctionSchedule)
    self.checkFunctionSchedule = nil
  end
  if self:getCheckFunctionSchedule() then
    mist.removeFunction(self:getCheckFunctionSchedule())
    self:setCheckFunctionSchedule(nil)
  end
end

function GroundUnitHandler:orderTextAnalysis(value)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:orderTextAnalysis(%s)", veaf.p(self:getName()), veaf.p(value))
  -- do nothing clever, all is done in the inheriting classes
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ArtilleryUnitHandler class
-------------------------------------------------------------------------------------------------------------------------------------------------------------

ArtilleryUnitHandler = GroundUnitHandler:new()
ArtilleryUnitHandler.CLASS_NAME = "ArtilleryUnitHandler"

-- fire for aim constants
ArtilleryUnitHandler.FIREFORAIM_SHELLS = 2
ArtilleryUnitHandler.FIREFORAIM_RADIUS = 10

-- fire for effect constants
ArtilleryUnitHandler.FIREFOREFFECT_SHELLS = 40
ArtilleryUnitHandler.FIREFOREFFECT_RADIUS = 100

ArtilleryUnitHandler.ORDER_STOP = 0
ArtilleryUnitHandler.ORDER_FIRE = 1
ArtilleryUnitHandler.ORDER_ADVANCE = 2

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- CTOR

function ArtilleryUnitHandler.init(object)
  -- status, from one of the ArtilleryUnitHandler.STATUS_xxx constants
  object.status = ArtilleryUnitHandler.STATUS_READY
end

function ArtilleryUnitHandler:new(objectToCopy)
-- LOGGING DISABLED WHEN COMPILING(ArtilleryUnitHandler.CLASS_NAME .. ":new()")
  local objectToCreate = objectToCopy or {} -- create object if user does not provide one
  setmetatable(objectToCreate, self)
  self.__index = self

  -- init the new object
  ArtilleryUnitHandler.init(objectToCreate)

  return objectToCreate
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- PROPERTIES

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- COMPUTED PROPERTIES

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- METHODS

function ArtilleryUnitHandler:orderTextAnalysis(text)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:orderTextAnalysis(%s)", veaf.p(self:getName()), veaf.p(text))

  -- analyze the string for an acceptable order
  ArtilleryUnitHandler.VERB_FIRE_FORAIM = 1
  ArtilleryUnitHandler.VERB_FIRE_FOREFFECT = 2

  -- Option parameters extracted from the mark text.
  local options = {}
  options.verb = ArtilleryUnitHandler.VERB_FIRE_FORAIM -- can be "aim", "fire"
  options.target = nil -- the coordinates of the target
  options.shells = nil -- the number of shells to fire
  options.radius = nil -- the precision of the shelling

  -- Check for correct keywords.
  if text:lower():find("aim") then
    options.verb = ArtilleryUnitHandler.VERB_FIRE_FORAIM
  elseif text:lower():find("fire") then
    options.verb = ArtilleryUnitHandler.VERB_FIRE_FOREFFECT
  else
    return nil
  end

  -- keywords are split by ";"
  local keywords = veaf.split(text, ";")

  for _, keyphrase in pairs(keywords) do
    -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
    local str = veaf.breakString(veaf.trim(keyphrase), " ")
    local key = str[1]
    local val = str[2] or ""

    if key:lower() == "target" then
      -- Set the target
-- LOGGING DISABLED WHEN COMPILING("Keyword target = %s", veaf.p(val))
      if veaf.computeLLFromString(val) then -- check target string validity
        options.target = val
      end
    end

    if key:lower() == "shells" then
      -- Set the number of shells
-- LOGGING DISABLED WHEN COMPILING("Keyword shells = %s", veaf.p(val))
      local nVal = veaf.getRandomizableNumeric(val)
-- LOGGING DISABLED WHEN COMPILING("shells = %s", veaf.p(nVal))
      options.shells = nVal
    end

    if key:lower() == "radius" then
      -- Set the radius of the shelling
-- LOGGING DISABLED WHEN COMPILING("Keyword radius = %s", veaf.p(val))
      local nVal = veaf.getRandomizableNumeric(val)
-- LOGGING DISABLED WHEN COMPILING("radius = %s", veaf.p(nVal))
      options.radius = nVal
    end
  end

  if options.verb == ArtilleryUnitHandler.VERB_FIRE_FORAIM then
    self:fireForAim(options.target, options.shells, options.radius)
  elseif options.verb == ArtilleryUnitHandler.VERB_FIRE_FOREFFECT then
    self:fireForEffect(options.target, options.shells, options.radius)
  end

  return options
end

-- give the artillery unit a fire for effect order
function ArtilleryUnitHandler:fireForAim(coordinates, shells, radius)
  if not shells then
    shells = ArtilleryUnitHandler.FIREFORAIM_SHELLS
  end
  if not radius then
    radius = ArtilleryUnitHandler.FIREFORAIM_RADIUS
  end
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:fireForAim(%s, %s, %s)", veaf.p(self:getName()), veaf.p(coordinates), veaf.p(shells), veaf.p(radius))
  -- check the parameters
  if not coordinates then
    veaf.loggers.get(veafGroundAI.Id):warn(self.CLASS_NAME .. "[%s]:fireForAim() : no target coordinates", veaf.p(self:getName()))
    if not self.silent then
      local message = string.format("%s cannot aim, no target coordinates provided", veaf.p(self:getName()))
      trigger.action.outText(message, 10)
    end
    return
  end
  self:fireAtCoordinates(coordinates, shells, radius)
end

-- give the artillery unit a fire for effect order
function ArtilleryUnitHandler:fireForEffect(coordinates, shells, radius)
  if not shells then
    shells = ArtilleryUnitHandler.FIREFOREFFECT_SHELLS
  end
  if not radius then
    radius = ArtilleryUnitHandler.FIREFOREFFECT_RADIUS
  end
  if not coordinates then
    coordinates = self._lastTarget
  end
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:fireForEffect(%s, %s)", veaf.p(self:getName()), veaf.p(shells), veaf.p(radius))
  if not coordinates then
    veaf.loggers.get(veafGroundAI.Id):warn(self.CLASS_NAME .. "[%s]:fireForEffect() : no previous target - cannot fire for effect", veaf.p(self:getName()))
    if not self.silent then
      local message = string.format("%s cannot fire for effect, no target coordinates provided and no previous target exist", veaf.p(self:getName()))
      trigger.action.outText(message, 10)
    end
    return
  end
  self:fireAtCoordinates(coordinates, shells, radius)
end

-- give the artillery unit a fire order
function ArtilleryUnitHandler:fireAtCoordinates(coordinates, shells, radius)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:fireAtCoordinates(%d, %s, %s)", veaf.p(self:getName()), veaf.p(shells), veaf.p(coordinates), veaf.p(radius))
  -- check the parameters
  if not shells then
    veaf.loggers.get(veafGroundAI.Id):warn(self.CLASS_NAME .. "[%s]:fireAtCoordinates() : shells is nil", veaf.p(self:getName()))
    return
  end
  if not coordinates then
    veaf.loggers.get(veafGroundAI.Id):warn(self.CLASS_NAME .. "[%s]:fireAtCoordinates() : coordinates is nil", veaf.p(self:getName()))
    return
  end
  if not radius then
    radius = ArtilleryUnitHandler.DEFAULT_FIRE_RADIUS
  end
  -- check if these are coordinates
  local target = nil
  if type(coordinates) == "table" then
    target = coordinates
  elseif type(coordinates) == "string" then
    local _lat, _lon = veaf.computeLLFromString(coordinates)
-- LOGGING DISABLED WHEN COMPILING(string.format("_lat=%s", veaf.p(_lat)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_lon=%s", veaf.p(_lon)))
    if _lat and _lon then
      target = coord.LLtoLO(_lat, _lon)
    else
      veaf.loggers.get(veafGroundAI.Id):warn(self.CLASS_NAME .. "[%s]:fireAtCoordinates() : coordinates are not valid: %s", veaf.p(self:getName()), veaf.p(coordinates))
    end
  end
  local order = { verb = ArtilleryUnitHandler.ORDER_FIRE, parameters = { shells = shells, target = target, radius = radius } }
  self:addOrder(order)
end

function ArtilleryUnitHandler:handleOrder(order)
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:handleOrder(%s)", veaf.p(self:getName()), veaf.p(order))
  if order.verb == ArtilleryUnitHandler.ORDER_FIRE then
    -- fire at the target
    local shells = order.parameters.shells
    local target = order.parameters.target
    local radius = order.parameters.radius
    if not target then
      veaf.loggers.get(veafGroundAI.Id):warn(self.CLASS_NAME .. "[%s]:handleOrder() : no target", veaf.p(self:getName()))
    else
      -- convert the target coordinates to UTM for the message
      local lat, lon, _ = coord.LOtoLL(target)
      local grid = coord.LLtoMGRS(lat, lon)
      local coordinates = grid.UTMZone .. ' ' .. grid.MGRSDigraph .. ' ' .. grid.Easting .. ' ' .. grid.Northing
      local message = string.format("%s is firing %d shells at %s with a %s m dispersion", veaf.p(self:getName()), veaf.p(shells), veaf.p(coordinates), veaf.p(radius))
      trigger.action.outText(message, 10)
-- LOGGING DISABLED WHEN COMPILING("ArtilleryUnitHandler[%s]:handleOrder() : firing %d shells at %s with a %s m dispersion", veaf.p(self:getName()), veaf.p(shells), veaf.p(coordinates), veaf.p(radius))
      -- fire the shells
      local fireParams = {
        x = target.x,
        y = target.z,
        zoneRadius = radius,
        expendQty = shells,
        expendQtyEnabled = true,
        counterbattaryRadius = 500,
      }
      local fire = { id = 'FireAtPoint', params = fireParams }
      self:getDcsGroup():getController():pushTask(fire)
      self._lastTarget = target
    end
  end
  self:completeOrder()
end

function ArtilleryUnitHandler:stop()
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:stop()", veaf.p(self:getName()))
  -- clear the group's orders queue
  self:getDcsGroup():getController():resetTask()
  return GroundUnitHandler.stop(self)
end

function ArtilleryUnitHandler:clearOrders()
-- LOGGING DISABLED WHEN COMPILING(self.CLASS_NAME .. "[%s]:clearOrders()", veaf.p(self:getName()))
  -- clear the group's orders queue
  self:getDcsGroup():getController():resetTask()
  return GroundUnitHandler.clearOrders(self)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event handler functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Function executed when a mark has changed. This happens when text is entered or changed.
function veafGroundAI.onEventMarkChange(eventPos, event)
  -- choose by default the coalition of the player who triggered the event
  local coa = coalition.side.BLUE
  if event.coalition == coalition.side.RED then
    coa = coalition.side.RED
  end

-- LOGGING DISABLED WHEN COMPILING(string.format("event.idx  = %s", veaf.p(event.idx)))

  if veafGroundAI.executeCommand(eventPos, event.text, coa, event.idx) then
    -- Delete old mark.
-- LOGGING DISABLED WHEN COMPILING(string.format("Removing mark # %d.", event.idx))
    trigger.action.removeMark(event.idx)
  end
end

function veafGroundAI.executeCommand(eventPos, eventText, eventCoalition, markId, bypassSecurity, spawnedGroups, route)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafGroundAI.executeCommand(eventText=[%s])", eventText))

  -- Check if marker has a text and contains an alias
  if eventText ~= nil then
    -- Analyse the mark point text and extract the keywords.
    local options = veafGroundAI.markTextAnalysis(eventPos, eventCoalition, eventText)
-- LOGGING DISABLED WHEN COMPILING(string.format("options = %s", veaf.p(options)))

    if options then
      -- do the magic
      if options.verb == veafGroundAI.VERB_SET then
-- LOGGING DISABLED WHEN COMPILING("options.verb == veafGroundAI.VERB_SET")
        local handlerName = options.name
        local group = options.group
        if group and handlerName then
-- LOGGING DISABLED WHEN COMPILING("group = %s", veaf.p(group))
          local handler = veafGroundAI.get(handlerName)
          if not handler then
            handler = ArtilleryUnitHandler:new():setName(handlerName)
          end
          if handler then
            handler:setDcsGroup(group)
            handler:start()
            return true
          end
        end
      elseif options.verb == veafGroundAI.VERB_UNSET then
-- LOGGING DISABLED WHEN COMPILING("options.verb == veafGroundAI.VERB_UNSET")
        local handlerName = options.name
        local handler = veafGroundAI.get(handlerName)
        if handler then
          handler:stop()
          veafGroundAI.remove(handler)
          return true
        end
      elseif options.verb == veafGroundAI.VERB_START then
-- LOGGING DISABLED WHEN COMPILING("options.verb == veafGroundAI.VERB_START")
        local handlerName = options.name
        local handler = veafGroundAI.get(handlerName)
        if handler then
          handler:start()
          return true
        end
      elseif options.verb == veafGroundAI.VERB_STOP then
-- LOGGING DISABLED WHEN COMPILING("options.verb == veafGroundAI.VERB_STOP")
        local handlerName = options.name
        local handler = veafGroundAI.get(handlerName)
        if handler then
          handler:stop()
          return true
        end
      elseif options.verb == veafGroundAI.VERB_CLEAR then
-- LOGGING DISABLED WHEN COMPILING("options.verb == veafGroundAI.VERB_CLEAR")
        local handlerName = options.name
        local handler = veafGroundAI.get(handlerName)
        if handler then
          handler:stop()
          handler:clearOrders()
          return true
        end
      elseif options.verb == veafGroundAI.VERB_STATUS then
-- LOGGING DISABLED WHEN COMPILING("options.verb == veafGroundAI.VERB_STATUS")
        local handlerName = options.name
        local handler = veafGroundAI.get(handlerName)
        if handler then
          trigger.action.outText(string.format("AI handler %s: %s", handlerName, handler:getDescription()), 10)
          return true
        end
      elseif options.verb == veafGroundAI.VERB_ORDER then
-- LOGGING DISABLED WHEN COMPILING("options.verb == veafGroundAI.VERB_ORDER")
        local handlerName = options.name
        local handler = veafGroundAI.get(handlerName)
        if handler then
          if handler:orderTextAnalysis(options.order) then
            return true
          end
        end
      end
    end
  end

  -- None of the keywords matched.
  return false
end

--- Extract keywords from mark text.
function veafGroundAI.markTextAnalysis(eventPos, eventCoalition, text)
-- LOGGING DISABLED WHEN COMPILING("veafGroundAI.markTextAnalysis(text=%s)", veaf.p(text))

  veafGroundAI.VERB_SET = 1
  veafGroundAI.VERB_UNSET = 2
  veafGroundAI.VERB_ORDER = 3
  veafGroundAI.VERB_START = 4
  veafGroundAI.VERB_STOP = 5
  veafGroundAI.VERB_CLEAR = 6
  veafGroundAI.VERB_STATUS = 7

  -- Option parameters extracted from the mark text.
  local options = {}
  options.verb = veafGroundAI.VERB_SET -- can be "set", "unset", "order", "start", "stop", "status"
  options.group = nil                  -- the DCS group that is concerned by "set" and "unset" verbs
  options.order = nil                  -- the order that is given by "order" verb
  options.name = nil                   -- the name of the handler that is concerned by all verbs

  -- Check for correct keywords.
  if text:lower():find(veafGroundAI.MarkerKeyphrase .. " set") then
    options.verb = veafGroundAI.VERB_SET
  elseif text:lower():find(veafGroundAI.MarkerKeyphrase .. " unset") then
    options.verb = veafGroundAI.VERB_UNSET
  elseif text:lower():find(veafGroundAI.MarkerKeyphrase .. " order") then
    options.verb = veafGroundAI.VERB_ORDER
  elseif text:lower():find(veafGroundAI.MarkerKeyphrase .. " start") then
    options.verb = veafGroundAI.VERB_START
  elseif text:lower():find(veafGroundAI.MarkerKeyphrase .. " stop") then
    options.verb = veafGroundAI.VERB_STOP
  elseif text:lower():find(veafGroundAI.MarkerKeyphrase .. " clear") then
    options.verb = veafGroundAI.VERB_CLEAR
  elseif text:lower():find(veafGroundAI.MarkerKeyphrase .. " status") then
    options.verb = veafGroundAI.VERB_STATUS
  else
    return nil
  end

  -- keywords are split by ","
  local keywords = veaf.split(text, ",")

  for _, keyphrase in pairs(keywords) do
    -- Split keyphrase by space. First one is the key and second, ... the parameter(s) until the next comma.
    local str = veaf.breakString(veaf.trim(keyphrase), " ")
    local key = str[1]
    local val = str[2] or ""

    if key:lower() == "groupname" then
      -- Set dcs group name.
-- LOGGING DISABLED WHEN COMPILING("Keyword groupname = %s", veaf.p(val))
      -- search for the DCS group
      options.group = Group.getByName(val)
    end

    if key:lower() == "name" then
      -- Set AI handler name.
-- LOGGING DISABLED WHEN COMPILING("Keyword name = %s", veaf.p(val))
      options.name = val
    end

    if key:lower() == "order" then
      -- Set order
-- LOGGING DISABLED WHEN COMPILING("Keyword order = %s", veaf.p(val))
      options.order = val
    end
  end

  -- check mandatory parameter "name" for all commands
  if not (options.name) then return nil end

  -- check mandatory parameter "groupname" for commands "set" and "unset"
  if ((options.verb == veafGroundAI.VERB_SET or options.verb == veafGroundAI.VERB_UNSET) and not (options.group)) then
    -- search for the nearest allied group
    local minDist = 999999
    local closestUnit = nil
    for _, unit in pairs(veaf.getUnitsOfCoalition(false, eventCoalition)) do
      local pos = unit:getPosition().p
      if pos then
        local name = unit:getName()
        local distanceFromCenter = ((pos.x - eventPos.x) ^ 2 + (pos.z - eventPos.z) ^ 2) ^ 0.5
-- LOGGING DISABLED WHEN COMPILING("name=%s; distanceFromCenter=%s", veaf.p(name), veaf.p(distanceFromCenter))
        if distanceFromCenter <= 250 then
          if distanceFromCenter < minDist then
            minDist = distanceFromCenter
            closestUnit = unit
          end
        end
      end
    end
    if closestUnit then
      options.group = closestUnit:getGroup()
    else
      return nil
    end
  end

  return options
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Global functions for the module
-------------------------------------------------------------------------------------------------------------------------------------------------------------

function veafGroundAI.add(handler)
-- LOGGING DISABLED WHEN COMPILING("veafGroundAI.add([%s])", veaf.p(handler:getName()))
  veafGroundAI.handlers[handler:getName():lower()] = handler
  return handler
end

function veafGroundAI.remove(handler)
-- LOGGING DISABLED WHEN COMPILING("veafGroundAI.remove([%s])", veaf.p(handler:getName()))
  veafGroundAI.handlers[handler:getName():lower()] = nil
end

function veafGroundAI.get(handlerName)
-- LOGGING DISABLED WHEN COMPILING("veafGroundAI.get([%s])", veaf.p(handlerName))
  local handler = veafGroundAI.handlers[handlerName:lower()]
  if handler then
-- LOGGING DISABLED WHEN COMPILING("handler found: %s", veaf.p(handler))
  end
  return handler
end

function veafGroundAI.initialize()
  veaf.loggers.get(veafGroundAI.Id):info("Initializing module")
  veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafGroundAI.onEventMarkChange)
end

------------------ END script veafGroundAI.lua  ------------------


------------------ START script veafWeather.lua  ------------------

------------------------------------------------------------------
-- VEAF weather information messages and markers
-- By Flogas (2024)
--
-- Features:
-- ---------
-- * Generation of weather messages and reports in different formats (METAR, ATIS)
-- * Generation of markers on the maps displaying the weather at the location
------------------------------------------------------------------
veafWeather = {}

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global module settings
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Identifier. All output in DCS.log will start with this.
veafWeather.Id = "WEATHER"

--- Version.
veafWeather.Version = "1.4.3"

-- trace level, specific to this module
veafWeather.LogLevel = "trace"
veaf.loggers.new(veafWeather.Id, veafWeather.LogLevel)

--- Key phrase to look for in the mark text which triggers the command.
veafWeather.Keyphrase = "_weather"

veafWeather.RadioMenuName = "WEATHER AND ATC"

veafWeather.RemoteCommandParser = "([[a-zA-Z0-9]+)%s?([^%s]*)%s?(.*)"

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Local constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------
local _dcsPresetDensity =
{
    -- {density, precipitation, visibility}
    ["Preset1"] = {2, false, nil}, -- LS1 -- FEW/SCT
    ["Preset2"] = {2, false, nil}, -- LS2 -- FEW/SCT
    ["Preset3"] = {3, false, nil}, -- HS1 -- SCT
    ["Preset4"] = {3, false, nil}, -- HS2 -- SCT
    ["Preset5"] = {3, false, nil}, -- S1 -- SCT
    ["Preset6"] = {4, false, nil}, -- S2 -- SCT/BKN
    ["Preset7"] = {3, false, nil}, -- S3 -- BKN
    ["Preset8"] = {4, false, nil}, -- HS3 -- SCT/BKN
    ["Preset9"] = {5, false, nil}, -- S4 -- BKN
    ["Preset10"] = {4, false, nil}, -- S5 -- SCT/BKN
    ["Preset11"] = {6, false, nil}, -- S6 -- BKN
    ["Preset12"] = {6, false, nil}, -- S7 -- BKN
    ["Preset13"] = {6, false, nil}, -- B1 -- BKN
    ["Preset14"] = {6, false, nil}, -- B2 -- BKN
    ["Preset15"] = {4, false, nil}, -- B3 -- SCT/BKN
    ["Preset16"] = {6, false, nil}, -- B4 -- BKN
    ["Preset17"] = {7, false, nil}, -- B5 -- BKN/OVC
    ["Preset18"] = {7, false, nil}, -- B6 -- BKN/OVC
    ["Preset19"] = {8, false, nil}, -- B7 -- OVC
    ["Preset20"] = {7, false, nil}, -- B8 -- BKN/OVC
    ["Preset21"] = {7, false, nil}, -- O1 -- BKN/OVC
    ["Preset22"] = {6, false, nil}, -- O2 -- BKN
    ["Preset23"] = {6, false, nil}, -- O3  -- BKN
    ["Preset24"] = {7, false, nil}, -- O4 -- BKN/OVC
    ["Preset25"] = {8, false, nil}, -- O5 -- OVC
    ["Preset26"] = {8, false, nil}, -- O6 -- OVC
    ["Preset27"] = {8, false, nil}, -- O7 -- OVC
    ["RainyPreset1"] = {8, true, 4000}, -- OR1 -- OVC
    ["RainyPreset2"] = {7, true, 3000}, -- OR2 -- BKN/OVC
    ["RainyPreset3"] = {8, true, 4000}, -- OR3 -- OVC
    ["RainyPreset4"] = {4, true, nil}, -- LR1 -- SCT/BKN
    ["RainyPreset5"] = {7, true, nil}, -- LR2 -- BKN/OVC
    ["RainyPreset6"] = {8, true, nil}, -- LR3 -- OVC
    ["NEWRAINPRESET4"] = {8, true, nil}, -- LR4 -- OVC
}

local _cloudDensity = { Clear = 0, Few = 1, Scattered = 2, Broken = 3, Overcast = 4 }
local _cloudDensityOktas =
{
    [0] = _cloudDensity.Clear,
    [1] = _cloudDensity.Few,
    [2] = _cloudDensity.Few,
    [3] = _cloudDensity.Scattered,
    [4] = _cloudDensity.Scattered,
    [5] = _cloudDensity.Broken,
    [6] = _cloudDensity.Broken,
    [7] = _cloudDensity.Overcast,
    [8] = _cloudDensity.Overcast
}

local _nKelvinToCelciusOffset = -273.15

local _visibilityAffect = { None = 0, Fog = 1, Mist = 2, Haze = 3 }
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Local tools
-------------------------------------------------------------------------------------------------------------------------------------------------------------
local function _computeClearSkyHumidity(nLatitude, nLongitude, iDayOfYear)
    -- Base RH for clear skies
    local baseRh = 30
    
    -- Seasonal adjustment
    -- Calculate seasonal factor (-1 to 1, peaks at middle of year)
    local seasonal_factor = math.cos((iDayOfYear - 182) * 2 * math.pi / 365)
    
    -- Latitude adjustment
    -- Higher latitudes tend to have lower RH in winter, higher in summer
    local lat_factor = math.abs(nLatitude) / 90
    
    -- Longitude adjustment (crude estimation of continental vs maritime)
    -- Assume areas around 0°, 180° longitude (typical ocean areas) have higher RH
    local long_factor = math.min(math.abs(nLongitude), math.abs(nLongitude - 180)) / 180
    local maritime_influence = 1 - long_factor
    
    -- Combine factors
    local seasonal_adjustment = seasonal_factor * 15 * lat_factor -- ±15% variation
    local maritime_adjustment = maritime_influence * 10 -- Up to +10% for maritime areas
    
    -- Calculate final RH
    local adjustedRh = baseRh + seasonal_adjustment + maritime_adjustment
    
    -- Clamp between reasonable values
    return math.max(20, math.min(70, adjustedRh))
end

local function _computeHumidity(vec3, iCloudBaseMeters, iVisibilityMeters, bPrecipitations, iAbsTime)
    local nLatitude, nLongitude, _ = coord.LOtoLL(vec3)
    local nHumidity
    
    if (iCloudBaseMeters == nil or iCloudBaseMeters > 10000) then
        -- Clear skies - estimate RH based on location and date
        local dateTime = veafTime.getMissionDateTime(iAbsTime)
        nHumidity = _computeClearSkyHumidity(nLatitude, nLongitude, dateTime.yday)
    else
        -- Convert cloud base to meters and estimate RH
        nHumidity = 100 - (iCloudBaseMeters / 100)
        -- Clamp RH between 0 and 100%
        nHumidity = math.max(0, math.min(100, nHumidity))
    end
    
    if (iVisibilityMeters < 1000) then
        nHumidity = 100  -- Fog implies saturation
    elseif (iVisibilityMeters < 5000) then
        nHumidity = math.max(nHumidity, 90)  -- At least 90% RH with any fog
    elseif (iVisibilityMeters < 10000) then
        -- Increase RH as visibility decreases
        local nVisibilityFactor = math.max(0, (10000 - iVisibilityMeters) / 10000)
        nHumidity = nHumidity + (nVisibilityFactor * 20)  -- Up to +20% for low visibility
    end
    
    -- Precipitation adjustments
    if (bPrecipitations) then
        nHumidity = math.max(nHumidity, 80)
    end
    
    -- Clamp final value
    return math.max(0, math.min(100, nHumidity))
end

local function _computeDewpoint(nTemperatureCelcius, nQnhPa, nHumidity)
    local nQnhHpa = nQnhPa / 100

    -- Constants for Magnus formula
    local a = 17.27
    local b = 237.7
    
    -- Calculate gamma term
    local gamma = ((a * nTemperatureCelcius) / (b + nTemperatureCelcius)) + math.log(nHumidity/100.0)
    
    -- Calculate dew point using Magnus formula
    local nDewPointCelcius = (b * gamma) / (a - gamma)
    
    -- Apply pressure correction (approximate)
    local pressure_correction = (1013.25 - nQnhHpa) * 0.0012
    nDewPointCelcius = nDewPointCelcius + pressure_correction
    
    nDewPointCelcius = math.min(nDewPointCelcius, nTemperatureCelcius)
    -- Round to one decimal place
    return math.floor(nDewPointCelcius * 10 + 0.5) / 10
end

local function _weatherSliceAtAltitude(vec3, iAltitudeMeters)
    local nTemperatureKelvin, nPressurePa = atmosphere.getTemperatureAndPressure({ x = vec3.x, y = iAltitudeMeters, z = vec3.z })
    local iWindDir, iWindSpeedMps = weathermark._GetWind(vec3, iAltitudeMeters)

    return
    {
        AltitudeMeters = iAltitudeMeters,
        PressureHpa = nPressurePa / 100,
        TemperatureCelcius = nTemperatureKelvin + _nKelvinToCelciusOffset,
        WindDirection = iWindDir,
        WindSpeedMps = iWindSpeedMps
    }
end

local function _getFlightLevelString(iAltitudeFeet)
    -- Round to nearest 500
    local iAltitudeFeetRounded = math.floor((iAltitudeFeet + 250) / 500) * 500
    
    -- Convert to flight level format (divide by 100)
    local iFlightLevel = math.floor(iAltitudeFeetRounded / 100)
    
    return string.format("FL%03d", iFlightLevel)
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  Weather measurement unit systems class
---  Defines a set of units to be used to display weather data
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
veafWeatherUnitSystem = {}
veafWeatherUnitSystem.__index = veafWeatherUnitSystem

veafWeatherUnitSystem.Units =
{
    Kts = 0,
    Mps = 1,
    M = 2,
    Sm = 3,
    Nm = 4,
    Ft = 5,
    Hpa = 6,
    InHg = 7,
    MmHg = 8
}
---------------------------------------------------------------------------------------------------
---  CTOR
function veafWeatherUnitSystem:create(windSpeeds, visibilities, altitudes, pressures)
    local this =
    {
        WindSpeeds = windSpeeds,
        Visibilities = visibilities,
        Altitudes = altitudes,
        Pressures = pressures,
    }

    setmetatable(this, veafWeatherUnitSystem)
    return this
end

---------------------------------------------------------------------------------------------------
---  Static data
veafWeatherUnitSystem.Systems =
{
    Full = veafWeatherUnitSystem:create({ veafWeatherUnitSystem.Units.Kts, veafWeatherUnitSystem.Units.Mps }, { veafWeatherUnitSystem.Units.M, veafWeatherUnitSystem.Units.Sm, veafWeatherUnitSystem.Units.Nm }, { veafWeatherUnitSystem.Units.Ft, veafWeatherUnitSystem.Units.M }, { veafWeatherUnitSystem.Units.Hpa, veafWeatherUnitSystem.Units.InHg, veafWeatherUnitSystem.Units.MmHg }), -- all 
    Icao = veafWeatherUnitSystem:create({ veafWeatherUnitSystem.Units.Kts }, { veafWeatherUnitSystem.Units.M }, { veafWeatherUnitSystem.Units.Ft }, { veafWeatherUnitSystem.Units.Hpa }), -- default
    IcaoMetric = veafWeatherUnitSystem:create({ veafWeatherUnitSystem.Units.Mps }, { veafWeatherUnitSystem.Units.M }, { veafWeatherUnitSystem.Units.Ft }, { veafWeatherUnitSystem.Units.Hpa }), -- for russian airfields
    Faa = veafWeatherUnitSystem:create({ veafWeatherUnitSystem.Units.Kts }, { veafWeatherUnitSystem.Units.Sm }, { veafWeatherUnitSystem.Units.Ft }, { veafWeatherUnitSystem.Units.InHg }), -- for US aircrafts or airfields, and for older british aircrafts
    FaaMetric = veafWeatherUnitSystem:create({ veafWeatherUnitSystem.Units.Kts }, { veafWeatherUnitSystem.Units.M }, { veafWeatherUnitSystem.Units.Ft }, { veafWeatherUnitSystem.Units.InHg }), -- for US army helicopters
    FaaNavy = veafWeatherUnitSystem:create({ veafWeatherUnitSystem.Units.Kts }, { veafWeatherUnitSystem.Units.Nm }, { veafWeatherUnitSystem.Units.Ft }, { veafWeatherUnitSystem.Units.InHg }), -- for US aircraft carriers
    Metric = veafWeatherUnitSystem:create({ veafWeatherUnitSystem.Units.Mps }, { veafWeatherUnitSystem.Units.M }, { veafWeatherUnitSystem.Units.M }, { veafWeatherUnitSystem.Units.Hpa }), -- for french army helicopters
    MetricEastern = veafWeatherUnitSystem:create({ veafWeatherUnitSystem.Units.Mps }, { veafWeatherUnitSystem.Units.M }, { veafWeatherUnitSystem.Units.M }, { veafWeatherUnitSystem.Units.MmHg }), -- for russian and chinese aircrafts
}
veafWeatherUnitSystem.DefaultUnitSystem = veafWeatherUnitSystem.Systems.Icao

veafWeatherUnitSystem.Theatres = {}
veafWeatherUnitSystem.Theatres.Faa = { veaf.theatreName.Nevada, veaf.theatreName.MarianaIslands }
veafWeatherUnitSystem.Theatres.IcaoMetric = { veaf.theatreName.Caucasus }

veafWeatherUnitSystem.Aircrafts = {}
veafWeatherUnitSystem.Aircrafts.Faa =
{
    "A-10A",
    "A-10C",
    "A-10C_2",
    "AV8BNA",
    "F-14A-135-GR",
    "F-14B",
    "F-15C",
    "F-15ESE",
    "F-16C_50",
    "FA-18C_hornet",
    "UH-1H",
    "P-47D-30",
    "P-47D-40",
    "P-51D",
    "P-51D-30-NA",
    "TF-51D",
    "Christen Eagle II",
    "SpitfireLFMkIX",
    "MosquitoFBMkVI"
}

veafWeatherUnitSystem.Aircrafts.Metric =
{
    "SA342L",
    "SA342M",
    "SA342Minigun",
    "SA342Mistral"
}

veafWeatherUnitSystem.Aircrafts.MetricEastern =
{
    "Ka-50",
    "Ka-50_3",
    "Mi-8MTV2",
    "Mi-24P",
    "MiG-15bis",
    "MiG-19P",
    "MiG-21Bis",
    "MiG-29S",
    "Su-25",
    "Su-25T",
    "Su-27",
    "Su-33",
    "J-11A",
    "FW-190A8",
    "FW-190D9",
    "I-16",
    "L-39C",
    "L-39ZA",
    "Yak-52"
}

veafWeatherUnitSystem.Aircrafts.FaaMetric =
{
    "AH-64D_BLK_II"
}

---------------------------------------------------------------------------------------------------
---  Methods
--- 

function veafWeatherUnitSystem.defaultForTypeName(sTypeName)
    if (veaf.tableContains(veafWeatherUnitSystem.Aircrafts.Faa, sTypeName)) then
        return veafWeatherUnitSystem.Systems.Faa
    elseif (veaf.tableContains(veafWeatherUnitSystem.Aircrafts.Metric, sTypeName)) then
        return veafWeatherUnitSystem.Systems.Metric
    elseif (veaf.tableContains(veafWeatherUnitSystem.Aircrafts.MetricEastern, sTypeName)) then
        return veafWeatherUnitSystem.Systems.MetricEastern
    elseif (veaf.tableContains(veafWeatherUnitSystem.Aircrafts.FaaMetric, sTypeName)) then
        return veafWeatherUnitSystem.Systems.FaaMetric        
    else
        return veafWeatherUnitSystem.DefaultUnitSystem
    end
end

function veafWeatherUnitSystem.defaultForTheatre()
    local sTheatre = env.mission.theatre

    if (veaf.tableContains(veafWeatherUnitSystem.Theatres.Faa, sTheatre)) then
        return veafWeatherUnitSystem.Systems.Faa
    elseif (veaf.tableContains(veafWeatherUnitSystem.Theatres.IcaoMetric, sTheatre)) then
        return veafWeatherUnitSystem.Systems.IcaoMetric
    else
        return veafWeatherUnitSystem.DefaultUnitSystem
    end
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  Weather management class
---  Collects and compile wheather data form various sources in the sim at a location
---  Can be output to string as METAR or ATIS informations
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
veafWeatherData = {}
veafWeatherData.__index = veafWeatherData

---------------------------------------------------------------------------------------------------
---  CTOR
function veafWeatherData:create(vec3, iAbsTime, iAltitudeMeters)
    iAbsTime = iAbsTime or timer.getAbsTime()
    iAltitudeMeters = iAltitudeMeters or veaf.getLandHeight(vec3)

    local sunTimesZulu = veafTime.getSunTimesZulu(vec3)
    local sunTimesLocal = veafTime.getSunTimesLocal(vec3)

    local iWindDirSurface, iWindSpeedSurfaceMps = weathermark._GetWind(vec3, iAltitudeMeters + 10) -- Measure the wind velocity at the standard height of 10 metres above the surface. This is the internationally accepted meteorological definition of ‘surface wind’ designed to eliminate distortion attributable to very local terrain effects

    -- the static env.mission.weather.visibility.distance is not used anymore, and the DCS engine apparently sets a default at 100000, as seen in this log line:
    -- WEATHER (Main): set fog: visibility:100000  thickness:0.0
    local iVisibilityMeters = 100000
    local clouds = nil
    local bPrecipitation = false;
    local sCloudPreset = env.mission.weather.clouds.preset
    if (veaf.isNullOrEmpty(sCloudPreset)) then
        if (env.mission.weather.clouds.density > 0) then
            local iDensity = mist.utils.round(env.mission.weather.clouds.density * 8 / 10) -- 10 levels in dcs, convert to oktas
            clouds = { Density = iDensity, BaseMeters = env.mission.weather.clouds.base }
        end
        bPrecipitation = (env.mission.weather.clouds.iprecptns > 0)
    else
        if (_dcsPresetDensity[sCloudPreset]) then
            clouds = {Density = _dcsPresetDensity[sCloudPreset][1], BaseMeters = env.mission.weather.clouds.base}
            bPrecipitation = _dcsPresetDensity[sCloudPreset][2]
            --[[ -- visibilities in presets were already not reliable, and they have no meaning with the new fog visiblity setting
            if (_dcsPresetDensity[sCloudPreset][3] and _dcsPresetDensity[sCloudPreset][3] < iVisibilityMeters) then
                iVisibilityMeters = _dcsPresetDensity[sCloudPreset][3]
            end
            ]]
        end
    end

    local iFogThicknessMeters = world.weather.getFogThickness()
    local iFogVisibilityMeters = world.weather.getFogVisibilityDistance()
    if (iFogThicknessMeters >= iAltitudeMeters) then
        iVisibilityMeters = iFogVisibilityMeters
-- LOGGING DISABLED WHEN COMPILING("Visibility new fog=%d", iVisibilityMeters)
    else
-- LOGGING DISABLED WHEN COMPILING("Visibility=%d. New fog ignored, measure point above fog ceiling [ altitude=%d, fog thickness=%d ]", iVisibilityMeters, iAltitudeMeters, iFogThicknessMeters)
    end

    local _, nQfePa = atmosphere.getTemperatureAndPressure({ x = vec3.x, y = iAltitudeMeters, z = vec3.z })
    local nTemperatureKelvin, nQnhPa = atmosphere.getTemperatureAndPressure({ x = vec3.x, y = 0, z = vec3.z })
    local nTemperatureCelcius = nTemperatureKelvin + _nKelvinToCelciusOffset

    local nHumidity = _computeHumidity(vec3, clouds.BaseMeters, iVisibilityMeters, bPrecipitation, iAbsTime)
    local nDewPointCelcius = _computeDewpoint(nTemperatureCelcius, nQnhPa, nHumidity)

    -- Fog FG or mist BR: fog is less than 1000 meters visibility. Mist BR or haze HZ: if the humidity is more than 80% it is mist.
    local visibilityAffect = _visibilityAffect.None
    if (iVisibilityMeters < 1000) then
        visibilityAffect = _visibilityAffect.Fog
    elseif (iVisibilityMeters < 5000 and nHumidity >= 80) then
        visibilityAffect = _visibilityAffect.Mist
    elseif (iVisibilityMeters < 5000) then
        visibilityAffect = _visibilityAffect.Haze
    end

    local weatherSlices = { }
    if (iAltitudeMeters < 600) then
        table.insert(weatherSlices, _weatherSliceAtAltitude(vec3, 500))
    end
    if (iAltitudeMeters < 2100) then
        table.insert(weatherSlices, _weatherSliceAtAltitude(vec3, 2000))
    end
    if (iAltitudeMeters < 8100) then
        table.insert(weatherSlices, _weatherSliceAtAltitude(vec3, 8000))
    end

    local this =
    {
        AbsTime = iAbsTime,
        Vec3 = vec3,
        AltitudeMeter = iAltitudeMeters,
        WindDirection = iWindDirSurface,
        WindSpeedMps = iWindSpeedSurfaceMps,
        VisibilityMeters = iVisibilityMeters,
        Dust = env.mission.weather.enable_dust,
        VisibilityAffect = visibilityAffect, 
        Clouds = clouds,
        Precipitation = bPrecipitation,
        TemperatureCelcius = nTemperatureCelcius,
        DewPointCelcius = nDewPointCelcius,
        QnhHpa = nQnhPa / 100,
        QfeHpa = nQfePa / 100,
        SunriseZulu = sunTimesZulu.Sunrise,
        SunsetZulu = sunTimesZulu.Sunset,
        SunriseLocal = sunTimesLocal.Sunrise,
        SunsetLocal = sunTimesLocal.Sunset,

        WeatherSlices = weatherSlices,
    }

    setmetatable(this, veafWeatherData)

-- LOGGING DISABLED WHEN COMPILING(this:toString(veafWeatherUnitSystem.Systems.Faa))
-- LOGGING DISABLED WHEN COMPILING(this:toString(veafWeatherUnitSystem.Systems.FaaNavy))
-- LOGGING DISABLED WHEN COMPILING(this:toString(veafWeatherUnitSystem.Systems.Icao))
    
    return this
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Static methods
function veafWeatherData.getWeatherString(vec3, dcsElementName, unitSystem)
    local bWithLaste = false

    local sTypeName = veaf.getDcsTypeName(dcsElementName)

    if (unitSystem == nil) then
        unitSystem = veafWeatherUnitSystem.defaultForTypeName(sTypeName)
    end

    if (not veaf.isNullOrEmpty(sTypeName) and veaf.startsWith(sTypeName, "A-10", false)) then
        bWithLaste = true
    end

    local weatherData = veafWeatherData:create(vec3)
    return weatherData:toString(unitSystem, bWithLaste)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Methods
function veafWeatherData:getNormalizedWindDirection(iDirectionTrue, bMagnetic)
    bMagnetic = bMagnetic or false

    local iDirection = iDirectionTrue
    
    if (bMagnetic) then
        iDirection = iDirection - veaf.getMagneticDeclination()
        if (iDirection) < 0 then
            iDirection = iDirection + 360
        end    
    end

    if (iDirection == 0) then
        iDirection = 360
    end

    return iDirection
end

function veafWeatherData:getNormalizedCloudBaseMeters(bHeight)
    bHeight = bHeight or false

    if (self.Clouds == nil or self.Clouds.Density <= 0) then
        return nil
    else
         local iCloudBase = self.Clouds.BaseMeters
         if (bHeight) then
            iCloudBase = iCloudBase - self.AltitudeMeter
        end

        return iCloudBase
    end
end

function veafWeatherData:getNormalizedCloudsDensity()
    if (self.Clouds == nil or self.Clouds.BaseMeters == nil or self.Clouds.Density <= 0) then
        return _cloudDensity.Clear
    else
        return _cloudDensityOktas[self.Clouds.Density]
    end
end

function veafWeatherData:isCavok()
    local iCloudHeightMeters = self:getNormalizedCloudBaseMeters(true)

    if (iCloudHeightMeters == nil or mist.utils.metersToFeet(iCloudHeightMeters) < 5000) then
        return false -- no clouds or cloud below 5000 ft
    else
       return (self.VisibilityMeters >= 10000 and not self.Precipitation and not self.Dust)
    end
end

function veafWeatherData:getCarrierCase()
    -- Case I departures are flown during the day when weather conditions allow departure under visual flight rules (VFR). The weather minimums are a cloud deck above 3,000 feet and visibility greater than 5 miles
    -- Case II departures are flown during the day when visual conditions are present at the carrier, but a controlled climb through the clouds is required. The weather minimums are a cloud deck above 1,000 feet and visibility greater than 5 miles.
    -- Case III departures are flown at night and when weather conditions are below the minimums of 1,000 feet cloud deck and 5 miles visibility
    
    local bNight = veafTime.isAeronauticalNight(self.Vec3, self.AbsTime)
    if (bNight) then
        return 3
    end

    local iCloudBase = nil
    if (self.Clouds and self.Clouds.Density > 4) then
        iCloudBase = self.Clouds.BaseMeters
    end

    local iVisibilityCase12 = mist.utils.NMToMeters(5)
    local iCloudBaseCase1 = mist.utils.feetToMeters(3000)
    local iCloudBaseCase2 = mist.utils.feetToMeters(1000)

-- LOGGING DISABLED WHEN COMPILING(string.format("GetCarrierCase - Cloud base=%d feet (need more than 1000 for CASE 2 and 300 for CASE 3) - visibility=%d nm (need more than 5 for CASE 1/2)", iCloudBase or -1, UTILS.MetersToNM (self.VisibilityMeters)))

    if (self.VisibilityMeters > iVisibilityCase12 and (iCloudBase == nil or iCloudBase > iCloudBaseCase1)) then
        return 1
    elseif (self.VisibilityMeters > iVisibilityCase12 and (iCloudBase == nil or iCloudBase > iCloudBaseCase2)) then
        return 2
    else
        return 3
    end
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToStrings
function veafWeatherData:appendString(s, sAppend)
    sAppend = sAppend or ""

    if (veaf.isNullOrEmpty(s)) then
        return sAppend
    elseif (veaf.isNullOrEmpty(sAppend)) then
        return s
    else
        return s .. "|" .. sAppend
    end
end

function veafWeatherData:toStringWind(unitSystem, iDirection, nSpeedMps, bMagnetic)
    unitSystem = unitSystem or veafWeatherUnitSystem.DefaultUnitSystem
    bMagnetic = bMagnetic or false

    if (nSpeedMps <= 0.5) then
        return "calm"
    end

    local iDirection = self:getNormalizedWindDirection(iDirection, bMagnetic)
    local sSpeedKts = string.format("%dkts", mist.utils.mpsToKnots(nSpeedMps))
    local sSpeedMps = string.format("%dm/s", nSpeedMps)
    local sSpeed
    if(veaf.tableContains(unitSystem.WindSpeeds, veafWeatherUnitSystem.Units.Kts)) then
        sSpeed = veafWeatherData:appendString(sSpeed, sSpeedKts)
    end
    if(veaf.tableContains(unitSystem.WindSpeeds, veafWeatherUnitSystem.Units.Mps)) then
        sSpeed = veafWeatherData:appendString(sSpeed, sSpeedMps)
    end

    local sDegrees
    if (bMagnetic) then
        sDegrees = "°M"
    else
        sDegrees = "°T"
    end

    return string.format("%03d%s @ %s", iDirection, sDegrees, sSpeed)
end

function veafWeatherData:toStringVisibility(unitSystem, bWithMax)
    unitSystem = unitSystem or veafWeatherUnitSystem.DefaultUnitSystem

    local sVisibilityMeters
    if (self.VisibilityMeters >= 10000) then
        sVisibilityMeters = "10+km"
    else
        local iVisibilityMeters
        if (self.VisibilityMeters >= 100) then
            iVisibilityMeters = mist.utils.round(self.VisibilityMeters / 100) * 100
        else
            iVisibilityMeters = mist.utils.round(self.VisibilityMeters / 50) * 50
        end
            
        sVisibilityMeters = string.format("%dm", iVisibilityMeters)
    end

    local sVisibilityStatuteMile
    local iVisibilityStatuteMile = self.VisibilityMeters * 0.000621371
    if (iVisibilityStatuteMile >= 10) then
        sVisibilityStatuteMile = "10+SM"
    elseif (iVisibilityStatuteMile >= 1) then
        sVisibilityStatuteMile = string.format("%dSM", mist.utils.round(iVisibilityStatuteMile))
    elseif (iVisibilityStatuteMile >= 0.75) then
        sVisibilityStatuteMile = "3/4SM"
    elseif (iVisibilityStatuteMile >= 0.5) then
        sVisibilityStatuteMile = "1/2SM"
    elseif (iVisibilityStatuteMile >= 0.25) then
        sVisibilityStatuteMile = "1/4SM"
    else
        sVisibilityStatuteMile = "0SM"
    end

    local sVisibilityNauticalMile
    local iVisibilityNauticalMile = mist.utils.metersToNM(self.VisibilityMeters)
    if (iVisibilityNauticalMile >= 10) then
        sVisibilityNauticalMile = "10+NM"
    elseif (iVisibilityNauticalMile >= 1) then
        sVisibilityNauticalMile = string.format("%dNM", mist.utils.round(iVisibilityNauticalMile))
    else
        local iVisibilityYards = mist.utils.round((iVisibilityNauticalMile * 2025.37) / 100) * 100
        sVisibilityNauticalMile = string.format("%dyds", iVisibilityYards)
    end

    local sVisibility
    if(veaf.tableContains(unitSystem.Visibilities, veafWeatherUnitSystem.Units.M)) then
        sVisibility = veafWeatherData:appendString(sVisibility, sVisibilityMeters)
    end
    if(veaf.tableContains(unitSystem.Visibilities, veafWeatherUnitSystem.Units.Sm)) then
        sVisibility = veafWeatherData:appendString(sVisibility, sVisibilityStatuteMile)
    end
    if(veaf.tableContains(unitSystem.Visibilities, veafWeatherUnitSystem.Units.Nm)) then
        sVisibility = veafWeatherData:appendString(sVisibility, sVisibilityNauticalMile)
    end

    if (self.VisibilityAffect == _visibilityAffect.Fog) then
        sVisibility = sVisibility .. " - fog"
    elseif (self.VisibilityAffect == _visibilityAffect.Haze) then
        sVisibility = sVisibility .. " - haze"
    elseif (self.VisibilityAffect == _visibilityAffect.Mist) then
        sVisibility = sVisibility .. " - mist"
    end
    if (self.Dust) then
        sVisibility = sVisibility .. " - dust"
    end
    if (self.Precipitation) then
        sVisibility = sVisibility .. " - precipitations"
    end

    return sVisibility
end

function veafWeatherData:toStringClouds(unitSystem, bHeight)
    unitSystem = unitSystem or veafWeatherUnitSystem.DefaultUnitSystem
    bHeight = bHeight or false

    local cloudDensity = self:getNormalizedCloudsDensity()
    local iCloudBaseMeters = self:getNormalizedCloudBaseMeters(bHeight)

    local sCloudDensity = ""
    local sCloudBase = ""

    if (cloudDensity == _cloudDensity.Clear) then
        sCloudDensity = "No clouds"
    else
        if (cloudDensity == _cloudDensity.Scattered) then
            sCloudDensity = "Scattered clouds"
        elseif (cloudDensity == _cloudDensity.Broken) then
            sCloudDensity = "Broken clouds"
        elseif (cloudDensity == _cloudDensity.Overcast) then
            sCloudDensity = "Overcast clouds"
        else
            sCloudDensity = "Few clouds"
        end
        
        if (iCloudBaseMeters ~= nil and iCloudBaseMeters > 0) then
            local iCloudBaseFeet = math.floor((mist.utils.metersToFeet(iCloudBaseMeters) + 250) / 500) * 500
            local iCloudBaseMeters = math.floor((iCloudBaseMeters + 250) / 500) * 500
            local sCloudBaseFeet = string.format("%dft", iCloudBaseFeet)
            local sCloudBaseMeters = string.format("%dm", iCloudBaseMeters)

            if(veaf.tableContains(unitSystem.Altitudes, veafWeatherUnitSystem.Units.Ft)) then
                sCloudBase = veafWeatherData:appendString(sCloudBase, sCloudBaseFeet)
            end
            if(veaf.tableContains(unitSystem.Altitudes, veafWeatherUnitSystem.Units.M)) then
                sCloudBase = veafWeatherData:appendString(sCloudBase, sCloudBaseMeters)
            end

            sCloudBase = string.format(" @ %s", sCloudBase)

            if (bHeight) then
                sCloudBase = sCloudBase .. " AGL"
            else
                sCloudBase = sCloudBase .. " ASL"
            end
        end
    end

    return string.format("%s%s", sCloudDensity, sCloudBase) 
end

function veafWeatherData:toStringTemperature(nTemperatureCelcius)
    return string.format("%d°C", mist.utils.round(nTemperatureCelcius))
end

function veafWeatherData:toStringPressure(unitSystem, nPressureHpa)
    unitSystem = unitSystem or veafWeatherUnitSystem.DefaultUnitSystem

    local sPressureHpa = string.format("%.0fHpa", nPressureHpa)
    local sPressureInHg = string.format("%.2finHg", mist.utils.converter("hpa", "inhg", nPressureHpa))
    local sPressureMmHg = string.format("%.0fmmHg", nPressureHpa * 0.75006375541921) -- mist convert has the wrong coefficient for hpa to mmHg
 
    local sPressure
    if(veaf.tableContains(unitSystem.Pressures, veafWeatherUnitSystem.Units.Hpa)) then
        sPressure = veafWeatherData:appendString(sPressure, sPressureHpa)
    end
    if(veaf.tableContains(unitSystem.Pressures, veafWeatherUnitSystem.Units.InHg)) then
        sPressure = veafWeatherData:appendString(sPressure, sPressureInHg)
    end
    if(veaf.tableContains(unitSystem.Pressures, veafWeatherUnitSystem.Units.MmHg)) then
        sPressure = veafWeatherData:appendString(sPressure, sPressureMmHg)
    end

    return sPressure
end

function veafWeatherData:toStringSunTime(dateTimeZulu, bZulu, bLocal)
    local sLocal = ""
    if (bLocal) then
        local dateTimeLocal = veafTime.toLocal(dateTimeZulu)
        sLocal = string.format("%sL", veafTime.toStringTime(dateTimeLocal, false))
    end 

    local sZulu = ""
    if (bZulu) then
        sZulu = string.format("%sZ", veafTime.toStringTime(dateTimeZulu, false))
    end 

    if (bLocal and bZulu) then
        return string.format("%s - %s", sZulu, sLocal)
    elseif (bLocal) then
        return sLocal
    else
        return sZulu
    end
end

function veafWeatherData:toStringSlice(weatherSlice, unitSystem, bMagnetic)
    unitSystem = unitSystem or veafWeatherUnitSystem.DefaultUnitSystem
    bMagnetic = bMagnetic or false

    local sAltitudeMeters = string.format("%dm", weatherSlice.AltitudeMeters)
    local sAltitudeFl = _getFlightLevelString(mist.utils.metersToFeet(weatherSlice.AltitudeMeters))

    local sAltitude
    if(veaf.tableContains(unitSystem.Altitudes, veafWeatherUnitSystem.Units.Ft)) then
        sAltitude = veafWeatherData:appendString(sAltitude, sAltitudeFl)
    end
    if(veaf.tableContains(unitSystem.Altitudes, veafWeatherUnitSystem.Units.M)) then
        sAltitude = veafWeatherData:appendString(sAltitude, sAltitudeMeters)
    end

    local sTemperature = self:toStringTemperature(weatherSlice.TemperatureCelcius)
    local sPressure = self:toStringPressure(unitSystem, weatherSlice.PressureHpa)
    local sWind = self:toStringWind(unitSystem, weatherSlice.WindDirection, weatherSlice.WindSpeedMps, bMagnetic)

    return string.format("%s:  wind %s ; %s", sAltitude, sWind, sTemperature)
end

function veafWeatherData:toStringLaste()
    local function _getLasteAt(iDesiredHeightFeet)
        local iAltitudeFeet = math.floor((mist.utils.metersToFeet(self.AltitudeMeter) + iDesiredHeightFeet + 500) / 1000) * 1000
        local iAltitudeMeters = mist.utils.feetToMeters(iAltitudeFeet)
        local iTemperatureKelvin, _ = atmosphere.getTemperatureAndPressure({ x = self.Vec3.x, y = iAltitudeMeters, z = self.Vec3.z })
        local iWindDirection, iWindSpeedMps = weathermark._GetWind(self.Vec3, iAltitudeMeters)
        local iWindDirectionMagnetic = veafWeatherData:getNormalizedWindDirection(iWindDirection, true)

        local sLaste = string.format("ALT%02d W%03d/%02d T%+d", iAltitudeFeet / 1000, iWindDirectionMagnetic, mist.utils.mpsToKnots(iWindSpeedMps), iTemperatureKelvin + _nKelvinToCelciusOffset)
-- LOGGING DISABLED WHEN COMPILING(string.format("LASTE @ %f - W%dM %dT", iAltitudeFeet, iWindDirectionMagnetic, iWindDirection))
-- LOGGING DISABLED WHEN COMPILING(sLaste)
        return sLaste
    end

    local sLaste = ""
    sLaste = sLaste .. string.format("\n%s", _getLasteAt(2000))
    sLaste = sLaste .. string.format("\n%s", _getLasteAt(8000))
    sLaste = sLaste .. string.format("\n%s", _getLasteAt(16000))
    --sLaste = sLaste .. string.format("\n%s", _getLasteAt(28000))

    return sLaste
end

function veafWeatherData:toString(unitSystem, bWithLaste)
    unitSystem = unitSystem or veafWeatherUnitSystem.DefaultUnitSystem
    bWithLaste = bWithLaste or false

    local sString = ""
    sString = sString .. string.format("Wind:          %s", self:toStringWind(unitSystem, self.WindDirection, self.WindSpeedMps))
    sString = sString .. "\n"
    sString = sString .. string.format("\nVisibility:    %s", self:toStringVisibility(unitSystem))
    sString = sString .. string.format("\nClouds:        %s", self:toStringClouds(unitSystem, true))
    sString = sString .. "\n"
    sString = sString .. string.format("\nTemperature:   %s - Dew point: %s", self:toStringTemperature(self.TemperatureCelcius), self:toStringTemperature(self.DewPointCelcius))
    sString = sString .. string.format("\nQNH:           %s", self:toStringPressure(unitSystem, self.QnhHpa))
    sString = sString .. string.format("\nQFE:           %s", self:toStringPressure(unitSystem, self.QfeHpa))
    sString = sString .. string.format("\nSunrise:       %s", self:toStringSunTime(self.SunriseZulu, true, true))
    sString = sString .. string.format("\nSunset:       %s", self:toStringSunTime(self.SunsetZulu, true, true))
    
    sString = sString .. "\n"
    if(bWithLaste) then
        sString = sString .. string.format("\nLASTE:%s", self:toStringLaste())
    else
        for _, weatherSlice in pairs(self.WeatherSlices) do
            sString = sString .. string.format("\n @ %s", self:toStringSlice(weatherSlice, unitSystem))
        end
    end

    return sString
end

function veafWeatherData:toStringExtended(unitSystem, bHeight)
    unitSystem = unitSystem or veafWeatherUnitSystem.DefaultUnitSystem

    local sAltitudeFeet = string.format("%dft", mist.utils.round(mist.utils.metersToFeet(self.AltitudeMeter)))
    local sAltitudeMeters = string.format("%dm", mist.utils.round(self.AltitudeMeter))
    local sAltitude
    if(veaf.tableContains(unitSystem.Altitudes, veafWeatherUnitSystem.Units.Ft)) then
        sAltitude = veafWeatherData:appendString(sAltitude, sAltitudeFeet)
    end
    if(veaf.tableContains(unitSystem.Altitudes, veafWeatherUnitSystem.Units.M)) then
        sAltitude = veafWeatherData:appendString(sAltitude, sAltitudeMeters)
    end

    local nLatitude, nLongitude = coord.LOtoLL(self.Vec3)
    
    local sString = ""
    sString = sString .. string.format("Time:          %s", veafTime.absTimeToStringDateTime(self.AbsTime))
    sString = sString .. string.format("\nLocation:      %s", mist.tostringLL(nLatitude, nLongitude, 0, true))
    sString = sString .. string.format("\nAltitude:      %s", sAltitude)
    sString = sString .. "\n\n" .. self:toString(unitSystem, bHeight)
    return sString    
end

function veafWeatherData:toStringAtis(unitSystem)
    unitSystem = unitSystem or veafWeatherUnitSystem.DefaultUnitSystem

    local sAtis = ""
    sAtis = sAtis .. string.format("Wind %s", self:toStringWind(unitSystem, self.WindDirection, self.WindSpeedMps, true))
    if(self:isCavok()) then
        sAtis = sAtis .. "\nCeiling and visiblity OK, CAVOK"
    else
        sAtis = sAtis .. string.format("\nVisibility %s, %s", self:toStringVisibility(unitSystem), self:toStringClouds(unitSystem, true))
    end
    
    sAtis = sAtis .. string.format("\nTemperature %s, dew point %s", self:toStringTemperature(self.TemperatureCelcius), self:toStringTemperature(self.DewPointCelcius))
    sAtis = sAtis .. string.format("\nQNH %s", self:toStringPressure(unitSystem, self.QnhHpa))
    
    if(veafTime.isAeronauticalNight(self.Vec3, self.AbsTime)) then
        sAtis = sAtis .. string.format("\nSunrise %s", self:toStringSunTime(self.SunriseZulu, true, false))
    else
        sAtis = sAtis .. string.format("\nSunset %s", self:toStringSunTime(self.SunsetZulu, true, false))
    end
    
    return sAtis
end
--[[
function FgWeather:ToStringMetar()
    local iWindForce, iWindDirection = self:GetFormattedWind()
    local sWind
    if (iWindForce < 1) then
        sWind = "00000KT"
    else
        iWindDirection = UTILS.Round(iWindDirection / 10) * 10
        if (iWindDirection == 0) then
            iWindDirection = 360
        end
        sWind = string.format("%03d%02dKT", iWindDirection, iWindForce)
    end

    local iVisibility = UTILS.Round(self.VisibilityMeters / 100) * 100
    if (iVisibility >= 10000) then
        iVisibility = 9999
    end
    local sVisibility = string.format("%04d", iVisibility)

    local sSignificativeWeather = nil
    if (self.Precipitation) then
        sSignificativeWeather = "RA" -- TODO rain will be snow if season+map+t° ?
    end
    if (self.Fog) then
        sSignificativeWeather = Fg.AppendWithSeparator(sSignificativeWeather, "FG")
    end
    if (self.Dust) then
        sSignificativeWeather = Fg.AppendWithSeparator(sSignificativeWeather, "DU")
    end

    sVisibility = Fg.AppendWithSeparator(sVisibility, sSignificativeWeather)

    local sClouds
    local cloudDensity, iCloudBase = self:GetFormattedClouds(true)
    if (cloudDensity == CloudDensityLabel.Clear) then
        sClouds = "SKC"
    elseif (cloudDensity == CloudDensityLabel.Cavok) then
        sClouds = "CAVOK"
        sVisibility = nil
    else
        local sDensity = "FEW"
        if (cloudDensity == CloudDensityLabel.Scattered) then
            sDensity = "SCT"
        elseif (cloudDensity == CloudDensityLabel.Broken) then
            sDensity = "BKN"
        elseif (cloudDensity == CloudDensityLabel.Overcast) then
            sDensity = "OVC"
        end
           
        sClouds = string.format("%s%03d", sDensity, iCloudBase)
    end

    local sTemperature
    local iTemperature = UTILS.Round(self.TemperatureCelcius)
    if (iTemperature >= 0) then
        sTemperature = string.format("%02d", iTemperature)
    else
        sTemperature = string.format("M%02d", -iTemperature)
    end

    local sQnh = string.format("Q%d/%.2f", self.QnhHpa, UTILS.hPa2inHg(self.QnhHpa))

    local sMetar = Fg.TimeToStringMetar()
    sMetar = sMetar .. " " .. sWind
    sMetar = Fg.AppendWithSeparator (sMetar, sVisibility, " ")
    sMetar = sMetar .. " " .. sClouds
    sMetar = sMetar .. " " .. sTemperature
    sMetar = sMetar .. " " .. sQnh

    return sMetar
end

function FgWeather.CreateMetarMark(mooseCoord, mooseGroup)
    local weather = FgWeather:Create(mooseCoord)
    local sMetar = weather:ToStringMetar()

    local vec3 = mooseCoord:GetVec3()
    local iMarkId = UTILS.GetMarkID()
    if (mooseGroup) then
        trigger.action.markToGroup(iMarkId, sMetar, vec3, mooseGroup:GetDCSObject():getID(), false, nil)
    else
        trigger.action.markToAll(iMarkId, sMetar, vec3, false, nil)
    end

    return iMarkId
end
]]
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  ATIS management class
---  Simulation of the recording of an ATIS information per hour per airfield
---  For each info a recording time and corresponding letter is generated (just to fluff it)
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
veafWeatherAtis = {}
veafWeatherAtis.__index = veafWeatherAtis
veafWeatherAtis.ListInEffect = {}
---------------------------------------------------------------------------------------------------
---  CTORS
function veafWeatherAtis:Create(veafAirbase, dateTimeZulu)
    local iHoursSinceMidnight = dateTimeZulu.hour
    local sLetter = string.char(math.floor(iHoursSinceMidnight) + string.byte("A"))

    local iRecordedAtMinutes = math.random(2, 11) -- ATIS recorded between h:02 and hour:11
    if (iRecordedAtMinutes > dateTimeZulu.min) then
        -- if record is in the future set recording at the request time
        iRecordedAtMinutes = dateTimeZulu.min
    end
    dateTimeZulu.min = iRecordedAtMinutes

    local iAltitude = nil
    local unitSystem
    if (veafAirbase.Category == Airbase.Category.SHIP) then
        -- Maybe use the type name to decide the unit system?
        --[[
        local dcsShip = dcsAirbase:getUnit()
        local dcsShipType = dcsShip:getTypeName()
-- LOGGING DISABLED WHEN COMPILING(veaf.p(dcsShipType))
        ]]

        iAltitude = 20
        unitSystem = veafWeatherUnitSystem.Systems.FaaNavy
    else
        unitSystem = veafWeatherUnitSystem.defaultForTheatre()
    end
    
    local weatherData = veafWeatherData:create(veafAirbase.DcsAirbase:getPoint(), nil, iAltitude)

    local sMessage
    
    if (veafAirbase.Category == Airbase.Category.SHIP) then
        sMessage = string.format("%s information at %sZ", veafAirbase.DisplayName, veafTime.toStringTime(dateTimeZulu, false))
        local iCarrierCase = weatherData:getCarrierCase()
        if (iCarrierCase) then
            local sCaseString = nil
            if (iCarrierCase == 1) then sCaseString = "I"
            elseif (iCarrierCase == 2) then sCaseString = "II"
            elseif (iCarrierCase == 3) then sCaseString = "III"
            end
            
            if (not veaf.isNullOrEmpty(sCaseString)) then
                sMessage = sMessage .. string.format("\nProbable CASE %s in effect", sCaseString)
            end
        end
    elseif (veafAirbase.Category == Airbase.Category.HELIPAD) then
        sMessage = string.format("%s information at %sZ", veafAirbase.DisplayName, veafTime.toStringTime(dateTimeZulu, false))
    else
        sMessage = string.format("%s information %s, recorded at %sZ", veafAirbase.Name, sLetter, veafTime.toStringTime(dateTimeZulu, false))
        local sRunwayInService = veafAirbase:getRunwayInServiceString(weatherData.WindDirection)
        if (not veaf.isNullOrEmpty(sRunwayInService)) then
            sMessage = sMessage .. string.format("\nRecommended runway %s", sRunwayInService)
        end
    end

    sMessage = sMessage .. "\n" .. weatherData:toStringAtis(unitSystem)
    --sMessage = sMessage .. "\n" .. weatherData:toStringExtended()

    local this =
    {
        AirbaseName = veafAirbase.Name,
        Letter = sLetter,
        DateTimeZulu = dateTimeZulu,
        Message = sMessage
    }

    setmetatable(this, self)
    return this
end

---------------------------------------------------------------------------------------------------
---  Methods


---------------------------------------------------------------------------------------------------
---  Static methods
function veafWeatherAtis.getAtis(veafAirbase)
    local iAbsTime = timer.getAbsTime()
    local dateTime = veafTime.absTimeToDateTime(iAbsTime)
    local dateTimeZulu = veafTime.toZulu(dateTime)

-- LOGGING DISABLED WHEN COMPILING(string.format("Preparing ATIS for airbase %s at %sZ", veafAirbase.Name, veafTime.toStringTime(dateTimeZulu, false)))

    local atisInEffect = veafWeatherAtis.ListInEffect[veafAirbase.Name]
    if (atisInEffect) then
-- LOGGING DISABLED WHEN COMPILING(string.format("ATIS in effect: %s %s", atisInEffect.Letter, veafTime.toStringTime(atisInEffect.DateTimeZulu, false)))
        if (dateTimeZulu.year > atisInEffect.DateTimeZulu.year or dateTimeZulu.month > atisInEffect.DateTimeZulu.month or dateTimeZulu.day > atisInEffect.DateTimeZulu.day or dateTimeZulu.hour > atisInEffect.DateTimeZulu.hour) then
            -- if current date is in the next hour of more from the current one, declare new atis
-- LOGGING DISABLED WHEN COMPILING(string.format("Current time %s: new ATIS", veafTime.toStringTime(dateTimeZulu, false)))  
            atisInEffect = nil 
        end
    end

    if (atisInEffect == nil) then
        atisInEffect = veafWeatherAtis:Create(veafAirbase, dateTimeZulu)
-- LOGGING DISABLED WHEN COMPILING(string.format("New ATIS in effect for airbase %s: %s %s", veafAirbase.Name, atisInEffect.Letter, veafTime.toStringTime(atisInEffect.DateTimeZulu, false)))  
        veafWeatherAtis.ListInEffect[veafAirbase.Name] = atisInEffect
    end

    return atisInEffect
end

function veafWeatherAtis.getAtisString(veafAirbase)
    local atisInEffect = veafWeatherAtis.getAtis(veafAirbase)
    return atisInEffect.Message
end

function veafWeatherAtis.getAtisStringFromVeafPoint(sPointName, iAbsTime)
    if (veaf.isNullOrEmpty(sPointName)) then
        veaf.loggers.get(veafWeather.Id):error("No point name")
        return "No point name"
    end

    local dcsAirbase = veaf.findDcsAirbase(sPointName)
    local veafAirbase = veafAirbases.getAirbaseFromDcsAirbase(dcsAirbase)

    if (veafAirbase == nil) then
        veaf.loggers.get(veafWeather.Id):error("Airbase not found for point " .. sPointName)
        return "Airbase not found for point " .. sPointName
    end

-- LOGGING DISABLED WHEN COMPILING("Airbase found from veaf point named %s: %s",veaf.p(sPointName), veaf.p(veaf.ifnn(dcsAirbase, "getName")))

    return veafWeatherAtis.getAtisString(veafAirbase, iAbsTime)
end

function veafWeather.messageWeatherAtClosestPoint(unitName, forUnit)
-- LOGGING DISABLED WHEN COMPILING("veafWeather.messageWeatherAtClosestPoint(unitName=%s)",veaf.p(unitName))
    local closestPoint = veafNamedPoints.getNearestPoint(unitName)
    if closestPoint then
        local BR = veafNamedPoints.getPointBearing({closestPoint.name, unitName})
        if BR then BR = " ("..BR..")" else BR = "" end
        local weatherReport = "WEATHER        : " .. closestPoint.name .. BR .. "\n\n"
        weatherReport = weatherReport .. veafWeatherData.getWeatherString(closestPoint, unitName)
        if forUnit then
            veaf.outTextForUnit(unitName, weatherReport, 30)
        else
            veaf.outTextForGroup(unitName, weatherReport, 30)
        end
    end
end

function veafWeather.messageAtcClosestAirbase(unitName, forUnit)
    local dcsUnit = Unit.getByName(unitName)
    local veafAirbase = veafAirbases.getNearestAirbase(dcsUnit)
    if (veafAirbase) then
        local sAtcReport = veafWeatherAtis.getAtisString(veafAirbase)
        if forUnit then
            veaf.outTextForUnit(dcsUnit:getName(), sAtcReport, 30)
        else
            veaf.outTextForGroup(dcsUnit:getName(), sAtcReport, 30)
        end
    end
end

function veafWeather.messageAtcAndWeather(unitName, forUnit)
    veafWeather.messageAtcClosestAirbase(unitName, forUnit)
    veafWeather.messageWeatherAtClosestPoint(unitName, forUnit)    
end

----------------------------------------------------------------------------------------------------
--- WEATHER modifications during runtime

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- VeafFog class methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------

VeafFog = {}
VeafFog.DELAY_BETWEEN_DYNAMIC_CHECKS = 5 * 60
VeafFog.DYNAMICFOG_BASEFACTOR_HEAVY  = 0.8
VeafFog.DYNAMICFOG_BASEFACTOR_MEDIUM = 0.5
VeafFog.DYNAMICFOG_BASEFACTOR_SPARSE = 0.2

function VeafFog.init(object)
  -- technical name
  object.name = nil
  -- scheduled function that is used to update the object
  object.dynamicCheckFunctionScheduled = nil
  -- if true, the object is enabled and the fog settings (static and/or dynamic) are applied
  object.enabled = false
  -- the dynamic fog parameters for this object
  object.fogAnimationData = {}
  -- the static fog parameters for this object
  object.fogStaticData = {visibility = 10000, thickness = 150}
  -- the static fog parameters saved by this object (stored state before it changes them)
  object.savedFogStaticData = nil
  -- if set to a base fog factor (between 0 and 1), the fog will be dynamically computed based on latitude, season and time of day
  object.dynamicFogBaseFactor = nil
  -- if true, the dynamic fog system will animate the transitions
  object.dynamicFogIsAnimated = true
end

function VeafFog:new(objectToCopy)
-- LOGGING DISABLED WHEN COMPILING("VeafFog:new()")
  local objectToCreate = objectToCopy or {} -- create object if user does not provide one
  setmetatable(objectToCreate, self)
  self.__index = self

  -- init the new object
  VeafFog.init(objectToCreate)

  return objectToCreate
end

function VeafFog:activate()
-- LOGGING DISABLED WHEN COMPILING("VeafFog[%s]:activate()", veaf.p(self.name))
    veafWeather.setAndActivateFog(self)
end

function VeafFog:enable()
-- LOGGING DISABLED WHEN COMPILING("VeafFog[%s]:enable()", veaf.p(self.name))

    self.enabled = true

    -- store the existing fog parameters
-- LOGGING DISABLED WHEN COMPILING("store the existing fog parameters")
-- LOGGING DISABLED WHEN COMPILING("world.weather.getFogVisibilityDistance()=[%s]", veaf.p(world.weather.getFogVisibilityDistance()))
-- LOGGING DISABLED WHEN COMPILING("world.weather.getFogThickness()=[%s]", veaf.p(world.weather.getFogThickness()))
    self.fogSavedStaticData = {visibility = world.weather.getFogVisibilityDistance(), thickness = world.weather.getFogThickness()}

    -- set the fog to the programmed parameters
-- LOGGING DISABLED WHEN COMPILING("set the fog to the programmed parameters")
    if self.forAnimationData then
-- LOGGING DISABLED WHEN COMPILING("self.forAnimationData=[%s]", veaf.p(self.forAnimationData))
        -- create an animation
        local animation = {
            self.forAnimationData
        }
        -- first reset fog animation
-- LOGGING DISABLED WHEN COMPILING("first reset fog animation")
        world.weather.setFogAnimation({})
-- LOGGING DISABLED WHEN COMPILING("store the existing fog parameters")
-- LOGGING DISABLED WHEN COMPILING("world.weather.getFogVisibilityDistance()=[%s]", veaf.p(world.weather.getFogVisibilityDistance()))
-- LOGGING DISABLED WHEN COMPILING("world.weather.getFogThickness()=[%s]", veaf.p(world.weather.getFogThickness()))
        -- set the new fog animation
        world.weather.setFogAnimation(animation)
    elseif self.fogStaticData then
-- LOGGING DISABLED WHEN COMPILING("self.fogStaticData=[%s]", veaf.p(self.fogStaticData))
        world.weather.setFogThickness(self.fogStaticData.thickness)
        world.weather.setFogVisibilityDistance(self.fogStaticData.visibility)
    end

    -- do the first check, the method will reschedule itself
-- LOGGING DISABLED WHEN COMPILING("do the first check, the method will reschedule itself")
    self:dynamicCheck()

    return self
end

function VeafFog:disable(dontRestore)
-- LOGGING DISABLED WHEN COMPILING("VeafFog[%s]:disable()", veaf.p(self.name))

    -- disable the scheduler
    if self.dynamicCheckFunctionScheduled then
-- LOGGING DISABLED WHEN COMPILING("disable the scheduler")
        mist.removeFunction(self.dynamicCheckFunctionScheduled)
        self.dynamicCheckFunctionScheduled = nil
    end

    self.enabled = false

    if not dontRestore then
        -- reset to the fog values stored at start
-- LOGGING DISABLED WHEN COMPILING("reset to the fog values stored at start")
        if self.fogSavedStaticData then
            world.weather.setFogThickness(self.fogSavedStaticData.thickness)
            world.weather.setFogVisibilityDistance(self.fogSavedStaticData.visibility)
        end
    end

    return self
end

function VeafFog:dynamicCheck()
-- LOGGING DISABLED WHEN COMPILING("VeafFog[%s]:dynamicCheck()", veaf.p(self.name))
    if self.dynamicFogBaseFactor then
        local position = {x=0, y=0, z=0} -- somewhere in the map ^^

        -- compute the fog that should be set at this moment in time
        local date = veafTime.getMissionDateTime()
-- LOGGING DISABLED WHEN COMPILING("date=[%s]", veaf.p(date))

        -- Seasonal adjustment based on latitude and time of year
        local latitude, _, _ = coord.LOtoLL(position)
-- LOGGING DISABLED WHEN COMPILING("latitude=[%s]", veaf.p(latitude))
        local month = date.month
        local seasonal_peaks = { [3] = 0.8, [4] = 0.9, [5] = 0.7, [9] = 0.8, [10] = 0.9, [11] = 0.7 }
        local season_factor = seasonal_peaks[month] or 0.5

        -- Temperature-dew point difference
        local weatherData = veafWeatherData:create(position)
        local temp_diff = math.abs(weatherData.TemperatureCelcius - weatherData.DewPointCelcius)

        -- Diurnal adjustment based on hour
        local diurnal_factor = 0

        -- Convert sunrise and sunset times to minutes since midnight
        local sunrise_time = weatherData.SunriseLocal.hour * 60 + weatherData.SunriseLocal.min
        local sunset_time = weatherData.SunsetLocal.hour * 60 + weatherData.SunsetLocal.min

        -- Calculate daylight duration
        local daylight_duration = sunset_time - sunrise_time

        -- Morning is from sunrise and lasts 25% of daylight
        local morningEnd_time = sunrise_time + 0.25 * daylight_duration
        local morningPeak_time = sunrise_time + 0.125 * daylight_duration
        -- Evening start is 25% before sunset
        local eveningStart_time = sunset_time - 0.25 * daylight_duration
        local eveningPeak_time = sunset_time - 0.125 * daylight_duration

        -- Convert current time to minutes since midnight
        local current_time = date.hour * 60 + date.min

-- LOGGING DISABLED WHEN COMPILING("sunrise_time=[%s:%s]", veaf.p(math.floor(sunrise_time/60)), veaf.p(math.fmod(sunrise_time,60)))
-- LOGGING DISABLED WHEN COMPILING("morningPeak_time=[%s:%s]", veaf.p(math.floor(morningPeak_time/60)), veaf.p(math.fmod(morningPeak_time,60)))
-- LOGGING DISABLED WHEN COMPILING("morningEnd_time=[%s:%s]", veaf.p(math.floor(morningEnd_time/60)), veaf.p(math.fmod(morningEnd_time,60)))
-- LOGGING DISABLED WHEN COMPILING("eveningStart_time=[%s:%s]", veaf.p(math.floor(eveningStart_time/60)), veaf.p(math.fmod(eveningStart_time,60)))
-- LOGGING DISABLED WHEN COMPILING("eveningPeak_time=[%s:%s]", veaf.p(math.floor(eveningPeak_time/60)), veaf.p(math.fmod(eveningPeak_time,60)))
-- LOGGING DISABLED WHEN COMPILING("sunset_time=[%s:%s]", veaf.p(math.floor(sunset_time/60)), veaf.p(math.fmod(sunset_time,60)))
-- LOGGING DISABLED WHEN COMPILING("daylight_duration=[%s]", veaf.p(daylight_duration))
-- LOGGING DISABLED WHEN COMPILING("current_time=[%s:%s]", veaf.p(math.floor(current_time/60)), veaf.p(math.fmod(current_time,60)))

        if current_time >= sunrise_time and current_time < morningPeak_time then
            -- Phase 1: From sunrise to middle of the morning (raise)
-- LOGGING DISABLED WHEN COMPILING("Phase 1: From sunrise to middle of the morning (raise)")
            diurnal_factor = (current_time - sunrise_time) / (morningPeak_time - sunrise_time)
        elseif current_time >= morningPeak_time and current_time < morningEnd_time then
            -- Phase 2: From middle of the morning to end of the morning (decrease)
-- LOGGING DISABLED WHEN COMPILING("Phase 2: From middle of the morning to end of the morning (decrease)")
            diurnal_factor = 1 - (current_time - morningPeak_time) / (morningEnd_time - morningPeak_time)
        elseif current_time >= morningEnd_time and current_time < eveningStart_time then
            -- Phase 3: Day phase (constant base value)
-- LOGGING DISABLED WHEN COMPILING("Phase 3: Day phase (constant base value)")
            diurnal_factor = 0.1
        elseif current_time >= eveningStart_time and current_time < eveningPeak_time then
            -- Phase 4: From start of the evening to middle of the evening (raise)
-- LOGGING DISABLED WHEN COMPILING("Phase 4: From start of the evening to middle of the evening (raise)")
            diurnal_factor = (current_time - eveningStart_time) / (eveningPeak_time - eveningStart_time)
        elseif current_time >= eveningStart_time and current_time < sunset_time then
            -- Phase 5: From middle of the evening to sunset (decrease)
-- LOGGING DISABLED WHEN COMPILING("Phase 5: From middle of the evening to sunset (decrease)")
            diurnal_factor = 1 - (current_time - eveningPeak_time) / (sunset_time - eveningPeak_time)
        end

        -- Base fog probability calculation
        local base_prob = math.max(0, math.min(1, 1 - (temp_diff / 10) - (weatherData.WindSpeedMps / 10)))
        local fog_probability = base_prob * season_factor * diurnal_factor
-- LOGGING DISABLED WHEN COMPILING("weatherData.WindSpeedMps=[%s]", veaf.p(weatherData.WindSpeedMps))
-- LOGGING DISABLED WHEN COMPILING("temp_diff=[%s]", veaf.p(temp_diff))
-- LOGGING DISABLED WHEN COMPILING("base_prob=[%s]", veaf.p(base_prob))
-- LOGGING DISABLED WHEN COMPILING("season_factor=[%s]", veaf.p(season_factor))
-- LOGGING DISABLED WHEN COMPILING("diurnal_factor=[%s]", veaf.p(diurnal_factor))
-- LOGGING DISABLED WHEN COMPILING("fog_probability=[%s]", veaf.p(fog_probability))

        -- Compute visibility and thickness based on fog_probability with smooth transitions
        local visibility, thickness
        if fog_probability < 0.2 then
            visibility = 50000 * (1 - fog_probability)  -- High visibility as fog_probability decreases
            thickness = 0  -- No fog, so no thickness
        else
            -- Normalize the fog factor relative to the 0.2-1.0 range
            local normalizedFactor = (fog_probability - 0.2) / 0.8

            local minVisibility = 100 * (1 - self.dynamicFogBaseFactor)
            local maxVisibility = 5000 * (1 - self.dynamicFogBaseFactor)
            local minThickness = 100 * self.dynamicFogBaseFactor
            local maxThickness = 500 * self.dynamicFogBaseFactor
-- LOGGING DISABLED WHEN COMPILING("minVisibility=[%s]", veaf.p(minVisibility))
-- LOGGING DISABLED WHEN COMPILING("maxVisibility=[%s]", veaf.p(maxVisibility))
-- LOGGING DISABLED WHEN COMPILING("minThickness=[%s]", veaf.p(minThickness))
-- LOGGING DISABLED WHEN COMPILING("maxThickness=[%s]", veaf.p(maxThickness))

            -- Calculate visibility (decreasing from 5000 to 100)
            visibility = maxVisibility - ((maxVisibility - minVisibility) * normalizedFactor)

            -- Calculate thickness (increasing from 100 to 1000)
            thickness = minThickness + ((maxThickness - minThickness) * normalizedFactor)
        end

        visibility = math.floor(visibility)
        thickness = math.floor(thickness)
-- LOGGING DISABLED WHEN COMPILING("thickness=[%s]", veaf.p(thickness))
-- LOGGING DISABLED WHEN COMPILING("visibility=[%s]", veaf.p(visibility))

        if self.dynamicFogIsAnimated then
            -- create an animation
-- LOGGING DISABLED WHEN COMPILING("thickness=[%s]", veaf.p(thickness))
            local animation = {
                VeafFog.DELAY_BETWEEN_DYNAMIC_CHECKS - VeafFog.DELAY_BETWEEN_DYNAMIC_CHECKS*0.1, visibility, thickness
            }
-- LOGGING DISABLED WHEN COMPILING("animation=[%s]", veaf.p(animation))

            -- first reset fog animation
            world.weather.setFogAnimation({})
            -- set the new fog animation
            world.weather.setFogAnimation(animation)
        else
            world.weather.setFogThickness(thickness)
            world.weather.setFogVisibilityDistance(visibility)
        end
    end

    -- reschedule
    self.dynamicCheckFunctionScheduled = mist.scheduleFunction(VeafFog.dynamicCheck, {self}, timer.getTime() + VeafFog.DELAY_BETWEEN_DYNAMIC_CHECKS)
end

function veafWeather.createStaticFog(name, thickness, visibility)
    local fog = VeafFog:new()
    fog.name = name
    fog.fogStaticData = { thickness = thickness, visibility = visibility}
    return fog
end

function veafWeather.createDynamicFog(name, baseFactor, notAnimated)
    local fog = VeafFog:new()
    fog.name = name
    fog.dynamicFogBaseFactor = baseFactor
    fog.dynamicFogIsAnimated = not notAnimated
    return fog
end

function veafWeather.createAnimatedFog(name, minutes, thickness, visibility)
    local fog = VeafFog:new()
    fog.name = name
    fog.forAnimationData = {minutes * 60, visibility, thickness}
    return fog
end

function veafWeather.setAndActivateFog(fogObject)
-- LOGGING DISABLED WHEN COMPILING("fogObject=[%s]", veaf.p(fogObject))

    -- disable the existing fog object if any
    if veafWeather.existingFog ~= nil then
-- LOGGING DISABLED WHEN COMPILING("disable the existing fog object if any")
-- LOGGING DISABLED WHEN COMPILING("veafWeather.existingFog=[%s]", veaf.p(veafWeather.existingFog))
        veafWeather.existingFog:disable(true)
    end

    -- activate the new fog object
-- LOGGING DISABLED WHEN COMPILING("activate the new fog object")
    veafWeather.existingFog = fogObject
    fogObject:enable()

    trigger.action.outText("Fog set to "..fogObject.name, 5)

    return fogObject
end

-- dynamically managed fog instances
veafWeather.FOG_DYNAMIC_HEAVY     = veafWeather.createDynamicFog("Dynamic HEAVY fog", VeafFog.DYNAMICFOG_BASEFACTOR_HEAVY)
veafWeather.FOG_DYNAMIC_MEDIUM    = veafWeather.createDynamicFog("Dynamic MEDIUM fog", VeafFog.DYNAMICFOG_BASEFACTOR_MEDIUM)
veafWeather.FOG_DYNAMIC_SPARSE    = veafWeather.createDynamicFog("Dynamic SPARSE fog", VeafFog.DYNAMICFOG_BASEFACTOR_SPARSE)

-- static fog instances
veafWeather.FOG_STATIC_HEAVY      = veafWeather.createStaticFog("Static HEAVY fog", 500, 100)
veafWeather.FOG_STATIC_MEDIUM     = veafWeather.createStaticFog("Static MEDIUM fog", 500, 500)
veafWeather.FOG_STATIC_MEDIUM_LOW = veafWeather.createStaticFog("Static MEDIUM LOW fog", 100, 500)
veafWeather.FOG_STATIC_SPARSE     = veafWeather.createStaticFog("Static SPARSE fog", 500, 5000)
veafWeather.FOG_STATIC_SPARSE_LOW = veafWeather.createStaticFog("Static SPARSE LOW fog", 100, 5000)
veafWeather.FOG_STATIC_NO         = veafWeather.createStaticFog("Static NO fog", 0, 0)

-- animated fog instances
for _, minutes in pairs({1, 5, 10, 15, 30, 60, 90}) do
    local overMinutesText = string.format(" over %d minutes", minutes)
    veafWeather["FOG_ANIMATED_"..minutes.."M_HEAVY"]      = veafWeather.createAnimatedFog("Animated HEAVY fog"..overMinutesText, minutes, 500, 100)
    veafWeather["FOG_ANIMATED_"..minutes.."M_MEDIUM"]     = veafWeather.createAnimatedFog("Animated MEDIUM fog"..overMinutesText, minutes, 500, 500)
    veafWeather["FOG_ANIMATED_"..minutes.."M_MEDIUM_LOW"] = veafWeather.createAnimatedFog("Animated MEDIUM LOW fog"..overMinutesText, minutes, 100, 500)
    veafWeather["FOG_ANIMATED_"..minutes.."M_SPARSE"]     = veafWeather.createAnimatedFog("Animated SPARSE fog"..overMinutesText, minutes, 500, 5000)
    veafWeather["FOG_ANIMATED_"..minutes.."M_SPARSE_LOW"] = veafWeather.createAnimatedFog("Animated SPARSE LOW fog"..overMinutesText, minutes, 100, 5000)
    veafWeather["FOG_ANIMATED_"..minutes.."M_NO"]         = veafWeather.createAnimatedFog("Animated NO fog"..overMinutesText, minutes, 0, 0)
end

---------------------------------------------------------------------------------------------------
---  Radio menu and remote interface
-------------------------------------------------------------------------------------------------------------------------------------------------------------

--- Build the initial radio menu
function veafWeather.buildRadioMenu()
-- LOGGING DISABLED WHEN COMPILING("buildRadioMenu()")

    veafWeather.rootPath = veafRadio.addMenu(veafWeather.RadioMenuName)
    veafRadio.addCommandToSubmenu("Weather on closest point" , veafWeather.rootPath, veafWeather.messageWeatherAtClosestPoint, nil, veafRadio.USAGE_ForGroup)
    veafRadio.addCommandToSubmenu("ATC on closest airbase" , veafWeather.rootPath, veafWeather.messageAtcClosestAirbase, nil, veafRadio.USAGE_ForGroup)
    veafRadio.addCommandToSubmenu("ATC and weather in one go" , veafWeather.rootPath, veafWeather.messageAtcAndWeather, nil, veafRadio.USAGE_ForGroup)

    local fogPath = veafRadio.addSubMenu("Fog settings", veafWeather.rootPath)

    local dynamicFogPath = veafRadio.addSubMenu("Dynamic fog", fogPath)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_DYNAMIC_HEAVY.name, dynamicFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_DYNAMIC_HEAVY, veafRadio.USAGE_ForAll)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_DYNAMIC_MEDIUM.name, dynamicFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_DYNAMIC_MEDIUM, veafRadio.USAGE_ForAll)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_DYNAMIC_SPARSE.name, dynamicFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_DYNAMIC_SPARSE, veafRadio.USAGE_ForAll)

    local animatedFogPath = veafRadio.addSubMenu("Animated fog", fogPath)
    for _, minutes in pairs({1, 5, 10, 15, 30, 60, 90}) do
        local overMinutesText = string.format(" over %d minutes", minutes)
        local _path = veafRadio.addSubMenu("Animated fog"..overMinutesText, animatedFogPath)
        veafRadio.addSecuredCommandToSubmenu(veafWeather["FOG_ANIMATED_"..minutes.."M_HEAVY"].name, _path, veafWeather.setAndActivateFog, veafWeather["FOG_ANIMATED_"..minutes.."M_HEAVY"], veafRadio.USAGE_ForAll)
        veafRadio.addSecuredCommandToSubmenu(veafWeather["FOG_ANIMATED_"..minutes.."M_MEDIUM"].name, _path, veafWeather.setAndActivateFog, veafWeather["FOG_ANIMATED_"..minutes.."M_MEDIUM"], veafRadio.USAGE_ForAll)
        veafRadio.addSecuredCommandToSubmenu(veafWeather["FOG_ANIMATED_"..minutes.."M_MEDIUM_LOW"].name, _path, veafWeather.setAndActivateFog, veafWeather["FOG_ANIMATED_"..minutes.."M_MEDIUM_LOW"], veafRadio.USAGE_ForAll)
        veafRadio.addSecuredCommandToSubmenu(veafWeather["FOG_ANIMATED_"..minutes.."M_SPARSE"].name, _path, veafWeather.setAndActivateFog, veafWeather["FOG_ANIMATED_"..minutes.."M_SPARSE"], veafRadio.USAGE_ForAll)
        veafRadio.addSecuredCommandToSubmenu(veafWeather["FOG_ANIMATED_"..minutes.."M_SPARSE_LOW"].name, _path, veafWeather.setAndActivateFog, veafWeather["FOG_ANIMATED_"..minutes.."M_SPARSE_LOW"], veafRadio.USAGE_ForAll)
        veafRadio.addSecuredCommandToSubmenu(veafWeather["FOG_ANIMATED_"..minutes.."M_NO"].name, _path, veafWeather.setAndActivateFog, veafWeather.FOG_ANIMATED_5_NO, veafRadio.USAGE_ForAll)
    end

    local staticFogPath = veafRadio.addSubMenu("Static fog", fogPath)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_STATIC_HEAVY.name, staticFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_STATIC_HEAVY, veafRadio.USAGE_ForAll)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_STATIC_MEDIUM.name, staticFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_STATIC_MEDIUM, veafRadio.USAGE_ForAll)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_STATIC_MEDIUM_LOW.name, staticFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_STATIC_MEDIUM_LOW, veafRadio.USAGE_ForAll)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_STATIC_SPARSE.name, staticFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_STATIC_SPARSE, veafRadio.USAGE_ForAll)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_STATIC_SPARSE_LOW.name, staticFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_STATIC_SPARSE_LOW, veafRadio.USAGE_ForAll)
    veafRadio.addSecuredCommandToSubmenu(veafWeather.FOG_STATIC_NO.name, staticFogPath, veafWeather.setAndActivateFog, veafWeather.FOG_STATIC_NO, veafRadio.USAGE_ForAll)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- remote interface
-------------------------------------------------------------------------------------------------------------------------------------------------------------

-- execute command from the remote interface
function veafWeather.executeCommandFromRemote(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("veafWeather.executeCommandFromRemote()"))
-- LOGGING DISABLED WHEN COMPILING(string.format("parameters= %s", veaf.p(parameters)))
    local _pilot, _pilotName, _unitName, _command = unpack(parameters)
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilot= %s", veaf.p(_pilot)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_pilotName= %s", veaf.p(_pilotName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_unitName= %s", veaf.p(_unitName)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_command= %s", veaf.p(_command)))
    if not _pilot or not _command then
        return false
    end

    if _command then
        -- parse the command
        local _action, _name, _parameters = _command:match(veafWeather.RemoteCommandParser)
-- LOGGING DISABLED WHEN COMPILING(string.format("_action=%s",veaf.p(_action)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_name=%s",veaf.p(_name)))
-- LOGGING DISABLED WHEN COMPILING(string.format("_parameters=%s",veaf.p(_parameters)))
        if _action and _action:lower() == "weather" then
            veaf.loggers.get(veafWeather.Id):info(string.format("[%s] is requesting weather",veaf.p(_pilotName)))
            veafWeather.messageWeatherAtClosestPoint(_unitName, true)
            return true
        elseif _action and _action:lower() == "atc" then
            veaf.loggers.get(veafWeather.Id):info(string.format("[%s] is requesting atc",veaf.p(_pilotName)))
            veafWeather.messageAtcClosestAirbase(_unitName, true)
            return true
        elseif not _action or _action:lower() == "all" then
            veaf.loggers.get(veafWeather.Id):info(string.format("[%s] is requesting both atc and weather",veaf.p(_pilotName)))
            veafWeather.messageAtcAndWeather(_unitName, true)
            return true
        elseif _action and _action:lower() == "fog" then
            if _name then
                local uName = _name:upper()
                local fogObject = veafWeather[uName]
                if fogObject then
                    veaf.loggers.get(veafWeather.Id):info(string.format("[%s] is requesting fog [%s]",veaf.p(_pilotName), veaf.p(uName)))
                    veafWeather.setAndActivateFog(fogObject)
                    return true
                end
            end
        end
    end
    return false
end

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
---  MAIN MODULE INITIALIZATION
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------

function veafWeather.initialize()
-- LOGGING DISABLED WHEN COMPILING("veafWeather.initialize()")
    veafWeather.buildRadioMenu()
    -- TODO veafMarkers.registerEventHandler(veafMarkers.MarkerChange, veafWeather.onEventMarkChange)
    veafAirbases.initialize()
end

veaf.loggers.get(veafWeather.Id):info(string.format("Loading version %s", veafWeather.Version))

---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
-------------------- TEST STUFF--------------------------------------------------------------------
--[[
.getFogThickness=[function]
.getFogVisibilityDistance=[function]
.setFogAnimation=[function]
.setFogThickness=[function]
.setFogVisibilityDistance=[function]

veafAirbases.initialize()
for _, veafAirbase in pairs(veafAirbases.Airbases) do
-- LOGGING DISABLED WHEN COMPILING(veafWeatherAtis.getAtisString(veafAirbase))
-- LOGGING DISABLED WHEN COMPILING(veafWeatherData.getWeatherString(veafAirbase.DcsAirbase:getPoint()))
end
-- LOGGING DISABLED WHEN COMPILING(veaf.p(env.mission.weather.enable_fog))
-- LOGGING DISABLED WHEN COMPILING(veaf.p(world.weather.getFogVisibilityDistance()))
-- LOGGING DISABLED WHEN COMPILING(veaf.p(world.weather.getFogThickness()))
]]

------------------ END script veafWeather.lua  ------------------




-----------------------------------------------------------------------------------
-- END OF Veaf scripts 5.75.1;2025.01.04.11.18.12
-----------------------------------------------------------------------------------

