--[[ GROUND UNITS AI ENHANCEMENT -- AI-EN

This script is possible thanks to the existence of many other scripts that I had the opportunity to use as inspiration, or partial copy, or modification due to being publicly available.
I basically learnt coding starting with mist, CTLD, SLmod, dismount script, and else. All credits goes to these authors, and I am very grateful. Therefore this script is obviously public
and you should feel free to use it totally, partially or anything else for any DCS gaming purpose.

What is it?
This script is done with the purpose of rehearsing the standard DCS ground groups and units AI, 
implementing additional behaviours using the SSE, aiming to get some more dynamic and realistic
response of those units to the environment and users actions.

The script currently is focused on ground units, except SAM and medium-to-long range air defences which are not being commanded.

This works mostly by changing 2 common behaviours: 
- the fact the the units basically do not react to any hostile fire moving elsewhere: the script provide movement decision
- the fact that artillery units fire only when they directly achieve a target, which is mostly unlikely: the script provide targets on arty groups via allied units informations

It also act on another important thing on ground war: dismountable soldiers' teams. By rehearsing an idea of MBot dismount script,
AIEN add soldier teams to any IFV, APC and trucks unit in the scenery. That means that a ground unit normally "unarmed" like a M-939 
might in fact transport RPG, rifle and even manpads units that will dismount from it when necessary to respond to specific menace.

In the end, if you need a specific group or more to do NOT be altered by the script, you simply have to add the exclusion tag (AIEN_xcl_tag string variable) in their group name: they will be completely ignored.


Usage informations:
- The script does not require any other script to run;
- Load the script in a "do script file" trigger after at least 3 seconds from mission start;
- The script does not need set-up or additional coding besides the user customization variables setup below;
- It won't be properly compatible with any other script that try to change the AI behaviours for ground movers. It should be ok with IADS scripts;
- It alter the ground groups behaviour and any change will effectively "broke" any other actions given by DCS or CA user when kicking in (and that's just normal and ok).
- The script do not use any naming convention (except for the exclusion tag), it will define available AI behaviour change using DCS available parameters such as unit attributes, skills, etc.
- you will be able to customize each of its "features", by enabling and disabling each option you want to use, here below  

Purpose and limitations:
The script does not want to provide a complete "automated ground war" thing: this is already in development by both ED and also by DSMC script by me. 
In fact, this script is a part of the DSMC script, but still usable externally as standalone.

Suggestion, ideas:
-- Please refer to the script GitHub project, you will find the project details to suggest modification, contribute with coding and else.


--]]--

--## GLOBAL GENERAL AIEN CONTENT TABLE
AIEN                                	= {}

--## USER CUSTOMIZATION VARIABLES ##
AIEN.config = {}
AIEN.config.dontInitialize      = true     -- if true, AIEN will not initialize; instead, you'll have to run it from your own code - it's useful when you want to override some functions/parameters before the initialization takes place

-- coalition affected by the script
AIEN.config.blueAI 		        = true 		-- true/false. If true, the AI enhancement will be applied to the blue coalition ground groups, else, no script effect will take place
AIEN.config.redAI			    = true 		-- true/false. If true, the AI enhancement will be applied to the red  coalition ground groups, else, no script effect will take place

-- Action sets allowed.
AIEN.config.firemissions        = true      -- true/false. If true, each artillery in the coalition will fire automatically at available targets provided by other ground units and drones
AIEN.config.reactions           = true      -- true/false. If true, when a mover group gets an hit, it will react accordingly to its skills and to its situational awareness, not staying there taking hits without doing nothing
AIEN.config.suppression         = true      -- true/false. If true, once a group take fire from arty or air and it's not armoured, it will be suppressed for 15-45 seconds and won't return fire. Require reactions to be set as 'true'
AIEN.config.dismount 		    = true 		-- true/false. //BEWARE: CAN AFFECT PERFORMANCES ON LOW END SYSTEMS // Thanks to MBot's original script, if true AI ground units with infantry transport capabilities (mainly APC/IFV/Trucks) will dismount soldiers with rifle, rpg and sometimes mandpads when appropriate

-- User advanced customization
AIEN.config.AIEN_xcl_tag		= "XCL" 	-- string, global, case sensitive. Can be dynamically changed by other script or triggers, since it's a global variable. used as a text format without spaces or special characters. only letters and numbers allowed. Any ground group with this 'tag' in its group name won't get AI enhancement behaviour, regardless of its coalition 
AIEN.config.AIEN_icl_tag		= nil    	-- string, global, case sensitive. Can be dynamically changed by other script or triggers, since it's a global variable. used as a text format without spaces or special characters. only letters and numbers allowed. Any ground group with this 'tag' in its group name will get AI enhancement behaviour; setting this variable disables the automatic inclusion of all groups in the mission. This is useful if you want to have a specific group to be affected by AIEN script behaviours, regardless of its coalition. If set, it will override the coalition settings above.
AIEN.config.AIEN_zoneFilter     = ""    	-- string, global, case sensitive. Can be dynamically changed by other script or triggers, since it's a global variable. used as a text format without spaces or special characters. only letters and numbers allowed, i.e. "AIEN" will fit. If left nil, or void string like "", won't be used. Only groups inside the named trigger zone will be affected by AIEN script behaviors of reaction, dismount and suppression, and vice versa. If no trigger zone with the specific name is in the mission, then all the groups will use AIEN features.
AIEN.config.message_feed        = true 		-- true/false. If true, each relevant AI action starting will also create a trigger message feedback for its coalition
AIEN.config.mark_on_f10_map     = true 	    -- true/false. If true, when an artillery fire mission is ongoing, a markpoint will appear on the map of the allied coalition to show the expected impact point
AIEN.config.skill_action_const  = false     -- true/false. If true, AI available reactions types will be limited by the group average skill. If not, almost 2/3 of all available actions will be always be available regardless of the group skills

-- User bug report: prior to report a bug, please try reproducing it with this variable set to "debug" (uncomment the commented line below).
AIEN.LogLevel = "info"
--AIEN.LogLevel = "trace"

--## LOCAL HIGH LEVEL VARIABLES ##
-- changing the variable below is for fine customization, but it's not recommended cause it can change the code behaviour. 
-- If you do so, please revert to original value and retry before reporting bugs.

-- movement variables
AIEN.config.outRoadSpeed                      = 8	              -- do *3.6 for km/h, cause DCS thinks in m/s	
AIEN.config.inRoadSpeed                       = 15	              -- do *3.6 for km/h, cause DCS thinks in m/s
AIEN.config.infantrySpeed                     = 2	              -- do *3.6 for km/h, cause DCS thinks in m/s	
AIEN.config.repositionDistance				  = 500		          -- meters, radius to a specific destination point that will be randomized between 90% and 110% of this value. Used when a group is moved upon another group position: the other group position will be the destination.
AIEN.config.rndFleeDistance		              = 2000 		      -- meters, reposition distance given to a group when a destination is not defined. The direction also will be totally random. Used, i.e., for "panic" reaction

-- dismounted troops variables
AIEN.config.droppedReposition                 = 80                -- if no enemy is identified, this is the distance where dismount group will reposition themselves
AIEN.config.remountTime                       = 600               -- time after which dismounted troops will try to go back to their original vehicle for remount, if commanded
AIEN.config.infantryExtractDist               = 200               -- max distance from vehicle to troops to allow a group extraction
AIEN.config.infantrySearchDist                = 2000              -- max distance from vehicle to troops to allow a dismount group to run toward the enemies

-- informative calls variables
AIEN.config.outAmmoLowLevel                   = 0.5		          -- factor on total amount

-- reactions and tasking variables
AIEN.config.intelDbTimeout                    = 1200              -- seconds. Used to cancel intelDb entries for units (not static!), when the time of the contact gathering is more than this value
AIEN.config.artyFireLastContactThereshold     = 300               -- seconds, max amount of time since last contact to consider an arty target ok
AIEN.config.taskTimeout                       = 480               -- seconds after which a tasked group is removed from the database
AIEN.config.targetedTimeout                   = 240               -- seconds after which a targeted variable in inteldb is removed from database
AIEN.config.disperseActionTime				  = 120		          -- seconds
AIEN.config.counterBatteryRadarRange          = 50000             -- m, capable distance for a radar to perform counter battery calculations
AIEN.config.counterBatteryPlanDelay           = 240               -- s, will be also randomized on +-35%. Used to define the delay of the planned counter battery fire if available
AIEN.config.smoke_source_num                  = 5                 -- number, between 4 and 9. Generated smokes for each unit when smoke reaction is called in. Any number below 4 or above 9 will be converted in the nearest threshold

-- SA evaluation variables
AIEN.config.proxyBuildingDistance			  = 4000              -- m, if buildings are within this distance value, they are considered "close"
AIEN.config.proxyUnitsDistance                = 5000              -- m, if units are within this distance value, they are considered "close"
AIEN.config.supportDistance					  = 8000			  -- m, maximum distance for evaluating support or cover movements when under attack
AIEN.config.withrawDist                       = 15000             -- m, maximum distance for withdraw manoeuvre nearby a friendly support unit



--####################################################################################################
--###### DO NOT CHANGE CODE BELOW HERE ###############################################################
--####################################################################################################

--###### LOGGING ############################################################################

-- The code below is used to log the information in the script.
-- The log is saved in the DCS log folder, and it is possible to set the level of logging for each module.
-- The logging system is _pluggable_, meaning that you can use any logging system you want by registering a new logger instead of the default one
AIEN.Id = "AIEN"

AIEN.Logger =
{
    -- technical name
    name = nil,
    -- logging level
    level = nil,
}
AIEN.Logger.__index = AIEN.Logger

AIEN.Logger.LEVEL = {
    ["error"]=1,
    ["warning"]=2,
    ["info"]=3,
    ["debug"]=4,
    ["trace"]=5,
}

function AIEN.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}", AIEN.p(o.x), AIEN.p(o.z), AIEN.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}", AIEN.p(o.x), AIEN.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 AIEN._p(o, level, skip, includeMeta, dontRecurse)
end

function AIEN._p(o, level, skip, includeMeta, dontRecurse)
    local MAX_LEVEL = 20
    if level == nil then level = 0 end
    if level > MAX_LEVEL then
        AIEN.loggers.get(AIEN.Id):error("max depth reached in AIEN.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.."="..AIEN.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.."="..AIEN.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 AIEN.Logger:new(name, level)
    local self = setmetatable({}, AIEN.Logger)
    self:setName(name)
    self:setLevel(level)
    return self
end

function AIEN.Logger:setName(value)
    self.name = value
    return self
end

function AIEN.Logger:getName()
    return self.name
end

function AIEN.Logger:setLevel(value, force)
    local level = value
    if type(level) == "string" then
        level = AIEN.Logger.LEVEL[level:lower()]
    end
    if not level then
        level = AIEN.Logger.LEVEL["info"]
    end
    self.level = level
    return self
end

function AIEN.Logger:getLevel()
    return self.level
end

function AIEN.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 AIEN.Logger.formatText(text, ...)
    if not text then
        return ""
    end
    if type(text) ~= 'string' then
        text = AIEN.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] = AIEN.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 AIEN.Logger:print(level, text)
    local texts = AIEN.Logger.splitText(text)
    local levelChar = 'E'
    local logFunction = env.error
    if level == AIEN.Logger.LEVEL["warning"] then
        levelChar = 'W'
        logFunction = env.warning
    elseif level == AIEN.Logger.LEVEL["info"] then
        levelChar = 'I'
        logFunction = env.info
    elseif level == AIEN.Logger.LEVEL["debug"] then
        levelChar = 'D'
        logFunction = env.info
    elseif level == AIEN.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 AIEN.Logger:error(text, ...)
    if self.level >= 1 then
        text = AIEN.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 AIEN.Logger:warn(text, ...)
    if self.level >= 2 then
        text = AIEN.Logger.formatText(text, arg)
        self:print(2, text)
    end
end

function AIEN.Logger:info(text, ...)
    if self.level >= 3 then
        text = AIEN.Logger.formatText(text, arg)
        self:print(3, text)
    end
end

function AIEN.Logger:debug(text, ...)
    if self.level >= 4 then
        text = AIEN.Logger.formatText(text, arg)
        self:print(4, text)
    end
end

function AIEN.Logger:trace(text, ...)
    if self.level >= 5 then
        text = AIEN.Logger.formatText(text, arg)
        self:print(5, text)
    end
end

function AIEN.Logger:wouldLogWarn()
    return self.level >= 2
end

function AIEN.Logger:wouldLogInfo()
    return self.level >= 3
end

function AIEN.Logger:wouldLogDebug()
    return self.level >= 4
end

function AIEN.Logger:wouldLogTrace()
    return self.level >= 5
end

AIEN.loggers = {}
AIEN.loggers.dict = {}

function AIEN.loggers.new(loggerId, level)
    if not loggerId or #loggerId == 0 then
        return nil
    end
    local result = AIEN.Logger:new(loggerId:upper(), level)
    AIEN.loggers.dict[loggerId:lower()] = result
    return result
end

function AIEN.loggers.get(loggerId)
    local result = nil
    if loggerId and #loggerId > 0 then
        result = AIEN.loggers.dict[loggerId:lower()]
    end
    if not result then
        result = AIEN.loggers.get("AIEN")
    end
    return result
end

AIEN.loggers.new(AIEN.Id, AIEN.LogLevel)

--###### CONFIG AND VARIABLES ########################################################################

--## MAIN VARIABLES

-- Mark id addition
local markIdStart                       = 12345000000

-- DSMC version of the script check: if already there due to DSMC version, the script won't be loaded.
if AIEN.performPhaseCycle then
    env.error("AIEN already there in another way, stopping")
    return
end

--## LOCAL GENERAL INFORMATIONS VARIABLES (mostly used for debug log and info)
local ModuleName  						= "AIEN"
local MainVersion 						= "1"
local SubVersion 						= "0"
local Build 							= "0154-VEAF-2025.04.29"
local Date								= "2025.04.13"

--## NOT USED (YET) / TO BE REMOVED
local resumeRouteTimer                  = 300				-- seconds
local supportDist                       = 20000             -- m of distance max between objective point and group
local resupplyMaxDist                   = 200000            -- m of distance for the maximum allowed resupply route.
local soldierWeight                     = 110               -- weight of a soldiers in kg
local f10menuUpdateFreq                 = 10                -- F10 menù refresh rate
local baseArtyRange                     = 15000             -- m when AIEN.tblThreatsRange is not available  - QUIIII
local artilleryFrequencyFire            = 10*60             -- time between two salvo fires
local proxyVegetationDistance           = 2000              -- m, if trees or vegetation are whitin this distance value, they are considered "close" -- // CURRENTLY NOT POSSIBLE IN DCS
local infantryDismountRange             = 1000              -- distance from enemy that trigger troops dismount

--## LOCAL LOW LEVEL VARIABLES

--Debug 
local AIEN_io 					    	= _G.io  	        -- check if io  is available in mission environment, if so debug will also produce files. NOT-RECOMMENDED to have an unsanitized mission env.
local AIEN_lfs 					    	= _G.lfs		    -- check if lfs is available in mission environment, if so debug will also produce files. NOT-RECOMMENDED to have an unsanitized mission env.

-- FSM and system
local PHASE                             = "Initialization"  -- used by FSM, don't change, it won't affect anything
local phase_index                   	= nil
local phase_keys                        = {}
local phaseCycleTimer                   = 0.2               -- seconds, used by FSM. Define how much time pass between a loop entry calculation and another. You might want to reduce it further or if you feel DCS being "slow" you can raise up to 1.0 second. 
local rndMinRT_xper                     = 2                 -- seconds counted as minimum basic reaction time after an event (beware, reaction time also depends on group averaged skill)
local rndMacRT_xper                     = 4                 -- seconds counted as maximum basic reaction time after an event (beware, reaction time also depends on group averaged skill)
local stupidIndex                       = 1                 -- used to avoid infinite loops

--AI processing timers
local underAttack                       = {}                -- used when a group has been attacked, it keeps "tactical" tasking off for 10 mins leaving room for "reaction" decision making


--Dynamic and_or linked to other code
if not DSMC_baseGcounter then
	DSMC_baseGcounter = 20000000
end
if not DSMC_baseUcounter then
	DSMC_baseUcounter = 19000000
end

--## LOCAL DYNAMIC TABLES (DBs)

local groundgroupsDb    = {} -- used for general purpose on groups command
local droneunitDb       = {} -- used mostly for artillery control
local intelDb           = {} -- used for any enemy assessment evaluation. The getSA function is used for populating the db
local mountedDb         = {} -- used for assign infantry teams to each capable vehicle or trucks
local infcarrierDb      = {} -- used for store infantry carriers informations (i.e. available space)

--## LOCAL STATIC TABLES

-- used for prioritizing arty target by class
local classPriority     = {
    ["MLRS"] = 10,
    ["ARTY"] = 9.5,
    ["MBT"] = 9,
    ["ATGM"] = 8,
    ["SAM"] = 9.6,
    ["SHORAD"] = 7.5,
    ["IFV"] = 7,
    ["APC"] = 4,
    ["RECCE"] = 3.5,
    ["AAA"] = 3,
    ["MISSILE"] = 4.5,
    ["MANPADS"] = 6,
    ["LOGI"] = 2,
    ["INF"] = 1,
    ["UNKN"] = 0.5,
    ["ARBN"] = 0,    
    ["SHIP"] = 2,  
}

-- used to identify if a group is suitable for supporting others in ground battle
local supportGroundClasses  = {
    ["MLRS"] = 2.1,
    ["ARTY"] = 2.3,
    ["MBT"] = 10,
    ["ATGM"] = 9,
    ["SAM"] = 3,
    ["SHORAD"] = 5,
    ["IFV"] = 7,
    ["APC"] = 5.5,
    ["RECCE"] = 3.5,
    ["AAA"] = 2,
    ["MISSILE"] = 1,
    ["MANPADS"] = 0.8,
    ["LOGI"] = 1.2,
    ["INF"] = 0.5,
    ["UNKN"] = 0.9,
    --["ARBN"] = 0,
    --["SHIP"] = 0,  
}

-- used to identify if a group is suitable for supporting others in ground battle
local supportCounterAirClasses  = {
    ["MLRS"] = 0.5,
    ["ARTY"] = 1,
    ["MBT"] = 4,
    ["ATGM"] = 5,
    ["SAM"] = 10,
    ["SHORAD"] = 9,
    ["IFV"] = 4.5,
    ["APC"] = 3,
    ["RECCE"] = 2.5,
    ["AAA"] = 7,
    ["MISSILE"] = 0.2,
    ["MANPADS"] = 8,
    ["LOGI"] = 1.2,
    ["INF"] = 0.1,
    ["UNKN"] = 2,
    --["ARBN"] = 0,
    --["SHIP"] = 0,  
}

-- used in multiple part of the scripts for defining reactions, speed, and smartness of the groups
local skills = {
    ["Average"] = {aim_delay = 170, skillVal = 4},
    ["High"] = {aim_delay = 130, skillVal = 9},
    ["Good"] = {aim_delay = 150, skillVal = 6},
    ["Excellent"] = {aim_delay = 110, skillVal = 12},   
	["Random"] = {aim_delay = 140, skillVal = 8},  -- skill val is NOT used in this case, it's replaced by a randomness.
}

-- used for define infantry teams carrier capacity by attribute. BEWARE: the table key are not "classes", are actual DCS attributes enum. Don't add "casual" things, stick to them. You can find a list here: https://wiki.hoggitworld.com/view/DCS_enum_attributes 
local dismountCarriers = {
    ["Trucks"] = 16,    -- this should be handled as a 3 teams of 4 each
    ["APC"] = 8,        -- this should be handled as a 2 teams of 4 each
    ["IFV"] = 5,        -- this should be handled as a 1 teams of 4 each
}

-- used for define infantry teams composition. In the table, p is the probability from 1 to 100, c is the composition
local dismountTeamsWest = { 
    ["rifle"] = {id = "rifle", p = 55, c = {
        [1] = "Soldier M4",
        [2] = "Soldier M4",
        [3] = "Soldier M4",
        [4] = "Soldier M249",
    }},
    ["mixed"] = {id = "mixed", p = 20, c = {
        [1] = "Soldier M4",
        [2] = "Paratrooper RPG-16",
        [3] = "Soldier M4",
        [4] = "Soldier M249",
    }},-- mixed is actually 3 rifle and 1 rpg      
    ["RPGs"] = {id = "RPGs", p = 9, c = {
        [1] = "Paratrooper RPG-16",
        [2] = "Paratrooper RPG-16",
        [3] = "Soldier M4",
        [4] = "Soldier M4",
    }},              
    ["manpads"] = {id = "manpads", p = 3, c = {
        [1] = "Stinger manpad",
        [2] = "Stinger manpad",
        [3] = "Soldier M4",
    }},  
}

local dismountTeamsEast = { 
    ["rifle"] = {id = "rifle", p = 55, c = {
        [1] = "Infantry AK ver3",
        [2] = "Infantry AK ver2",
        [3] = "Infantry AK ver2",
        [4] = "Infantry AK ver3",
    }},
    ["mixed"] = {id = "mixed", p = 20, c = {
        [1] = "Infantry AK ver2",
        [2] = "Paratrooper RPG-16",
        [3] = "Infantry AK ver3",
        [4] = "Infantry AK ver3",
    }},-- mixed is actually 3 rifle and 1 rpg      
    ["RPGs"] = {id = "RPGs", p = 9, c = {
        [1] = "Paratrooper RPG-16",
        [2] = "Paratrooper RPG-16",
        [3] = "Infantry AK ver2",
        [4] = "Infantry AK ver3",
    }},               
    ["manpads"] = {id = "manpads", p = 3, c = {
        [1] = "SA-18 Igla manpad",
        [2] = "SA-18 Igla manpad",
        [3] = "Infantry AK ver3",
    }},  
}

if env.mission and env.mission.date and env.mission.date.Year then
    local y = tonumber(env.mission.date.Year)

    AIEN.loggers.get(AIEN.Id):trace("AIEN mission date: %s", y)
    

    if y < 1980 then
        dismountTeamsWest["manpads"] = nil
        AIEN.loggers.get(AIEN.Id):trace("AIEN removed stinger")
        
    elseif y < 1970 then
        dismountTeamsEast["manpads"] = nil
        dismountTeamsWest["manpads"] = nil
        AIEN.loggers.get(AIEN.Id):trace("AIEN removed all manpads")
        
    end
end



--## LINKED TABLES (or local if not available)
local tblThreatsRange                   = nil  -- this is a foundamental table cause it holds the firing range of any units, but mostly artillery one! Since the required data aren't available in the mission env, it can be either ported by DSMC (if used) or manually prompted. For the latter, obviously, it require to be manually updated. 
if EMBD then -- just for compatibility enhancement
    tblThreatsRange                = EMBD.tblThreatsRange
    if tblThreatsRange then
        for tId, tData in pairs(tblThreatsRange) do
            tData.attr = nil
        end
    end
end

if DGWS then -- just for compatibility enhancement
    tblThreatsRange                = DSMC_tblThreatsRange
    for tId, tData in pairs(tblThreatsRange) do
        tData.attr = nil
    end
end

if not tblThreatsRange then
	tblThreatsRange = {
        ["S-60_Type59_Artillery"] = 
        {
            ["detection"] = 5000,
            ["threat"] = 6000,
        }, -- end of ["S-60_Type59_Artillery"]
        ["flak30"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 2500,
        }, -- end of ["flak30"]
        ["Daimler_AC"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 0,
            ["threat"] = 2000,
        }, -- end of ["Daimler_AC"]
        ["MTLB"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 1000,
        }, -- end of ["MTLB"]
        ["L-39ZA"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["L-39ZA"]
        ["Czech hedgehogs 2"] = 
        {
        }, -- end of ["Czech hedgehogs 2"]
        ["Horch_901_typ_40_kfz_21"] = 
        {
            ["irsignature"] = 0.065,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Horch_901_typ_40_kfz_21"]
        ["uh1h_cargo"] = 
        {
        }, -- end of ["uh1h_cargo"]
        ["pipes_small_cargo"] = 
        {
        }, -- end of ["pipes_small_cargo"]
        ["Gepard"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 15000,
            ["threat"] = 4000,
        }, -- end of ["Gepard"]
        ["M 818"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["M 818"]
        ["Soldier RPG"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Soldier RPG"]
        ["Container_watchtower_lights"] = 
        {
        }, -- end of ["Container_watchtower_lights"]
        ["FarpHide_Med"] = 
        {
        }, -- end of ["FarpHide_Med"]
        ["CV_59_MD3"] = 
        {
        }, -- end of ["CV_59_MD3"]
        ["CastleClass_01"] = 
        {
            ["detection"] = 25000,
            ["threat"] = 3000,
        }, -- end of ["CastleClass_01"]
        ["Merkava_Mk4"] = 
        {
            ["irsignature"] = 0.12,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["Merkava_Mk4"]
        ["Patriot cp"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Patriot cp"]
        ["5p73 s-125 ln"] = 
        {
            ["irsignature"] = 0.02,
            ["detection"] = 0,
            ["threat"] = 18000,
        }, -- end of ["5p73 s-125 ln"]
        ["house2arm"] = 
        {
            ["irsignature"] = 0.007,
            ["detection"] = 0,
            ["threat"] = 800,
        }, -- end of ["house2arm"]
        ["F-15E"] = 
        {
            ["irsignature"] = 0.91,
        }, -- end of ["F-15E"]
        ["M45_Quadmount"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 1500,
        }, -- end of ["M45_Quadmount"]
        ["Churchill_VII"] = 
        {
            ["irsignature"] = 0.105,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["Churchill_VII"]
        ["B-52H"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["B-52H"]
        ["AS32-p25"] = 
        {
        }, -- end of ["AS32-p25"]
        ["SturmPzIV"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 4500,
        }, -- end of ["SturmPzIV"]
        ["T155_Firtina"] = 
        {
            ["irsignature"] = 0.11,
            ["detection"] = 0,
            ["threat"] = 41000,
        }, -- end of ["T155_Firtina"]
        ["oiltank_cargo"] = 
        {
        }, -- end of ["oiltank_cargo"]
        ["Carrier LSO Personell 2"] = 
        {
        }, -- end of ["Carrier LSO Personell 2"]
        ["Freya_Shelter_Brick"] = 
        {
        }, -- end of ["Freya_Shelter_Brick"]
        ["Building04_PBR"] = 
        {
        }, -- end of ["Building04_PBR"]
        ["A-20G"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["A-20G"]
        ["r11_volvo"] = 
        {
        }, -- end of ["r11_volvo"]
        ["Container_40ft"] = 
        {
        }, -- end of ["Container_40ft"]
        ["Predator GCS"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Predator GCS"]
        ["hy_launcher"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 100000,
            ["threat"] = 100000,
        }, -- end of ["hy_launcher"]
        ["Bf-109K-4"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["Bf-109K-4"]
        ["Seawise_Giant"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Seawise_Giant"]
        ["LST_Mk2"] = 
        {
            ["irsignature"] = 0.3,
            ["detection"] = 0,
            ["threat"] = 4000,
        }, -- end of ["LST_Mk2"]
        ["m1_vla"] = 
        {
        }, -- end of ["m1_vla"]
        ["Mirage-F1BQ"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1BQ"]
        ["ZU-23 Closed Insurgent"] = 
        {
            ["irsignature"] = 0.006,
            ["detection"] = 5000,
            ["threat"] = 2500,
        }, -- end of ["ZU-23 Closed Insurgent"]
        ["HQ-7_LN_P"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 8000,
            ["threat"] = 12000,
        }, -- end of ["HQ-7_LN_P"]
        ["snr s-125 tr"] = 
        {
            ["irsignature"] = 0.06,
            ["detection"] = 100000,
            ["threat"] = 0,
        }, -- end of ["snr s-125 tr"]
        ["Siegfried Line"] = 
        {
        }, -- end of ["Siegfried Line"]
        ["SAU Msta"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 23500,
        }, -- end of ["SAU Msta"]
        ["Tent02"] = 
        {
        }, -- end of ["Tent02"]
        ["outpost_road"] = 
        {
            ["irsignature"] = 0.007,
            ["detection"] = 0,
            ["threat"] = 800,
        }, -- end of ["outpost_road"]
        ["Vulcan"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 5000,
            ["threat"] = 2000,
        }, -- end of ["Vulcan"]
        ["Dragonteeth 2"] = 
        {
        }, -- end of ["Dragonteeth 2"]
        ["leopard-2A4_trs"] = 
        {
            ["irsignature"] = 0.12,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["leopard-2A4_trs"]
        ["Sd_Kfz_7"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 0,
        }, -- end of ["Sd_Kfz_7"]
        ["container_40ft"] = 
        {
        }, -- end of ["container_40ft"]
        ["TYPE-59"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 2500,
        }, -- end of ["TYPE-59"]
        ["SpGH_Dana"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 18700,
        }, -- end of ["SpGH_Dana"]
        ["Ski Ramp"] = 
        {
        }, -- end of ["Ski Ramp"]
        ["BMP-2"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["BMP-2"]
        ["santafe"] = 
        {
        }, -- end of ["santafe"]
        ["Allies_Director"] = 
        {
            ["irsignature"] = 0.03,
            ["detection"] = 30000,
            ["threat"] = 0,
        }, -- end of ["Allies_Director"]
        ["F-5E"] = 
        {
            ["irsignature"] = 0.4,
        }, -- end of ["F-5E"]
        ["VAB_Mephisto"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 3800,
        }, -- end of ["VAB_Mephisto"]
        ["Cargo06"] = 
        {
        }, -- end of ["Cargo06"]
        ["Stug_IV"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["Stug_IV"]
        ["F-4E"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["F-4E"]
        ["M-109"] = 
        {
            ["irsignature"] = 0.11,
            ["detection"] = 0,
            ["threat"] = 22000,
        }, -- end of ["M-109"]
        ["LAZ Bus"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["LAZ Bus"]
        ["UH-60A"] = 
        {
            ["irsignature"] = 0.22,
        }, -- end of ["UH-60A"]
        ["Beer Bomb"] = 
        {
        }, -- end of ["Beer Bomb"]
        ["ATZ-10"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ATZ-10"]
        ["P20_01"] = 
        {
        }, -- end of ["P20_01"]
        ["Elefant_SdKfz_184"] = 
        {
            ["irsignature"] = 0.11,
            ["detection"] = 0,
            ["threat"] = 6000,
        }, -- end of ["Elefant_SdKfz_184"]
        ["M978 HEMTT Tanker"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["M978 HEMTT Tanker"]
        ["Mirage-F1CE"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1CE"]
        ["Marder"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 1500,
        }, -- end of ["Marder"]
        ["HarborTug"] = 
        {
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["HarborTug"]
        ["CVN_72"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 50000,
            ["threat"] = 25000,
        }, -- end of ["CVN_72"]
        ["Sandbag_11"] = 
        {
        }, -- end of ["Sandbag_11"]
        ["BRDM-2"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 1600,
        }, -- end of ["BRDM-2"]
        ["ATZ-5"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ATZ-5"]
        ["Tent01"] = 
        {
        }, -- end of ["Tent01"]
        ["An-26B"] = 
        {
            ["irsignature"] = 0.5,
        }, -- end of ["An-26B"]
        ["LiAZ Bus"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["LiAZ Bus"]
        ["soldier_wwii_us"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["soldier_wwii_us"]
        ["trunks_small_cargo"] = 
        {
        }, -- end of ["trunks_small_cargo"]
        ["Tiger_II_H"] = 
        {
            ["irsignature"] = 0.105,
            ["detection"] = 0,
            ["threat"] = 6000,
        }, -- end of ["Tiger_II_H"]
        ["KC130"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["KC130"]
        ["LAV-25"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 2500,
        }, -- end of ["LAV-25"]
        ["SK_C_28_naval_gun"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 20000,
        }, -- end of ["SK_C_28_naval_gun"]
        ["Stug_III"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["Stug_III"]
        ["BoomBarrier_open"] = 
        {
        }, -- end of ["BoomBarrier_open"]
        ["tetrapod_cargo"] = 
        {
        }, -- end of ["tetrapod_cargo"]
        ["German_covered_wagon_G10"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["German_covered_wagon_G10"]
        ["TugHarlan_drivable"] = 
        {
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["TugHarlan_drivable"]
        ["UAZ-469"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["UAZ-469"]
        ["CV_59_NS60"] = 
        {
        }, -- end of ["CV_59_NS60"]
        ["FPS-117 Dome"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 400000,
            ["threat"] = 0,
        }, -- end of ["FPS-117 Dome"]
        ["Building03_PBR"] = 
        {
        }, -- end of ["Building03_PBR"]
        ["Mirage-F1DDA"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1DDA"]
        ["CH-53E"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["CH-53E"]
        ["Mirage-F1CJ"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1CJ"]
        ["FW-190A8"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["FW-190A8"]
        ["FuMG-401"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 160000,
            ["threat"] = 0,
        }, -- end of ["FuMG-401"]
        ["Ural-4320T"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Ural-4320T"]
        ["HandyWind"] = 
        {
            ["irsignature"] = 0.35,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["HandyWind"]
        ["Ka-50"] = 
        {
            ["irsignature"] = 0.3,
        }, -- end of ["Ka-50"]
        ["Tiger_I"] = 
        {
            ["irsignature"] = 0.105,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["Tiger_I"]
        ["FireExtinguisher03"] = 
        {
        }, -- end of ["FireExtinguisher03"]
        ["Shelter01"] = 
        {
        }, -- end of ["Shelter01"]
        ["Kub 1S91 str"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 70000,
            ["threat"] = 0,
        }, -- end of ["Kub 1S91 str"]
        ["ALBATROS"] = 
        {
            ["irsignature"] = 0.35,
            ["detection"] = 30000,
            ["threat"] = 16000,
        }, -- end of ["ALBATROS"]
        ["M1126 Stryker ICV"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["M1126 Stryker ICV"]
        ["E-3A"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["E-3A"]
        ["Soldier AK"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Soldier AK"]
        ["SA-18 Igla-S manpad"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 5000,
            ["threat"] = 5200,
        }, -- end of ["SA-18 Igla-S manpad"]
        ["Kub 2P25 ln"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 25000,
        }, -- end of ["Kub 2P25 ln"]
        ["Cobra"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["Cobra"]
        ["MLRS FDDM"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["MLRS FDDM"]
        ["Mi-8MT"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["Mi-8MT"]
        ["bofors40"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 4000,
        }, -- end of ["bofors40"]
        ["ZU-23 Insurgent"] = 
        {
            ["irsignature"] = 0.006,
            ["detection"] = 5000,
            ["threat"] = 2500,
        }, -- end of ["ZU-23 Insurgent"]
        ["Ural-4320-31"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Ural-4320-31"]
        ["Hawk tr"] = 
        {
            ["irsignature"] = 0.06,
            ["detection"] = 90000,
            ["threat"] = 0,
        }, -- end of ["Hawk tr"]
        ["TACAN_beacon"] = 
        {
            ["irsignature"] = 0.005,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["TACAN_beacon"]
        ["SNR_75V"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 100000,
            ["threat"] = 0,
        }, -- end of ["SNR_75V"]
        ["Building08_PBR"] = 
        {
        }, -- end of ["Building08_PBR"]
        ["Stinger comm"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 5000,
            ["threat"] = 0,
        }, -- end of ["Stinger comm"]
        ["IL-76MD"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["IL-76MD"]
        ["Su-34"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["Su-34"]
        ["soldier_mauser98"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["soldier_mauser98"]
        ["AS32-36A"] = 
        {
        }, -- end of ["AS32-36A"]
        ["S-300PS 40B6MD sr_19J6"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 150000,
            ["threat"] = 0,
        }, -- end of ["S-300PS 40B6MD sr_19J6"]
        ["MJ-1_02"] = 
        {
        }, -- end of ["MJ-1_02"]
        ["M1045 HMMWV TOW"] = 
        {
            ["irsignature"] = 0.75,
            ["detection"] = 0,
            ["threat"] = 3800,
        }, -- end of ["M1045 HMMWV TOW"]
        ["Mirage-F1B"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1B"]
        ["LHD_LHA"] = 
        {
        }, -- end of ["LHD_LHA"]
        ["HQ-7_LN_SP"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 8000,
            ["threat"] = 12000,
        }, -- end of ["HQ-7_LN_SP"]
        ["SAU Akatsia"] = 
        {
            ["irsignature"] = 0.095,
            ["detection"] = 0,
            ["threat"] = 17000,
        }, -- end of ["SAU Akatsia"]
        ["NASAMS_Radar_MPQ64F1"] = 
        {
            ["irsignature"] = 0.06,
            ["detection"] = 50000,
            ["threat"] = 0,
        }, -- end of ["NASAMS_Radar_MPQ64F1"]
        ["Haystack 4"] = 
        {
        }, -- end of ["Haystack 4"]
        ["HESCO_wallperimeter_5"] = 
        {
        }, -- end of ["HESCO_wallperimeter_5"]
        ["F/A-18A"] = 
        {
            ["irsignature"] = 0.73,
        }, -- end of ["F/A-18A"]
        ["Revetment_x8"] = 
        {
        }, -- end of ["Revetment_x8"]
        ["Tetrarch"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 2000,
        }, -- end of ["Tetrarch"]
        ["MiG-23MLD"] = 
        {
            ["irsignature"] = 0.69,
        }, -- end of ["MiG-23MLD"]
        ["M32-10C_04"] = 
        {
        }, -- end of ["M32-10C_04"]
        ["Jagdpanther_G1"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 5000,
        }, -- end of ["Jagdpanther_G1"]
        ["Container_10ft"] = 
        {
        }, -- end of ["Container_10ft"]
        ["AV8BNA"] = 
        {
            ["irsignature"] = 0.7,
        }, -- end of ["AV8BNA"]
        ["Infantry AK Ins"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Infantry AK Ins"]
        ["Coach cargo"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Coach cargo"]
        ["M-2000C"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["M-2000C"]
        ["Carrier LSO Personell 5"] = 
        {
        }, -- end of ["Carrier LSO Personell 5"]
        ["ZIL-131 KUNG"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ZIL-131 KUNG"]
        ["Container_20ft"] = 
        {
        }, -- end of ["Container_20ft"]
        ["ZiL-131 APA-80"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ZiL-131 APA-80"]
        ["BMP-3"] = 
        {
            ["irsignature"] = 0.095,
            ["detection"] = 0,
            ["threat"] = 4000,
        }, -- end of ["BMP-3"]
        ["P-47D-30bl1"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["P-47D-30bl1"]
        ["CVN_75"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 50000,
            ["threat"] = 25000,
        }, -- end of ["CVN_75"]
        ["UH-1H"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["UH-1H"]
        ["Hummer"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Hummer"]
        ["Dragonteeth 1"] = 
        {
        }, -- end of ["Dragonteeth 1"]
        ["AH-64D_BLK_II"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["AH-64D_BLK_II"]
        ["leopard-2A4"] = 
        {
            ["irsignature"] = 0.12,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["leopard-2A4"]
        ["RQ-1A Predator"] = 
        {
            ["irsignature"] = 0.01,
        }, -- end of ["RQ-1A Predator"]
        ["USS_Arleigh_Burke_IIa"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 150000,
            ["threat"] = 100000,
        }, -- end of ["USS_Arleigh_Burke_IIa"]
        ["Carrier LSO Personell 1"] = 
        {
        }, -- end of ["Carrier LSO Personell 1"]
        ["SA342L"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["SA342L"]
        ["Sandbag_02"] = 
        {
        }, -- end of ["Sandbag_02"]
        ["Ladder"] = 
        {
        }, -- end of ["Ladder"]
        ["f_bar_cargo"] = 
        {
        }, -- end of ["f_bar_cargo"]
        ["Tornado GR4"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["Tornado GR4"]
        ["Hawk pcp"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Hawk pcp"]
        ["S-300PS 40B6M tr"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 160000,
            ["threat"] = 0,
        }, -- end of ["S-300PS 40B6M tr"]
        ["ZU-23 Emplacement Closed"] = 
        {
            ["irsignature"] = 0.006,
            ["detection"] = 5000,
            ["threat"] = 2500,
        }, -- end of ["ZU-23 Emplacement Closed"]
        ["ZIL-135"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ZIL-135"]
        ["OH58D"] = 
        {
            ["irsignature"] = 0.07,
        }, -- end of ["OH58D"]
        ["Bunker"] = 
        {
            ["irsignature"] = 0.005,
            ["detection"] = 0,
            ["threat"] = 800,
        }, -- end of ["Bunker"]
        ["Container_office"] = 
        {
        }, -- end of ["Container_office"]
        ["GAZ-3308"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["GAZ-3308"]
        ["flak41"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 5000,
        }, -- end of ["flak41"]
        ["SA-11 Buk LN 9A310M1"] = 
        {
            ["irsignature"] = 0.095,
            ["detection"] = 50000,
            ["threat"] = 35000,
        }, -- end of ["SA-11 Buk LN 9A310M1"]
        ["tt_B8M1"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 5000,
        }, -- end of ["tt_B8M1"]
        ["SON_9"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 55000,
            ["threat"] = 0,
        }, -- end of ["SON_9"]
        ["ZSU-23-4 Shilka"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 2500,
        }, -- end of ["ZSU-23-4 Shilka"]
        ["Freya_Shelter_Concrete"] = 
        {
        }, -- end of ["Freya_Shelter_Concrete"]
        ["Chieftain_mk3"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["Chieftain_mk3"]
        ["BTR-80"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 1600,
        }, -- end of ["BTR-80"]
        ["CV_1143_5"] = 
        {
            ["irsignature"] = 0.45,
            ["detection"] = 25000,
            ["threat"] = 12000,
        }, -- end of ["CV_1143_5"]
        ["r11_volvo_drivable"] = 
        {
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["r11_volvo_drivable"]
        ["AAV7"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["AAV7"]
        ["Ka-27"] = 
        {
            ["irsignature"] = 0.5,
        }, -- end of ["Ka-27"]
        ["KC135MPRS"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["KC135MPRS"]
        ["FPS-117"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 463000,
            ["threat"] = 0,
        }, -- end of ["FPS-117"]
        ["HEMTT_C-RAM_Phalanx"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 10000,
            ["threat"] = 2000,
        }, -- end of ["HEMTT_C-RAM_Phalanx"]
        ["Patriot ln"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 100000,
        }, -- end of ["Patriot ln"]
        ["H-6J"] = 
        {
            ["irsignature"] = 2.5,
        }, -- end of ["H-6J"]
        ["E-2C"] = 
        {
            ["irsignature"] = 0.5,
        }, -- end of ["E-2C"]
        ["offshore WindTurbine"] = 
        {
        }, -- end of ["offshore WindTurbine"]
        ["NASAMS_LN_B"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 15000,
        }, -- end of ["NASAMS_LN_B"]
        ["ZU-23 Emplacement"] = 
        {
            ["irsignature"] = 0.006,
            ["detection"] = 5000,
            ["threat"] = 2500,
        }, -- end of ["ZU-23 Emplacement"]
        ["tt_ZU-23"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 2500,
        }, -- end of ["tt_ZU-23"]
        ["Ural-4320 APA-5D"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Ural-4320 APA-5D"]
        ["S-300PS 5P85D ln"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 120000,
        }, -- end of ["S-300PS 5P85D ln"]
        ["Coach a passenger"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Coach a passenger"]
        ["Dry-cargo ship-1"] = 
        {
            ["irsignature"] = 0.2,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Dry-cargo ship-1"]
        ["Mirage-F1C"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1C"]
        ["Tu-22M3"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["Tu-22M3"]
        ["Boxcartrinity"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Boxcartrinity"]
        ["M8_Greyhound"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 2000,
        }, -- end of ["M8_Greyhound"]
        ["us carrier tech"] = 
        {
        }, -- end of ["us carrier tech"]
        ["Mirage-F1BE"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1BE"]
        ["M2A1_halftrack"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["M2A1_halftrack"]
        ["DRG_Class_86"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["DRG_Class_86"]
        ["MB-339APAN"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["MB-339APAN"]
        ["S-300PS 5H63C 30H6_tr"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 120000,
            ["threat"] = 0,
        }, -- end of ["S-300PS 5H63C 30H6_tr"]
        ["p-19 s-125 sr"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 160000,
            ["threat"] = 0,
        }, -- end of ["p-19 s-125 sr"]
        ["Carrier LSO Personell 4"] = 
        {
        }, -- end of ["Carrier LSO Personell 4"]
        ["Infantry AK"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Infantry AK"]
        ["APFC fuel"] = 
        {
        }, -- end of ["APFC fuel"]
        ["T-80UD"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 5000,
        }, -- end of ["T-80UD"]
        ["Type_054A"] = 
        {
            ["detection"] = 160000,
            ["threat"] = 45000,
        }, -- end of ["Type_054A"]
        ["Tu-160"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["Tu-160"]
        ["Dragonteeth 5"] = 
        {
        }, -- end of ["Dragonteeth 5"]
        ["Type_052B"] = 
        {
            ["detection"] = 100000,
            ["threat"] = 30000,
        }, -- end of ["Type_052B"]
        ["A-10C_2"] = 
        {
            ["irsignature"] = 0.53,
        }, -- end of ["A-10C_2"]
        ["CH-47D"] = 
        {
            ["irsignature"] = 0.72,
        }, -- end of ["CH-47D"]
        ["AH-64A"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["AH-64A"]
        ["Challenger2"] = 
        {
            ["irsignature"] = 0.11,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["Challenger2"]
        ["QF_37_AA"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 9000,
        }, -- end of ["QF_37_AA"]
        ["WingLoong-I"] = 
        {
            ["irsignature"] = 0.02,
        }, -- end of ["WingLoong-I"]
        ["Patriot EPP"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Patriot EPP"]
        ["S-3B Tanker"] = 
        {
            ["irsignature"] = 0.53,
        }, -- end of ["S-3B Tanker"]
        ["Su-25T"] = 
        {
            ["irsignature"] = 0.7,
        }, -- end of ["Su-25T"]
        ["HESCO_watchtower_2"] = 
        {
        }, -- end of ["HESCO_watchtower_2"]
        ["ELNYA"] = 
        {
            ["irsignature"] = 0.3,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ELNYA"]
        ["Mirage-F1C-200"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1C-200"]
        ["Ural-375 PBU"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Ural-375 PBU"]
        ["Smerch_HE"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 70000,
        }, -- end of ["Smerch_HE"]
        ["M6 Linebacker"] = 
        {
            ["irsignature"] = 0.095,
            ["detection"] = 8000,
            ["threat"] = 4500,
        }, -- end of ["M6 Linebacker"]
        ["Mirage-F1CT"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1CT"]
        ["Leopard-2A5"] = 
        {
            ["irsignature"] = 0.12,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["Leopard-2A5"]
        ["P-51D"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["P-51D"]
        ["Mirage-F1EH"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1EH"]
        ["WindTurbine"] = 
        {
        }, -- end of ["WindTurbine"]
        ["F-16C bl.52d"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["F-16C bl.52d"]
        ["PT_76"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 2000,
        }, -- end of ["PT_76"]
        ["ZWEZDNY"] = 
        {
            ["irsignature"] = 0.3,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ZWEZDNY"]
        ["MLRS"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 32000,
        }, -- end of ["MLRS"]
        ["Mirage-F1CG"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1CG"]
        ["Infantry AK ver2"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Infantry AK ver2"]
        ["J-11A"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["J-11A"]
        ["SpitfireLFMkIX"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["SpitfireLFMkIX"]
        ["Dog Ear radar"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 35000,
            ["threat"] = 0,
        }, -- end of ["Dog Ear radar"]
        ["Revetment_x4"] = 
        {
        }, -- end of ["Revetment_x4"]
        ["HESCO_post_1"] = 
        {
        }, -- end of ["HESCO_post_1"]
        ["P-51D-30-NA"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["P-51D-30-NA"]
        ["M1043 HMMWV Armament"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["M1043 HMMWV Armament"]
        ["S-300PS 40B6MD sr"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 60000,
            ["threat"] = 0,
        }, -- end of ["S-300PS 40B6MD sr"]
        ["Mirage-F1EE"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1EE"]
        ["Barrier A"] = 
        {
        }, -- end of ["Barrier A"]
        ["Tu-95MS"] = 
        {
            ["irsignature"] = 1.1,
        }, -- end of ["Tu-95MS"]
        ["HL_B8M1"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 5000,
        }, -- end of ["HL_B8M1"]
        ["KAMAZ Truck"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["KAMAZ Truck"]
        ["Toolbox02"] = 
        {
        }, -- end of ["Toolbox02"]
        ["Mirage-F1CZ"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1CZ"]
        ["Carrier LSO Personell"] = 
        {
        }, -- end of ["Carrier LSO Personell"]
        ["CV_59_Large_Forklift"] = 
        {
        }, -- end of ["CV_59_Large_Forklift"]
        ["Ju-88A4"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["Ju-88A4"]
        ["Silkworm_SR"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 200000,
            ["threat"] = 0,
        }, -- end of ["Silkworm_SR"]
        ["NEUSTRASH"] = 
        {
            ["irsignature"] = 0.35,
            ["detection"] = 27000,
            ["threat"] = 12000,
        }, -- end of ["NEUSTRASH"]
        ["SKP-11"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["SKP-11"]
        ["FarpHide_Dmed"] = 
        {
        }, -- end of ["FarpHide_Dmed"]
        ["JagdPz_IV"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["JagdPz_IV"]
        ["USS_Samuel_Chase"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 0,
            ["threat"] = 7000,
        }, -- end of ["USS_Samuel_Chase"]
        ["BMP-1"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["BMP-1"]
        ["T-55"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 2500,
        }, -- end of ["T-55"]
        ["Su-17M4"] = 
        {
            ["irsignature"] = 0.69,
        }, -- end of ["Su-17M4"]
        ["rapier_fsa_launcher"] = 
        {
            ["irsignature"] = 0.03,
            ["detection"] = 30000,
            ["threat"] = 6800,
        }, -- end of ["rapier_fsa_launcher"]
        ["F-4E-45MC"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["F-4E-45MC"]
        ["Log ramps 3"] = 
        {
        }, -- end of ["Log ramps 3"]
        ["HESCO_wallperimeter_3"] = 
        {
        }, -- end of ["HESCO_wallperimeter_3"]
        ["L118_Unit"] = 
        {
            ["detection"] = 500,
            ["threat"] = 17200,
        }, -- end of ["L118_Unit"]
        ["DR_50Ton_Flat_Wagon"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["DR_50Ton_Flat_Wagon"]
        ["Su-30"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["Su-30"]
        ["KILO"] = 
        {
            ["irsignature"] = 0.2,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["KILO"]
        ["Leopard1A3"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 2500,
        }, -- end of ["Leopard1A3"]
        ["M2A1-105"] = 
        {
            ["irsignature"] = 0.04,
            ["detection"] = 0,
            ["threat"] = 11500,
        }, -- end of ["M2A1-105"]
        ["Tu-142"] = 
        {
            ["irsignature"] = 1.1,
        }, -- end of ["Tu-142"]
        ["Twall_x6"] = 
        {
        }, -- end of ["Twall_x6"]
        ["Gas platform"] = 
        {
        }, -- end of ["Gas platform"]
        ["B600_drivable"] = 
        {
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["B600_drivable"]
        ["T-72B"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 4000,
        }, -- end of ["T-72B"]
        ["NF-2_LightOff02"] = 
        {
        }, -- end of ["NF-2_LightOff02"]
        ["AJS37"] = 
        {
            ["irsignature"] = 0.62,
        }, -- end of ["AJS37"]
        ["55G6 EWR"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 400000,
            ["threat"] = 0,
        }, -- end of ["55G6 EWR"]
        ["MOLNIYA"] = 
        {
            ["irsignature"] = 0.35,
            ["detection"] = 21000,
            ["threat"] = 2000,
        }, -- end of ["MOLNIYA"]
        ["IL-78M"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["IL-78M"]
        ["MiG-15bis"] = 
        {
            ["irsignature"] = 0.26,
        }, -- end of ["MiG-15bis"]
        ["Log ramps 2"] = 
        {
        }, -- end of ["Log ramps 2"]
        ["MJ-1_01"] = 
        {
        }, -- end of ["MJ-1_01"]
        ["Haystack 1"] = 
        {
        }, -- end of ["Haystack 1"]
        ["ZSU_57_2"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 5000,
            ["threat"] = 7000,
        }, -- end of ["ZSU_57_2"]
        ["Invisible FARP"] = 
        {
        }, -- end of ["Invisible FARP"]
        ["Uragan_BM-27"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 35800,
        }, -- end of ["Uragan_BM-27"]
        ["FarpHide_Dsmall"] = 
        {
        }, -- end of ["FarpHide_Dsmall"]
        ["MosquitoFBMkVI"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["MosquitoFBMkVI"]
        ["houseA_arm"] = 
        {
            ["irsignature"] = 0.007,
            ["detection"] = 0,
            ["threat"] = 800,
        }, -- end of ["houseA_arm"]
        ["F-14A-135-GR"] = 
        {
            ["irsignature"] = 0.9,
        }, -- end of ["F-14A-135-GR"]
        ["SAU Gvozdika"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 15000,
        }, -- end of ["SAU Gvozdika"]
        ["Type_071"] = 
        {
            ["detection"] = 300000,
            ["threat"] = 150000,
        }, -- end of ["Type_071"]
        ["Sandbag_13"] = 
        {
        }, -- end of ["Sandbag_13"]
        ["Mirage-F1BD"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1BD"]
        ["Tankcartrinity"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Tankcartrinity"]
        ["2S6 Tunguska"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 18000,
            ["threat"] = 8000,
        }, -- end of ["2S6 Tunguska"]
        ["Hawk"] = 
        {
            ["irsignature"] = 0.62,
        }, -- end of ["Hawk"]
        ["Dry-cargo ship-2"] = 
        {
            ["irsignature"] = 0.3,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Dry-cargo ship-2"]
        ["FuSe-65"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 60000,
            ["threat"] = 0,
        }, -- end of ["FuSe-65"]
        ["tacr2a"] = 
        {
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["tacr2a"]
        ["S_75M_Volhov"] = 
        {
            ["irsignature"] = 0.03,
            ["detection"] = 0,
            ["threat"] = 43000,
        }, -- end of ["S_75M_Volhov"]
        ["Pz_V_Panther_G"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["Pz_V_Panther_G"]
        ["Camouflage03"] = 
        {
        }, -- end of ["Camouflage03"]
        ["FW-190D9"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["FW-190D9"]
        ["Sandbag_16"] = 
        {
        }, -- end of ["Sandbag_16"]
        ["Su-27"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["Su-27"]
        ["Leopard-2"] = 
        {
            ["irsignature"] = 0.12,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["Leopard-2"]
        ["generator_5i57"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["generator_5i57"]
        ["Sd_Kfz_2"] = 
        {
            ["irsignature"] = 0.065,
            ["detection"] = 0,
        }, -- end of ["Sd_Kfz_2"]
        ["Strela-10M3"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 8000,
            ["threat"] = 5000,
        }, -- end of ["Strela-10M3"]
        ["HESCO_wallperimeter_1"] = 
        {
        }, -- end of ["HESCO_wallperimeter_1"]
        ["S-3B"] = 
        {
            ["irsignature"] = 0.53,
        }, -- end of ["S-3B"]
        ["Camouflage06"] = 
        {
        }, -- end of ["Camouflage06"]
        ["TZ-22_KrAZ"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["TZ-22_KrAZ"]
        ["BTR-82A"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 2000,
        }, -- end of ["BTR-82A"]
        ["Paratrooper RPG-16"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Paratrooper RPG-16"]
        ["Willys_MB"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Willys_MB"]
        ["AH-1W"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["AH-1W"]
        ["Sd_Kfz_251"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 0,
            ["threat"] = 1100,
        }, -- end of ["Sd_Kfz_251"]
        ["ElevatedPlatform_down"] = 
        {
        }, -- end of ["ElevatedPlatform_down"]
        ["Container_watchtower"] = 
        {
        }, -- end of ["Container_watchtower"]
        ["Carrier Airboss"] = 
        {
        }, -- end of ["Carrier Airboss"]
        ["LeFH_18-40-105"] = 
        {
            ["irsignature"] = 0.04,
            ["detection"] = 0,
            ["threat"] = 10500,
        }, -- end of ["LeFH_18-40-105"]
        ["ammo_cargo"] = 
        {
        }, -- end of ["ammo_cargo"]
        ["Tower Crane"] = 
        {
        }, -- end of ["Tower Crane"]
        ["Oil rig"] = 
        {
        }, -- end of ["Oil rig"]
        ["Tent05"] = 
        {
        }, -- end of ["Tent05"]
        ["Trolley bus"] = 
        {
            ["irsignature"] = 0.06,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Trolley bus"]
        ["Su-25"] = 
        {
            ["irsignature"] = 0.7,
        }, -- end of ["Su-25"]
        ["FARP_SINGLE_01"] = 
        {
        }, -- end of ["FARP_SINGLE_01"]
        ["offshore WindTurbine2"] = 
        {
        }, -- end of ["offshore WindTurbine2"]
        ["Cargo01"] = 
        {
        }, -- end of ["Cargo01"]
        ["tt_KORD"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 1200,
        }, -- end of ["tt_KORD"]
        ["Orca"] = 
        {
        }, -- end of ["Orca"]
        ["Tigr_233036"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Tigr_233036"]
        ["MAZ-6303"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["MAZ-6303"]
        ["M48 Chaparral"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 10000,
            ["threat"] = 8500,
        }, -- end of ["M48 Chaparral"]
        ["1L13 EWR"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 300000,
            ["threat"] = 0,
        }, -- end of ["1L13 EWR"]
        ["BDK-775"] = 
        {
            ["irsignature"] = 0.35,
            ["detection"] = 25000,
            ["threat"] = 6000,
        }, -- end of ["BDK-775"]
        ["fueltank_cargo"] = 
        {
        }, -- end of ["fueltank_cargo"]
        ["Haystack 3"] = 
        {
        }, -- end of ["Haystack 3"]
        ["SA-18 Igla manpad"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 5000,
            ["threat"] = 5200,
        }, -- end of ["SA-18 Igla manpad"]
        ["SA342M"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["SA342M"]
        ["S-300PS 5P85C ln"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 120000,
        }, -- end of ["S-300PS 5P85C ln"]
        ["Ka-50_3"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["Ka-50_3"]
        ["Type_052C"] = 
        {
            ["detection"] = 260000,
            ["threat"] = 100000,
        }, -- end of ["Type_052C"]
        ["Czech hedgehogs 1"] = 
        {
        }, -- end of ["Czech hedgehogs 1"]
        ["trunks_long_cargo"] = 
        {
        }, -- end of ["trunks_long_cargo"]
        ["Sandbag_12"] = 
        {
        }, -- end of ["Sandbag_12"]
        ["Cargo02"] = 
        {
        }, -- end of ["Cargo02"]
        ["Hemmkurvenhindernis"] = 
        {
        }, -- end of ["Hemmkurvenhindernis"]
        ["Sandbag_04"] = 
        {
        }, -- end of ["Sandbag_04"]
        ["leander-gun-condell"] = 
        {
            ["detection"] = 150000,
            ["threat"] = 100000,
        }, -- end of ["leander-gun-condell"]
        ["Soldier M4"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Soldier M4"]
        ["FA-18C_hornet"] = 
        {
            ["irsignature"] = 0.75,
        }, -- end of ["FA-18C_hornet"]
        ["AS32-32A"] = 
        {
        }, -- end of ["AS32-32A"]
        ["GAZ-3307"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["GAZ-3307"]
        ["RD_75"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 100000,
            ["threat"] = 0,
        }, -- end of ["RD_75"]
        ["Yak-52"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["Yak-52"]
        ["container_20ft"] = 
        {
        }, -- end of ["container_20ft"]
        ["billboard_motorized"] = 
        {
        }, -- end of ["billboard_motorized"]
        ["2B11 mortar"] = 
        {
            ["irsignature"] = 0.005,
            ["detection"] = 0,
            ["threat"] = 7000,
        }, -- end of ["2B11 mortar"]
        ["FarpHide_small"] = 
        {
        }, -- end of ["FarpHide_small"]
        ["Soldier stinger"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 5000,
            ["threat"] = 4500,
        }, -- end of ["Soldier stinger"]
        ["Log posts 2"] = 
        {
        }, -- end of ["Log posts 2"]
        ["Cow"] = 
        {
        }, -- end of ["Cow"]
        ["LARC-V"] = 
        {
            ["detection"] = 500,
            ["threat"] = 0,
        }, -- end of ["LARC-V"]
        ["P-47D-30"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["P-47D-30"]
        ["SA-18 Igla-S comm"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 5000,
            ["threat"] = 0,
        }, -- end of ["SA-18 Igla-S comm"]
        ["RPC_5N62V"] = 
        {
            ["detection"] = 400000,
            ["threat"] = 0,
        }, -- end of ["RPC_5N62V"]
        ["Dragonteeth 4"] = 
        {
        }, -- end of ["Dragonteeth 4"]
        ["P-47D-40"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["P-47D-40"]
        ["HL_ZU-23"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 2500,
        }, -- end of ["HL_ZU-23"]
        ["I-16"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["I-16"]
        ["BMD-1"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["BMD-1"]
        ["Mirage-F1CH"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1CH"]
        ["Camouflage07"] = 
        {
        }, -- end of ["Camouflage07"]
        ["S_75_ZIL"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["S_75_ZIL"]
        ["F-14B"] = 
        {
            ["irsignature"] = 0.9,
        }, -- end of ["F-14B"]
        ["M30_CC"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["M30_CC"]
        ["Belgian gate"] = 
        {
        }, -- end of ["Belgian gate"]
        ["C-130"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["C-130"]
        ["Carrier Seaman"] = 
        {
        }, -- end of ["Carrier Seaman"]
        ["Mirage-F1JA"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1JA"]
        ["GAZ-66"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["GAZ-66"]
        ["Locomotive"] = 
        {
            ["irsignature"] = 0.15,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Locomotive"]
        ["F-14A"] = 
        {
            ["irsignature"] = 0.97,
        }, -- end of ["F-14A"]
        ["HL_DSHK"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 1200,
        }, -- end of ["HL_DSHK"]
        ["CV_59_H60"] = 
        {
        }, -- end of ["CV_59_H60"]
        ["L-39C"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["L-39C"]
        ["S-300PS 64H6E sr"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 160000,
            ["threat"] = 0,
        }, -- end of ["S-300PS 64H6E sr"]
        ["CVN_73"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 50000,
            ["threat"] = 25000,
        }, -- end of ["CVN_73"]
        ["HQ-7_STR_SP"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 30000,
            ["threat"] = 0,
        }, -- end of ["HQ-7_STR_SP"]
        ["Mi-24P"] = 
        {
            ["irsignature"] = 0.5,
        }, -- end of ["Mi-24P"]
        ["SOM"] = 
        {
            ["irsignature"] = 0.2,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["SOM"]
        ["TugHarlan"] = 
        {
        }, -- end of ["TugHarlan"]
        ["Yak-40"] = 
        {
            ["irsignature"] = 0.5,
        }, -- end of ["Yak-40"]
        ["Igla manpad INS"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 5000,
            ["threat"] = 5200,
        }, -- end of ["Igla manpad INS"]
        ["Sandbag_05"] = 
        {
        }, -- end of ["Sandbag_05"]
        ["HESCO_wallperimeter_2"] = 
        {
        }, -- end of ["HESCO_wallperimeter_2"]
        ["soldier_wwii_br_01"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["soldier_wwii_br_01"]
        ["Hawk sr"] = 
        {
            ["irsignature"] = 0.06,
            ["detection"] = 90000,
            ["threat"] = 0,
        }, -- end of ["Hawk sr"]
        ["v1_launcher"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["v1_launcher"]
        ["Paratrooper AKS-74"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Paratrooper AKS-74"]
        ["MOSCOW"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 160000,
            ["threat"] = 75000,
        }, -- end of ["MOSCOW"]
        ["Carrier LSO Personell 3"] = 
        {
        }, -- end of ["Carrier LSO Personell 3"]
        ["Mi-26"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["Mi-26"]
        ["Pile of Woods"] = 
        {
        }, -- end of ["Pile of Woods"]
        ["outpost"] = 
        {
            ["irsignature"] = 0.007,
            ["detection"] = 0,
            ["threat"] = 800,
        }, -- end of ["outpost"]
        ["JF-17"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["JF-17"]
        ["KUZNECOW"] = 
        {
            ["irsignature"] = 0.45,
            ["detection"] = 25000,
            ["threat"] = 12000,
        }, -- end of ["KUZNECOW"]
        ["Building07_PBR"] = 
        {
        }, -- end of ["Building07_PBR"]
        ["Ural-375"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Ural-375"]
        ["Osa 9A33 ln"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 30000,
            ["threat"] = 10300,
        }, -- end of ["Osa 9A33 ln"]
        ["REZKY"] = 
        {
            ["irsignature"] = 0.35,
            ["detection"] = 30000,
            ["threat"] = 16000,
        }, -- end of ["REZKY"]
        ["Cargo04"] = 
        {
        }, -- end of ["Cargo04"]
        ["Oil Barrel"] = 
        {
        }, -- end of ["Oil Barrel"]
        ["Bedford_MWD"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Bedford_MWD"]
        ["Sandbag_10"] = 
        {
        }, -- end of ["Sandbag_10"]
        ["OH-58D"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["OH-58D"]
        ["l118"] = 
        {
        }, -- end of ["l118"]
        ["pipes_big_cargo"] = 
        {
        }, -- end of ["pipes_big_cargo"]
        ["B-17G"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["B-17G"]
        ["Land_Rover_109_S3"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Land_Rover_109_S3"]
        ["M32-10C_02"] = 
        {
        }, -- end of ["M32-10C_02"]
        ["C-101CC"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["C-101CC"]
        ["Smerch"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 70000,
        }, -- end of ["Smerch"]
        ["SAU 2-C9"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 7000,
        }, -- end of ["SAU 2-C9"]
        ["M32-10C_01"] = 
        {
        }, -- end of ["M32-10C_01"]
        ["fire_control"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 1100,
        }, -- end of ["fire_control"]
        ["M32-10C_03"] = 
        {
        }, -- end of ["M32-10C_03"]
        ["container_cargo"] = 
        {
        }, -- end of ["container_cargo"]
        ["Type_093"] = 
        {
            ["irsignature"] = 0.2,
            ["detection"] = 40000,
            ["threat"] = 40000,
        }, -- end of ["Type_093"]
        ["TICONDEROG"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 150000,
            ["threat"] = 100000,
        }, -- end of ["TICONDEROG"]
        ["FireExtinguisher02"] = 
        {
        }, -- end of ["FireExtinguisher02"]
        ["FPS-117 ECS"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["FPS-117 ECS"]
        ["ara_vdm"] = 
        {
            ["detection"] = 18000,
            ["threat"] = 5000,
        }, -- end of ["ara_vdm"]
        ["HESCO_watchtower_1"] = 
        {
        }, -- end of ["HESCO_watchtower_1"]
        ["AA8"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["AA8"]
        ["MCV-80"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 2500,
        }, -- end of ["MCV-80"]
        ["Building06_PBR"] = 
        {
        }, -- end of ["Building06_PBR"]
        ["Log ramps 1"] = 
        {
        }, -- end of ["Log ramps 1"]
        ["KJ-2000"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["KJ-2000"]
        ["HESCO_generator"] = 
        {
        }, -- end of ["HESCO_generator"]
        ["Sandbag_17"] = 
        {
        }, -- end of ["Sandbag_17"]
        ["barrels_cargo"] = 
        {
        }, -- end of ["barrels_cargo"]
        ["M4_Sherman"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["M4_Sherman"]
        ["ES44AH"] = 
        {
            ["irsignature"] = 0.15,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ES44AH"]
        ["345 Excavator"] = 
        {
        }, -- end of ["345 Excavator"]
        ["Stanley_LightHouse"] = 
        {
        }, -- end of ["Stanley_LightHouse"]
        ["ATZ-60_Maz"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ATZ-60_Maz"]
        ["Electric locomotive"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Electric locomotive"]
        ["FireExtinguisher01"] = 
        {
        }, -- end of ["FireExtinguisher01"]
        ["SA342Minigun"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["SA342Minigun"]
        ["Camouflage02"] = 
        {
        }, -- end of ["Camouflage02"]
        ["Wespe124"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 0,
            ["threat"] = 10500,
        }, -- end of ["Wespe124"]
        ["M12_GMC"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 18300,
        }, -- end of ["M12_GMC"]
        ["m117_cargo"] = 
        {
        }, -- end of ["m117_cargo"]
        ["VAZ Car"] = 
        {
            ["irsignature"] = 0.065,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["VAZ Car"]
        ["Toolbox01"] = 
        {
        }, -- end of ["Toolbox01"]
        ["ElevatedPlatform_up"] = 
        {
        }, -- end of ["ElevatedPlatform_up"]
        ["Predator TrojanSpirit"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Predator TrojanSpirit"]
        ["Mirage-F1EDA"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1EDA"]
        ["ZIL-4331"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ZIL-4331"]
        ["AH-64D"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["AH-64D"]
        ["Dragonteeth 3"] = 
        {
        }, -- end of ["Dragonteeth 3"]
        ["P20_drivable"] = 
        {
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["P20_drivable"]
        ["Mirage-F1ED"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1ED"]
        ["F-117A"] = 
        {
            ["irsignature"] = 0.15,
        }, -- end of ["F-117A"]
        ["Cone01"] = 
        {
        }, -- end of ["Cone01"]
        ["M10_GMC"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 6000,
        }, -- end of ["M10_GMC"]
        ["Suidae"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Suidae"]
        ["flak36"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 5000,
        }, -- end of ["flak36"]
        ["RLS_19J6"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 150000,
            ["threat"] = 0,
        }, -- end of ["RLS_19J6"]
        ["Tornado IDS"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["Tornado IDS"]
        ["rapier_fsa_blindfire_radar"] = 
        {
            ["irsignature"] = 0.03,
            ["detection"] = 30000,
            ["threat"] = 0,
        }, -- end of ["rapier_fsa_blindfire_radar"]
        ["SH-60B"] = 
        {
            ["irsignature"] = 0.35,
        }, -- end of ["SH-60B"]
        ["ZBD04A"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 4800,
        }, -- end of ["ZBD04A"]
        ["KS-19"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 20000,
        }, -- end of ["KS-19"]
        ["Strela-1 9P31"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 4200,
        }, -- end of ["Strela-1 9P31"]
        ["us carrier shooter"] = 
        {
        }, -- end of ["us carrier shooter"]
        ["M1128 Stryker MGS"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 4000,
        }, -- end of ["M1128 Stryker MGS"]
        ["leander-gun-lynch"] = 
        {
            ["detection"] = 180000,
            ["threat"] = 140000,
        }, -- end of ["leander-gun-lynch"]
        ["MiG-25RBT"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["MiG-25RBT"]
        ["F-86F Sabre"] = 
        {
            ["irsignature"] = 0.25,
        }, -- end of ["F-86F Sabre"]
        ["C-17A"] = 
        {
            ["irsignature"] = 3,
        }, -- end of ["C-17A"]
        ["CVN_71"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 50000,
            ["threat"] = 25000,
        }, -- end of ["CVN_71"]
        ["VINSON"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 30000,
            ["threat"] = 15000,
        }, -- end of ["VINSON"]
        ["Tent03"] = 
        {
        }, -- end of ["Tent03"]
        ["BoomBarrier_closed"] = 
        {
        }, -- end of ["BoomBarrier_closed"]
        ["Sandbag_06"] = 
        {
        }, -- end of ["Sandbag_06"]
        ["Mirage-F1EQ"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1EQ"]
        ["Forrestal"] = 
        {
            ["detection"] = 50000,
            ["threat"] = 25000,
        }, -- end of ["Forrestal"]
        ["PERRY"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 150000,
            ["threat"] = 100000,
        }, -- end of ["PERRY"]
        ["MiG-19P"] = 
        {
            ["irsignature"] = 0.34,
        }, -- end of ["MiG-19P"]
        ["MiG-25PD"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["MiG-25PD"]
        ["Building05_PBR"] = 
        {
        }, -- end of ["Building05_PBR"]
        ["TPZ"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 1000,
        }, -- end of ["TPZ"]
        ["C-101EB"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["C-101EB"]
        ["An-30M"] = 
        {
            ["irsignature"] = 0.5,
        }, -- end of ["An-30M"]
        ["S-200_Launcher"] = 
        {
            ["detection"] = 0,
            ["threat"] = 255000,
        }, -- end of ["S-200_Launcher"]
        ["Mirage-F1M-EE"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1M-EE"]
        ["Mirage-F1CR"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1CR"]
        ["Cargo03"] = 
        {
        }, -- end of ["Cargo03"]
        ["Barrier D"] = 
        {
        }, -- end of ["Barrier D"]
        ["Barrier C"] = 
        {
        }, -- end of ["Barrier C"]
        ["Barrier B"] = 
        {
        }, -- end of ["Barrier B"]
        ["Fire Control Bunker"] = 
        {
        }, -- end of ["Fire Control Bunker"]
        ["rapier_fsa_optical_tracker_unit"] = 
        {
            ["irsignature"] = 0.03,
            ["detection"] = 20000,
            ["threat"] = 0,
        }, -- end of ["rapier_fsa_optical_tracker_unit"]
        ["MJ-1_drivable"] = 
        {
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["MJ-1_drivable"]
        ["La_Combattante_II"] = 
        {
            ["irsignature"] = 0.35,
            ["detection"] = 19000,
            ["threat"] = 4000,
        }, -- end of ["La_Combattante_II"]
        ["KC-135"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["KC-135"]
        ["MQ-9 Reaper"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["MQ-9 Reaper"]
        ["Camouflage01"] = 
        {
        }, -- end of ["Camouflage01"]
        ["LHA_Tarawa"] = 
        {
            ["detection"] = 150000,
            ["threat"] = 20000,
        }, -- end of ["LHA_Tarawa"]
        ["Scud_B"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 285000,
        }, -- end of ["Scud_B"]
        ["NASAMS_LN_C"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 15000,
        }, -- end of ["NASAMS_LN_C"]
        ["B600"] = 
        {
        }, -- end of ["B600"]
        ["CCKW_353"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["CCKW_353"]
        ["SA-18 Igla comm"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 5000,
            ["threat"] = 0,
        }, -- end of ["SA-18 Igla comm"]
        ["T-90"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 5000,
        }, -- end of ["T-90"]
        ["Patriot ECS"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Patriot ECS"]
        ["A-10A"] = 
        {
            ["irsignature"] = 0.53,
        }, -- end of ["A-10A"]
        ["NF-2_LightOn"] = 
        {
        }, -- end of ["NF-2_LightOn"]
        ["Pak40"] = 
        {
            ["irsignature"] = 0.04,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["Pak40"]
        ["Coach cargo open"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Coach cargo open"]
        ["IKARUS Bus"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["IKARUS Bus"]
        ["house1arm"] = 
        {
            ["irsignature"] = 0.007,
            ["detection"] = 0,
            ["threat"] = 800,
        }, -- end of ["house1arm"]
        ["Hawk ln"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 45000,
        }, -- end of ["Hawk ln"]
        ["SA Ski Ramp"] = 
        {
        }, -- end of ["SA Ski Ramp"]
        ["Mi-28N"] = 
        {
            ["irsignature"] = 0.3,
        }, -- end of ["Mi-28N"]
        ["Hawk cwar"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 70000,
            ["threat"] = 0,
        }, -- end of ["Hawk cwar"]
        ["F-16C bl.50"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["F-16C bl.50"]
        ["SA-11 Buk SR 9S18M1"] = 
        {
            ["irsignature"] = 0.095,
            ["detection"] = 100000,
            ["threat"] = 0,
        }, -- end of ["SA-11 Buk SR 9S18M1"]
        ["Sd_Kfz_234_2_Puma"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 2000,
        }, -- end of ["Sd_Kfz_234_2_Puma"]
        ["Schnellboot_type_S130"] = 
        {
            ["irsignature"] = 0.3,
            ["detection"] = 10000,
            ["threat"] = 4000,
        }, -- end of ["Schnellboot_type_S130"]
        ["AS32-31A"] = 
        {
        }, -- end of ["AS32-31A"]
        ["M1097 Avenger"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 5200,
            ["threat"] = 4500,
        }, -- end of ["M1097 Avenger"]
        ["flak38"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 2500,
        }, -- end of ["flak38"]
        ["WindTurbine_11"] = 
        {
        }, -- end of ["WindTurbine_11"]
        ["M4_Tractor"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["M4_Tractor"]
        ["JTAC"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["JTAC"]
        ["Mirage-F1M-CE"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1M-CE"]
        ["M1_37mm"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 5700,
        }, -- end of ["M1_37mm"]
        ["KrAZ6322"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["KrAZ6322"]
        ["Stinger comm dsr"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 5000,
            ["threat"] = 0,
        }, -- end of ["Stinger comm dsr"]
        ["Higgins_boat"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 3000,
            ["threat"] = 1000,
        }, -- end of ["Higgins_boat"]
        ["Su-24MR"] = 
        {
            ["irsignature"] = 1.5,
        }, -- end of ["Su-24MR"]
        ["Jerrycan"] = 
        {
        }, -- end of ["Jerrycan"]
        ["warning_board_b"] = 
        {
        }, -- end of ["warning_board_b"]
        ["ATMZ-5"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["ATMZ-5"]
        ["leander-gun-andromeda"] = 
        {
            ["detection"] = 180000,
            ["threat"] = 140000,
        }, -- end of ["leander-gun-andromeda"]
        ["SA342Mistral"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["SA342Mistral"]
        ["SA-11 Buk CC 9S470M1"] = 
        {
            ["irsignature"] = 0.095,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["SA-11 Buk CC 9S470M1"]
        ["German_tank_wagon"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["German_tank_wagon"]
        ["Soldier M4 GRG"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Soldier M4 GRG"]
        ["Mi-24V"] = 
        {
            ["irsignature"] = 0.5,
        }, -- end of ["Mi-24V"]
        ["MiG-31"] = 
        {
            ["irsignature"] = 3,
        }, -- end of ["MiG-31"]
        ["leander-gun-achilles"] = 
        {
            ["detection"] = 180000,
            ["threat"] = 8000,
        }, -- end of ["leander-gun-achilles"]
        ["M-113"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 1200,
        }, -- end of ["M-113"]
        ["MB-339A"] = 
        {
            ["irsignature"] = 0.2,
        }, -- end of ["MB-339A"]
        ["Soldier M249"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 700,
        }, -- end of ["Soldier M249"]
        ["Mirage 2000-5"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage 2000-5"]
        ["Tent04"] = 
        {
        }, -- end of ["Tent04"]
        ["flak18"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 5000,
        }, -- end of ["flak18"]
        ["Building02_PBR"] = 
        {
        }, -- end of ["Building02_PBR"]
        ["MiG-21Bis"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["MiG-21Bis"]
        ["Roland Radar"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 35000,
            ["threat"] = 0,
        }, -- end of ["Roland Radar"]
        ["MiG-29S"] = 
        {
            ["irsignature"] = 0.77,
        }, -- end of ["MiG-29S"]
        ["Ural ATsP-6"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Ural ATsP-6"]
        ["Su-33"] = 
        {
            ["irsignature"] = 1,
        }, -- end of ["Su-33"]
        ["Coach a tank blue"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Coach a tank blue"]
        ["Centaur_IV"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 6000,
        }, -- end of ["Centaur_IV"]
        ["M4A4_Sherman_FF"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["M4A4_Sherman_FF"]
        ["Christen Eagle II"] = 
        {
            ["irsignature"] = 0.04,
        }, -- end of ["Christen Eagle II"]
        ["Sandbag_03"] = 
        {
        }, -- end of ["Sandbag_03"]
        ["BTR_D"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["BTR_D"]
        ["Nodding_Donkey_Pump"] = 
        {
        }, -- end of ["Nodding_Donkey_Pump"]
        ["AM32a-60_01"] = 
        {
        }, -- end of ["AM32a-60_01"]
        ["AM32a-60_02"] = 
        {
        }, -- end of ["AM32a-60_02"]
        ["Kubelwagen_82"] = 
        {
            ["irsignature"] = 0.065,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Kubelwagen_82"]
        ["flak37"] = 
        {
            ["irsignature"] = 0.01,
            ["detection"] = 0,
            ["threat"] = 5000,
        }, -- end of ["flak37"]
        ["Twall_x1"] = 
        {
        }, -- end of ["Twall_x1"]
        ["Sandbag_15"] = 
        {
        }, -- end of ["Sandbag_15"]
        ["M-2 Bradley"] = 
        {
            ["irsignature"] = 0.095,
            ["detection"] = 0,
            ["threat"] = 3800,
        }, -- end of ["M-2 Bradley"]
        ["M-1 Abrams"] = 
        {
            ["irsignature"] = 0.15,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["M-1 Abrams"]
        ["Cromwell_IV"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["Cromwell_IV"]
        ["F-16C_50"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["F-16C_50"]
        ["PIOTR"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 250000,
            ["threat"] = 190000,
        }, -- end of ["PIOTR"]
        ["Wellcarnsc"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Wellcarnsc"]
        ["Building01_PBR"] = 
        {
        }, -- end of ["Building01_PBR"]
        ["Cargo05"] = 
        {
        }, -- end of ["Cargo05"]
        ["Sandbag_01"] = 
        {
        }, -- end of ["Sandbag_01"]
        ["Camouflage04"] = 
        {
        }, -- end of ["Camouflage04"]
        ["SH-3W"] = 
        {
            ["irsignature"] = 0.72,
        }, -- end of ["SH-3W"]
        ["F-15ESE"] = 
        {
            ["irsignature"] = 0.91,
        }, -- end of ["F-15ESE"]
        ["SpitfireLFMkIXCW"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["SpitfireLFMkIXCW"]
        ["S-300PS 54K6 cp"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["S-300PS 54K6 cp"]
        ["hms_invincible"] = 
        {
            ["detection"] = 100000,
            ["threat"] = 74000,
        }, -- end of ["hms_invincible"]
        ["Tor 9A331"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 25000,
            ["threat"] = 12000,
        }, -- end of ["Tor 9A331"]
        ["Sandbox"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 800,
        }, -- end of ["Sandbox"]
        ["IMPROVED_KILO"] = 
        {
            ["irsignature"] = 0.2,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["IMPROVED_KILO"]
        ["Flakscheinwerfer_37"] = 
        {
            ["irsignature"] = 0.3,
            ["detection"] = 15000,
            ["threat"] = 15000,
        }, -- end of ["Flakscheinwerfer_37"]
        ["T-72B3"] = 
        {
            ["irsignature"] = 0.105,
            ["detection"] = 0,
            ["threat"] = 4000,
        }, -- end of ["T-72B3"]
        ["Ural-375 ZU-23"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 2500,
        }, -- end of ["Ural-375 ZU-23"]
        ["KDO_Mod40"] = 
        {
            ["irsignature"] = 0.03,
            ["detection"] = 30000,
            ["threat"] = 0,
        }, -- end of ["KDO_Mod40"]
        ["Patriot str"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 160000,
            ["threat"] = 0,
        }, -- end of ["Patriot str"]
        ["F-5E-3"] = 
        {
            ["irsignature"] = 0.4,
        }, -- end of ["F-5E-3"]
        ["HEMTT TFFT"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["HEMTT TFFT"]
        ["Cone02"] = 
        {
        }, -- end of ["Cone02"]
        ["M-60"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 8000,
        }, -- end of ["M-60"]
        ["Stennis"] = 
        {
            ["irsignature"] = 0.4,
            ["detection"] = 50000,
            ["threat"] = 25000,
        }, -- end of ["Stennis"]
        ["Shelter02"] = 
        {
        }, -- end of ["Shelter02"]
        ["Mirage-F1CK"] = 
        {
            ["irsignature"] = 0.8,
        }, -- end of ["Mirage-F1CK"]
        ["F-15C"] = 
        {
            ["irsignature"] = 0.85,
        }, -- end of ["F-15C"]
        ["Su-25TM"] = 
        {
            ["irsignature"] = 0.7,
        }, -- end of ["Su-25TM"]
        ["iso_container_small"] = 
        {
        }, -- end of ["iso_container_small"]
        ["Container_generator"] = 
        {
        }, -- end of ["Container_generator"]
        ["Maschinensatz_33"] = 
        {
            ["irsignature"] = 0.07,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Maschinensatz_33"]
        ["TF-51D"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["TF-51D"]
        ["Leclerc"] = 
        {
            ["irsignature"] = 0.12,
            ["detection"] = 0,
            ["threat"] = 3500,
        }, -- end of ["Leclerc"]
        ["Blitz_36-6700A"] = 
        {
            ["irsignature"] = 0.75,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Blitz_36-6700A"]
        ["NASAMS_Command_Post"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["NASAMS_Command_Post"]
        ["Infantry AK ver3"] = 
        {
            ["irsignature"] = 0.004,
            ["detection"] = 0,
            ["threat"] = 500,
        }, -- end of ["Infantry AK ver3"]
        ["MiG-27K"] = 
        {
            ["irsignature"] = 0.69,
        }, -- end of ["MiG-27K"]
        ["Camouflage05"] = 
        {
        }, -- end of ["Camouflage05"]
        ["Log posts 3"] = 
        {
        }, -- end of ["Log posts 3"]
        ["Coach a platform"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Coach a platform"]
        ["iso_container"] = 
        {
        }, -- end of ["iso_container"]
        ["A-10C"] = 
        {
            ["irsignature"] = 0.53,
        }, -- end of ["A-10C"]
        ["Small_LightHouse"] = 
        {
        }, -- end of ["Small_LightHouse"]
        ["speedboat"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 5000,
            ["threat"] = 1000,
        }, -- end of ["speedboat"]
        ["Su-24M"] = 
        {
            ["irsignature"] = 1.5,
        }, -- end of ["Su-24M"]
        ["HESCO_wallperimeter_4"] = 
        {
        }, -- end of ["HESCO_wallperimeter_4"]
        ["HL_KORD"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 1200,
        }, -- end of ["HL_KORD"]
        ["MiG-29A"] = 
        {
            ["irsignature"] = 0.77,
        }, -- end of ["MiG-29A"]
        ["NF-2_LightOff01"] = 
        {
        }, -- end of ["NF-2_LightOff01"]
        ["Ural-375 ZU-23 Insurgent"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 2500,
        }, -- end of ["Ural-375 ZU-23 Insurgent"]
        ["ZTZ96B"] = 
        {
            ["irsignature"] = 0.12,
            ["detection"] = 0,
            ["threat"] = 5000,
        }, -- end of ["ZTZ96B"]
        ["Grad-URAL"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 0,
            ["threat"] = 19000,
        }, -- end of ["Grad-URAL"]
        ["Haystack 2"] = 
        {
        }, -- end of ["Haystack 2"]
        ["HESCO_watchtower_3"] = 
        {
        }, -- end of ["HESCO_watchtower_3"]
        ["Coach a tank yellow"] = 
        {
            ["irsignature"] = 0,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Coach a tank yellow"]
        ["Uboat_VIIC"] = 
        {
            ["irsignature"] = 0.25,
            ["detection"] = 20000,
            ["threat"] = 4000,
        }, -- end of ["Uboat_VIIC"]
        ["Sandbag_09"] = 
        {
        }, -- end of ["Sandbag_09"]
        ["B-1B"] = 
        {
            ["irsignature"] = 3,
        }, -- end of ["B-1B"]
        ["Sandbag_07"] = 
        {
        }, -- end of ["Sandbag_07"]
        ["PLZ05"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 23500,
        }, -- end of ["PLZ05"]
        ["C-47"] = 
        {
            ["irsignature"] = 0.1,
        }, -- end of ["C-47"]
        ["F-16A"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["F-16A"]
        ["leander-gun-ariadne"] = 
        {
            ["detection"] = 150000,
            ["threat"] = 100000,
        }, -- end of ["leander-gun-ariadne"]
        ["F-16A MLU"] = 
        {
            ["irsignature"] = 0.6,
        }, -- end of ["F-16A MLU"]
        ["M1134 Stryker ATGM"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 0,
            ["threat"] = 3800,
        }, -- end of ["M1134 Stryker ATGM"]
        ["FlagPole"] = 
        {
        }, -- end of ["FlagPole"]
        ["Roland ADS"] = 
        {
            ["irsignature"] = 0.085,
            ["detection"] = 12000,
            ["threat"] = 8000,
        }, -- end of ["Roland ADS"]
        ["MiG-29G"] = 
        {
            ["irsignature"] = 0.77,
        }, -- end of ["MiG-29G"]
        ["Twall_x6_3mts"] = 
        {
        }, -- end of ["Twall_x6_3mts"]
        ["F/A-18C"] = 
        {
            ["irsignature"] = 0.73,
        }, -- end of ["F/A-18C"]
        ["warning_board_a"] = 
        {
        }, -- end of ["warning_board_a"]
        ["Ship_Tilde_Supply"] = 
        {
            ["irsignature"] = 0.35,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Ship_Tilde_Supply"]
        ["Sandbag_08"] = 
        {
        }, -- end of ["Sandbag_08"]
        ["Land_Rover_101_FC"] = 
        {
            ["irsignature"] = 0.075,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Land_Rover_101_FC"]
        ["Pz_IV_H"] = 
        {
            ["irsignature"] = 0.1,
            ["detection"] = 0,
            ["threat"] = 3000,
        }, -- end of ["Pz_IV_H"]
        ["Grad_FDDM"] = 
        {
            ["irsignature"] = 0.09,
            ["detection"] = 0,
            ["threat"] = 1000,
        }, -- end of ["Grad_FDDM"]
        ["Concertina wire"] = 
        {
        }, -- end of ["Concertina wire"]
        ["Tetrahydra"] = 
        {
        }, -- end of ["Tetrahydra"]
        ["Log posts 1"] = 
        {
        }, -- end of ["Log posts 1"]
        ["A-50"] = 
        {
            ["irsignature"] = 4,
        }, -- end of ["A-50"]
        ["Patriot AMG"] = 
        {
            ["irsignature"] = 0.05,
            ["detection"] = 0,
            ["threat"] = 0,
        }, -- end of ["Patriot AMG"]
        ["tt_DSHK"] = 
        {
            ["irsignature"] = 0.08,
            ["detection"] = 5000,
            ["threat"] = 1200,
        }, -- end of ["tt_DSHK"]
    }
end

--###### UTIL FUNCTIONS ############################################################################

-- all the below functions are basically elements used in other part of the code. Many of them are basically copy or modified copy of other useful code and script, 
-- the credits list would be quite long but mostly mist, MOOSE, CTLD. When able I kept the original name even if slightly modified.

-- revTODO the code below is not used; an error? -> Chromium: check this out -> nope will be used
local function escape_string(str)
    local replacements = {
        ['%'] = '%%',
        ['^'] = '%^',
        ['$'] = '%$',
        ['('] = '%(',
        [')'] = '%)',
        ['%['] = '%[%]',
        ['{'] = '%{',
        ['}'] = '%}',
        ['.'] = '%.',
        ['*'] = '%*',
        ['+'] = '%+',
        ['-'] = '%-',
        ['?'] = '%?',
        ['\0'] = '%z'
    }
    
    return (str:gsub(".", replacements))
end

local function contains(haystack, needle)
    -- Effettua l'escape dei caratteri speciali nella stringa 'needle'
    local function escape_special_characters(str)
        local replacements = {
            ['%'] = '%%',
            ['^'] = '%^',
            ['$'] = '%$',
            ['('] = '%(',
            [')'] = '%)',
            ['%['] = '%[%]',
            ['{'] = '%{',
            ['}'] = '%}',
            ['.'] = '%.',
            ['*'] = '%*',
            ['+'] = '%+',
            ['-'] = '%-',
            ['?'] = '%?',
            ['\0'] = '%z'
        }
        
        return (str:gsub(".", replacements))
    end

    -- Escape della stringa 'needle'
    local escaped_needle = escape_special_characters(needle)
    
    -- Controlla se 'needle' è contenuta in 'haystack'
    return haystack:find(escaped_needle) ~= nil
end

local function vec3Check(vec3)
    if vec3 then
        if type(vec3) == 'table' then -- assuming name
            if vec3.x and vec3.y and vec3.z then			
                return vec3
            elseif vec3.x and vec3.y and vec3.z == nil then
                AIEN.loggers.get(AIEN.Id):info("vec3Check: vector is vec2, converting to vec3")
                local new_y = land.getHeight({x = vec3.x, y = vec3.y})
                
                if new_y then
                    local new_Vec3 = {x = vec3.x, y = new_y, z = vec3.y}
                    return new_Vec3
                else
                    AIEN.loggers.get(AIEN.Id):info("vec3Check: vector is vec2, but no height found, returning nil")
                    return nil
                end
            else
                AIEN.loggers.get(AIEN.Id):info("vec3Check: wrong vector format")
                return nil
            end
        else
            AIEN.loggers.get(AIEN.Id):info("vec3Check: wrong variable")
            return nil
        end
    else
        AIEN.loggers.get(AIEN.Id):info("vec3Check: missing variable")
        return nil
    end
end

local function getDist(point1, point2)
    local xUnit = point1.x
    local yUnit = nil
    local xZone = point2.x
    local yZone = nil	
	if point1.z then
		yUnit = point1.z
	elseif point1.y then
		yUnit = point1.y
	end
	if point2.z then
		yZone = point2.z
	elseif point2.y then
		yZone = point2.y
	end
    local xDiff = xUnit - xZone
    local yDiff = yUnit - yZone
    return math.sqrt(xDiff * xDiff + yDiff * yDiff)
end

local function groupTableCheck(group)
    if group then
        if type(group) == 'string' then -- assuming name
            local groupTable = Group.getByName(group)

            if not groupTable then
                groupTable = StaticObject.getByName(group)
            end

            if groupTable then
                return groupTable
            else
                return nil
            end
        elseif type(group) == 'table' then
            return group
        else
            AIEN.loggers.get(AIEN.Id):info("groupTableCheck: wrong variable")
            return nil
        end
    else
        AIEN.loggers.get(AIEN.Id):info("groupTableCheck: missing variable")
        return nil
    end
end

local function aie_random(firstNum, secondNum)
    local lowNum, highNum
    if not secondNum then
        highNum = firstNum
        lowNum = 1
    else
        lowNum = firstNum
        highNum = secondNum
    end
    local total = 1
    if math.abs(highNum - lowNum + 1) < 50 then -- if total values is less than 50
        total = math.modf(50/math.abs(highNum - lowNum + 1)) -- make x copies required to be above 50
    end
    local choices = {}
    for i = 1, total do -- iterate required number of times
        for x = lowNum, highNum do -- iterate between the range
            choices[#choices +1] = x -- add each entry to a table
        end
    end
    local rtnVal = math.random(#choices) -- will now do a math.random of at least 50 choices
    for i = 1, 10 do
        rtnVal = math.random(#choices) -- iterate a few times for giggles
    end
    return choices[rtnVal]
end

local function round(num, idp)
    local mult = 10^(idp or 0)
    return math.floor(num * mult + 0.5) / mult
end 

local function buildWP(point, overRideForm, overRideSpeed)

    if point then 
        local wp = {}
        wp.x = point.x

        if point.z then
            wp.y = point.z
        else
            wp.y = point.y
        end
        local form, speed

        if point.speed and not overRideSpeed then
            wp.speed = point.speed
        elseif type(overRideSpeed) == 'number' then
            wp.speed = overRideSpeed
        else
            wp.speed = 18/3.6
        end

        if point.form and not overRideForm then
            form = point.form
        else
            form = overRideForm
        end

        if not form then
            wp.action = 'Off Road'
        else
            form = string.lower(form)
            if form == 'off_road' or form == 'off road' then
                wp.action = 'Off Road'
            elseif form == 'on_road' or form == 'on road' then
                wp.action = 'On Road'
            elseif form == 'rank' or form == 'line_abrest' or form == 'line abrest' or form == 'lineabrest'then
                wp.action = 'Rank'
            elseif form == 'cone' then
                wp.action = 'Cone'
            elseif form == 'diamond' then
                wp.action = 'Diamond'
            elseif form == 'vee' then
                wp.action = 'Vee'
            elseif form == 'echelon_left' or form == 'echelon left' or form == 'echelonl' then
                wp.action = 'EchelonL'
            elseif form == 'echelon_right' or form == 'echelon right' or form == 'echelonr' then
                wp.action = 'EchelonR'
            else
                wp.action = 'Off Road' -- if nothing matched
            end
        end

        wp.type = 'Turning Point'

        return wp
    else
        return false
    end
end

local function getRandPointInCircle(point, radius, innerRadius)
    local theta = 2*math.pi*math.random()
    local rad = math.random() + math.random()
    if rad > 1 then
        rad = 2 - rad
    end

    local radMult
    if innerRadius and innerRadius <= radius then
        radMult = (radius - innerRadius)*rad + innerRadius
    else
        radMult = radius*rad
    end

    if not point.z then --might as well work with vec2/3
        point.z = point.y
    end

    local rndCoord
    if radius > 0 then
        rndCoord = {x = math.cos(theta)*radMult + point.x, y = math.sin(theta)*radMult + point.z}
    else
        rndCoord = {x = point.x, y = point.z}
    end
    return rndCoord
end

local function getRandTerrainPointInCircle(var, radius, innerRadius, requestV3)
    local point = vec3Check(var)	
    if point and radius and innerRadius then
        for i = 1, 5 do
            local coordRun = getRandPointInCircle(point, radius, innerRadius)
            local destlandtype = land.getSurfaceType({coordRun.x, coordRun.z})
            if destlandtype == 1 or destlandtype == 4 then
                if requestV3 == true then
                    local c2 = {x = coordRun.x, y = land.getHeight({x = coordRun.x, y = coordRun.y}), z = coordRun.y}
                    return c2
                else
                    return coordRun
                end
            end
        end
        return nil -- this means that no valid result has found
        
    end
end

local function deepCopy(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for index, value in pairs(object) do
            new_table[_copy(index)] = _copy(value)
        end
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
end

local function multyTypeMessage(var)
    local mexType       = var[1]
    local mexText       = var[2]
    local mexDuration   = var[3]
    local mexPos        = var[4]
    local unitId        = var[5]
    local groupId       = var[6]
    local countryId     = var[7]
    local coaId         = var[8]
    local mexAuthor     = var[9]
    local voiceGender   = var[10]

    -- text message
    if mexType == "text" or mexType == "both" then
        if mexText and type(mexText) == "string" then

            local t = mexDuration
            if not t then t = 30 end

            if unitId then
                trigger.action.outTextForGroup(unitId, mexText, t)            
            elseif groupId then
                trigger.action.outTextForGroup(groupId, mexText, t)
            elseif countryId then
                trigger.action.outTextForCoalition(countryId, mexText, t)
            elseif coaId then
                trigger.action.outTextForCoalition(coaId, mexText, t)
            else
                trigger.action.outText(mexText, t)
            end

        else
            AIEN.loggers.get(AIEN.Id):info("multyTypeMessage, mexText is not a valid input")
        end
    end

end

local function vecmag(vec)
	return (vec.x^2 + vec.y^2 + vec.z^2)^0.5
end


local function getNorthCorrection(gPoint)
	local point = deepCopy(gPoint)
	if not point.z then --Vec2; convert to Vec3
		point.z = point.y
		point.y = 0
	end
	local lat, lon = coord.LOtoLL(point)
	local north_posit = coord.LLtoLO(lat + 1, lon)
	return math.atan2(north_posit.z - point.z, north_posit.x - point.x)
end

local function kmphToMps(kmph)
	return kmph/3.6
end

-- revTODO the code below is not used; an error? -> Chromium: check this out -> nope will be used
local function getHeading(unit, rawHeading)
	local unitpos = unit:getPosition()
	if unitpos then
		local Heading = math.atan2(unitpos.x.z, unitpos.x.x)
		if not rawHeading then
			Heading = Heading + getNorthCorrection(unitpos.p)
		end
		if Heading < 0 then
			Heading = Heading + 2*math.pi	-- put heading in range of 0 to 2*pi
		end
		return Heading
	end
end

local function makeVec3(vec, y)
	if not vec.z then
		if vec.alt and not y then
			y = vec.alt
		elseif not y then
			y = 0
		end
		return {x = vec.x, y = y, z = vec.y}
	else
		return {x = vec.x, y = vec.y, z = vec.z}	-- it was already Vec3, actually.
	end
end

local function avgVec3(tblPos)
    local avgPoint = {x = 0, y = 0, z = 0}
    local numpoints = #tblPos

    if numpoints == 0 then
        return avgPoint
    end

    for _, punto in ipairs(tblPos) do
        avgPoint.x = avgPoint.x + punto.x
        avgPoint.y = avgPoint.y + punto.y
        avgPoint.z = avgPoint.z + punto.z
    end

    avgPoint.x = avgPoint.x / numpoints
    avgPoint.z = avgPoint.z / numpoints
    if avgPoint.x and avgPoint.z then
        avgPoint.y = land.getHeight({x = avgPoint.x, y = avgPoint.z})
        return avgPoint
    else
        return nil
    end
end

local function getGroupSpeed(group)
    local g = groupTableCheck(group)
    if g then
        local units = g:getUnits()
        if units and #units > 0 then
            local s = 0
            local ms = 1000
            local u = 0
            for u, uData in pairs(units) do
                u = u + 1
                local us = vecmag(uData:getVelocity())
                if us and us >= 0 then
                    s = s + us
                    if us < ms then
                        ms = us
                    end
                end
            end
            return s, ms
        else
            return nil
        end
    else
        return nil
    end
end

local function toDegree(angle)
	return angle*180/math.pi
end

local function tostringLL(lat, lon, acc, DMS)

	local latHemi, lonHemi
	if lat > 0 then
		latHemi = 'N'
	else
		latHemi = 'S'
	end

	if lon > 0 then
		lonHemi = 'E'
	else
		lonHemi = 'W'
	end

	lat = math.abs(lat)
	lon = math.abs(lon)

	local latDeg = math.floor(lat)
	local latMin = (lat - latDeg)*60

	local lonDeg = math.floor(lon)
	local lonMin = (lon - lonDeg)*60

	if DMS then	-- degrees, minutes, and seconds.
		local oldLatMin = latMin
		latMin = math.floor(latMin)
		local latSec = round((oldLatMin - latMin)*60, acc)

		local oldLonMin = lonMin
		lonMin = math.floor(lonMin)
		local lonSec = round((oldLonMin - lonMin)*60, acc)

		if latSec == 60 then
			latSec = 0
			latMin = latMin + 1
		end

		if lonSec == 60 then
			lonSec = 0
			lonMin = lonMin + 1
		end

		local secFrmtStr -- create the formatting string for the seconds place
		if acc <= 0 then	-- no decimal place.
			secFrmtStr = '%02d'
		else
			local width = 3 + acc	-- 01.310 - that's a width of 6, for example.
			secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
		end

		return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. '	 '
		.. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi

	else	-- degrees, decimal minutes.
		latMin = round(latMin, acc)
		lonMin = round(lonMin, acc)

		if latMin == 60 then
			latMin = 0
			latDeg = latDeg + 1
		end

		if lonMin == 60 then
			lonMin = 0
			lonDeg = lonDeg + 1
		end

		local minFrmtStr -- create the formatting string for the minutes place
		if acc <= 0 then	-- no decimal place.
			minFrmtStr = '%02d'
		else
			local width = 3 + acc	-- 01.310 - that's a width of 6, for example.
			minFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
		end

		return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. '	 '
		.. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi

	end
end

local function tostringMGRS(MGRS, acc)
	if acc == 0 then
		return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph
	else
		return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph .. ' ' .. string.format('%0' .. acc .. 'd', round(MGRS.Easting/(10^(5-acc)), 0))
		.. ' ' .. string.format('%0' .. acc .. 'd', round(MGRS.Northing/(10^(5-acc)), 0))
	end
end

-- revTODO the code below is not used; an error? -> Chromium: check this out -> nope will be used 
local function zoneToVec3(zone)
    local new = {}
	if type(zone) == 'table' then
		if zone.point then
			new.x = zone.point.x
			new.y = zone.point.y
			new.z = zone.point.z
		elseif zone.x and zone.y and zone.z then
			return zone
		end
		return new
	elseif type(zone) == 'string' then
		zone = trigger.misc.getZone(zone)
		if zone then
			new.x = zone.point.x
			new.y = zone.point.y
			new.z = zone.point.z
			return new
		end
	end
end

local function pointInPolygon(point, poly) -- mist local copy ot f that function

	point = makeVec3(point)
	local px = point.x
	local pz = point.z
	local cn = 0
	local newpoly = deepCopy(poly)

    local polysize = #newpoly
    newpoly[#newpoly + 1] = newpoly[1]

    newpoly[1] = makeVec3(newpoly[1])

    for k = 1, polysize do
        newpoly[k+1] = makeVec3(newpoly[k+1])
        if ((newpoly[k].z <= pz) and (newpoly[k+1].z > pz)) or ((newpoly[k].z > pz) and (newpoly[k+1].z <= pz)) then
            local vt = (pz - newpoly[k].z) / (newpoly[k+1].z - newpoly[k].z)
            if (px < newpoly[k].x + vt*(newpoly[k+1].x - newpoly[k].x)) then
                cn = cn + 1
            end
        end
    end

    return cn%2 == 1
end

local function getPayload(unitName)
    -- refactor to search by groupId and allow groupId and groupName as inputs
	local unitTbl = Unit.getByName(unitName)
	local unitId = unitTbl:getID()
	local gpTbl = unitTbl:getGroup()
	local gpId = gpTbl:getID()

	if gpId and unitId then
		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
											for unitIndex, unitData in pairs(group_data.units) do --group index
												if unitData.unitId == unitId then
													return unitData.payload
												end
											end
										end
									end
								end
							end
						end
					end
				end
			end
		end
	else
		AIEN.loggers.get(AIEN.Id):trace("getPayload error, no gId or unitId")
		
		return false
	end
	return
end

local function ground_buildWP(point, overRideForm, overRideSpeed)

	local wp = {}
	wp.x = point.x

	if point.z then
		wp.y = point.z
	else
		wp.y = point.y
	end

    local form

	if point.speed and not overRideSpeed then
		wp.speed = point.speed
	elseif type(overRideSpeed) == 'number' then
		wp.speed = overRideSpeed
	else
		wp.speed = kmphToMps(20)
	end

	if point.form and not overRideForm then
		form = point.form
	else
		form = overRideForm
	end

	if not form then
		wp.action = 'Cone'
	else
		form = string.lower(form)
		if form == 'off_road' or form == 'off road' then
			wp.action = 'Off Road'
		elseif form == 'on_road' or form == 'on road' then
			wp.action = 'On Road'
		elseif form == 'rank' or form == 'line_abrest' or form == 'line abrest' or form == 'lineabrest'then
			wp.action = 'Rank'
		elseif form == 'cone' then
			wp.action = 'Cone'
		elseif form == 'diamond' then
			wp.action = 'Diamond'
		elseif form == 'vee' then
			wp.action = 'Vee'
		elseif form == 'echelon_left' or form == 'echelon left' or form == 'echelonl' then
			wp.action = 'EchelonL'
		elseif form == 'echelon_right' or form == 'echelon right' or form == 'echelonr' then
			wp.action = 'EchelonR'
		else
			wp.action = 'Cone' -- if nothing matched
		end
	end

	wp.type = 'Turning Point'

	return wp

end

local function dynAdd(ng)

    local newGroup = deepCopy(ng)

    local cntry = newGroup.country
	if newGroup.countryId then
		cntry = newGroup.countryId
	end

	local groupType = newGroup.category
	local newCountry = ''
	-- validate data

	for countryId, countryName in pairs(country.name) do
		if type(cntry) == 'string' then
			cntry = cntry:gsub("%s+", "_")
			if tostring(countryName) == string.upper(cntry) then
				newCountry = countryName
			end
		elseif type(cntry) == 'number' then
			if countryId == cntry then
				newCountry = countryName
			end
		end
	end

	if newCountry == '' then
		AIEN.loggers.get(AIEN.Id):trace("dynAdd Country not found")
		
		return false
	end

	local newCat = ''
	for catName, catId in pairs(Unit.Category) do
		if type(groupType) == 'string' then
			if tostring(catName) == string.upper(groupType) then
				newCat = catName
			end
		elseif type(groupType) == 'number' then
			if catId == groupType then
				newCat = catName
			end
		end

		if catName == 'GROUND_UNIT' and (string.upper(groupType) == 'VEHICLE' or string.upper(groupType) == 'GROUND') then
			newCat = 'GROUND_UNIT'
		elseif catName == 'AIRPLANE' and string.upper(groupType) == 'PLANE' then
			newCat = 'AIRPLANE'
		end
	end

	local typeName
	if newCat == 'GROUND_UNIT' then
		typeName = ' gnd '
	elseif newCat == 'AIRPLANE' then
		typeName = ' air '
	elseif newCat == 'HELICOPTER' then
		typeName = ' hel '
	elseif newCat == 'SHIP' then
		typeName = ' shp '
	elseif newCat == 'BUILDING' then
		typeName = ' bld '
	end    
	if newGroup.groupName or newGroup.name then
		if newGroup.groupName then
			newGroup.name = newGroup.groupName
		elseif newGroup.name then
			newGroup.name = newGroup.name
		end
	end

	if newGroup.clone or not newGroup.name then
		newGroup.name = tostring(newCountry .. tostring(typeName) .. string.format("%04d", tostring(stupidIndex)))
        stupidIndex = stupidIndex + 1
	end

	if not newGroup.hidden then
		newGroup.hidden = false
	end

	if not newGroup.visible then
		newGroup.visible = false
	end

	if (newGroup.start_time and type(newGroup.start_time) ~= 'number') or not newGroup.start_time then
		if newGroup.startTime then
			newGroup.start_time = round(newGroup.startTime)
		else
			newGroup.start_time = 0
		end
	end

    for unitIndex, unitData in pairs(newGroup.units) do
        local originalName = newGroup.units[unitIndex].unitName or newGroup.units[unitIndex].name
        if newGroup.units[unitIndex].unitName or newGroup.units[unitIndex].name then
            if newGroup.units[unitIndex].unitName then
                newGroup.units[unitIndex].name = newGroup.units[unitIndex].unitName
            elseif newGroup.units[unitIndex].name then
                newGroup.units[unitIndex].name = newGroup.units[unitIndex].name
            end
        end
        if newGroup.clone or not unitData.name then
            newGroup.units[unitIndex].name = tostring(newGroup.name .. ' unit' .. unitIndex)
        end

        if not unitData.skill then
            newGroup.units[unitIndex].skill = 'Random' -- provide something here
        end

        if newCat == 'AIRPLANE' or newCat == 'HELICOPTER' then
            if newGroup.units[unitIndex].alt_type and newGroup.units[unitIndex].alt_type ~= 'BARO' or not newGroup.units[unitIndex].alt_type then
                newGroup.units[unitIndex].alt_type = 'RADIO'
            end
            if not unitData.speed then
                if newCat == 'AIRPLANE' then
                    newGroup.units[unitIndex].speed = 150
                elseif newCat == 'HELICOPTER' then
                    newGroup.units[unitIndex].speed = 60
                end
            end
            if not unitData.payload then
                newGroup.units[unitIndex].payload = getPayload(originalName)
            end
            if not unitData.alt then
                if newCat == 'AIRPLANE' then
                    newGroup.units[unitIndex].alt = 2000
                    newGroup.units[unitIndex].alt_type = 'RADIO'
                    newGroup.units[unitIndex].speed = 150
                elseif newCat == 'HELICOPTER' then
                    newGroup.units[unitIndex].alt = 500
                    newGroup.units[unitIndex].alt_type = 'RADIO'
                    newGroup.units[unitIndex].speed = 60
                end
            end
            
        elseif newCat == 'GROUND_UNIT' then
            if nil == unitData.playerCanDrive then
                unitData.playerCanDrive = true
            end
        
        end
    end
    if newGroup.route then
        if newGroup.route and not newGroup.route.points then
            if newGroup.route[1] then
                local copyRoute = deepCopy(newGroup.route)
                newGroup.route = {}
                newGroup.route.points = copyRoute
            end
        end
    else -- if aircraft and no route assigned. make a quick and stupid route so AI doesnt RTB immediately
        --if newCat == 'AIRPLANE' or newCat == 'HELICOPTER' then
            newGroup.route = {}
            newGroup.route.points = {}
            newGroup.route.points[1] = {}
        --end
    end
	newGroup.country = newCountry

    -- update and verify any self tasks
    if newGroup.route and newGroup.route.points then 
        --log:warn(newGroup.route.points)
        for i, pData in pairs(newGroup.route.points) do
            if pData.task and pData.task.params and pData.task.params.tasks and #pData.task.params.tasks > 0 then
                for tIndex, tData in pairs(pData.task.params.tasks) do
                    if tData.params and tData.params.action then  
                        if tData.params.action.id == "EPLRS" then
                            tData.params.action.params.groupId = newGroup.groupId
                        elseif tData.params.action.id == "ActivateBeacon" or tData.params.action.id == "ActivateICLS" then 
                            tData.params.action.params.unitId = newGroup.units[1].unitId
                        end 
                    end
                end
            end
        
        end
    end

	-- sanitize table
	newGroup.groupName = nil
	newGroup.clone = nil
	newGroup.category = nil
	newGroup.country = nil

	newGroup.tasks = {}

	for unitIndex, unitData in pairs(newGroup.units) do
		newGroup.units[unitIndex].unitName = nil
	end

	coalition.addGroup(country.id[newCountry], Unit.Category[newCat], newGroup) -- QUIIIII, problema con ID?

	return newGroup

end

local function genSmokePoints(pos, dist, n)
    local points = {}
    local angle_step = 360 / n 

    for i = 0, n - 1 do
       
        local rad = (i * angle_step) * math.pi / 180
        local x = pos.x + dist * math.cos(rad)
        local z = pos.z + dist * math.sin(rad)
        table.insert(points, {x = x, y = pos.y-50, z = z})
    end

    return points
end

--[[ old function temporary here
local function pcallGetCategory(obj) -- done to avoid DCS errors 
    local function effectiveCheck(obj)
        if obj then
           if obj:isExist() then
                if obj:getPosition() then
                    if Object.getCategory(obj) then
                        return Object.getCategory(obj)
                    else
                        if AIEN.config.AIEN_debugProcessDetail == true then
                            env.info(("AIEN pcallGetCategory, missing category"))
                        end	
                        return nil
                    end
                else
                    if AIEN.config.AIEN_debugProcessDetail == true then
                        env.info(("AIEN pcallGetCategory, missing pos"))
                    end	
                    return nil
                end
            else
                if AIEN.config.AIEN_debugProcessDetail == true then
                    env.info(("AIEN pcallGetCategory, isExist failed"))
                end	
                return nil 
            end
        else
            if AIEN.config.AIEN_debugProcessDetail == true then
				env.info(("AIEN pcallGetCategory, missing obj"))
			end	
            return nil 
        end
    end
    local noError, errorOrResult = pcall(effectiveCheck, obj)
    if noError then
        return errorOrResult
    else
        env.info(string.format("AIEN pcallGetCategory, error returned when calling the function: %s", errorOrResult or ""))
    end
end
--]]--

local function pcallGetCategory(obj) -- done to avoid DCS errors 
    local function effectiveCheck(obj)
        if obj then
           if obj.isExist and obj:isExist() then
                if obj:getPosition() then
                    if Object.getCategory(obj) then
                        return Object.getCategory(obj)
                    else
                        AIEN.loggers.get(AIEN.Id):trace("pcallGetCategory, missing category")
                        
                        return nil
                    end
                else
                    AIEN.loggers.get(AIEN.Id):trace("pcallGetCategory, missing pos")
                    
                    return nil
                end
            else
                AIEN.loggers.get(AIEN.Id):trace("pcallGetCategory, isExist failed")
                
                return nil 
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("pcallGetCategory, missing obj")
			
            return nil 
        end
    end
    local noError, errorOrResult = pcall(effectiveCheck, obj)
    if noError then
        return errorOrResult
    else
        AIEN.loggers.get(AIEN.Id):warn("pcallGetCategory, error returned when calling the function: %s", errorOrResult)
    end
end
--]]--

local function pcallGetCategory(obj) -- done to avoid DCS errors 
    local function effectiveCheck(obj)
        if obj then
           if obj.isExist and obj:isExist() then
                if obj:getPosition() then
                    if Object.getCategory(obj) then
                        return Object.getCategory(obj)
                    else
                        AIEN.loggers.get(AIEN.Id):trace("pcallGetCategory, missing category")
                        
                        return nil
                    end
                else
                    AIEN.loggers.get(AIEN.Id):trace("pcallGetCategory, missing pos")
                    
                    return nil
                end
            else
                AIEN.loggers.get(AIEN.Id):trace("pcallGetCategory, isExist failed")
                
                return nil 
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("pcallGetCategory, missing obj")
			
            return nil 
        end
    end
    local noError, errorOrResult = pcall(effectiveCheck, obj)
    if noError then
        return errorOrResult
    else
        AIEN.loggers.get(AIEN.Id):warn("pcallGetCategory, error returned when calling the function: %s", errorOrResult)
    end
end

-- desanitized functions (if available), for logging, table printing and debug purposes

-- You should never run DCS desanitized unless specifically knowing the risks. However, if you already do that, for debug purposes AIEN will take advantages of the available io and lfs
-- to print out tables of the databases created in it.

if AIEN_io and AIEN_lfs then
	AIEN.loggers.get(AIEN.Id):info("loading desanitized additional function")

    function IntegratedbasicSerialize(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
    
    function Integratedserialize(name, value, level)
        -----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 IntegratedbasicSerialize(o)
            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"..level.."{\n")
    
                for k,v in pairs(value) do -- serialize its fields
                local key
                if type(k) == "number" then
                    key = string.format("[%s]", k)
                else
                    key = string.format("[%q]", k)
                end
    
                table.insert(var_str_tbl, Integratedserialize(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
            print("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 IntegratedserializeWithCycles(name, value, saved)
        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 IntegratedbasicSerialize(o)
            end
        end
    
        local t_str = {}
        saved = saved or {}       -- initial value
        if ((type(value) == 'string') or (type(value) == 'number') or (type(value) == 'table') or (type(value) == 'boolean')) then
            table.insert(t_str, name .. " = ")
            if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then
                table.insert(t_str, basicSerialize(value) ..  "\n")
            else
    
                if saved[value] then    -- value already saved?
                    table.insert(t_str, saved[value] .. "\n")
                else
                    saved[value] = name   -- save name for next time
                    table.insert(t_str, "{}\n")
                    for k,v in pairs(value) do      -- save its fields
                        local fieldname = string.format("%s[%s]", name, basicSerialize(k))
                        table.insert(t_str, IntegratedserializeWithCycles(fieldname, v, saved))
                    end
                end
            end
            return table.concat(t_str)
        else
            return ""
        end
    end

	function tableShow(tbl, loc, indent, tableshow_tbls)
		tableshow_tbls = tableshow_tbls or {} --create table of tables
		loc = loc or ""
		indent = indent or ""
		if type(tbl) == 'table' then --function only works for tables!
			tableshow_tbls[tbl] = loc
			
			local tbl_str = {}

			tbl_str[#tbl_str + 1] = indent .. '{\n'
			
			for ind,val in pairs(tbl) do -- serialize its fields
				if type(ind) == "number" then
					tbl_str[#tbl_str + 1] = indent 
					tbl_str[#tbl_str + 1] = loc .. '['
					tbl_str[#tbl_str + 1] = tostring(ind)
					tbl_str[#tbl_str + 1] = '] = '
				else
					tbl_str[#tbl_str + 1] = indent 
					tbl_str[#tbl_str + 1] = loc .. '['
					tbl_str[#tbl_str + 1] = IntegratedbasicSerialize(ind)
					tbl_str[#tbl_str + 1] = '] = '
				end
						
				if ((type(val) == 'number') or (type(val) == 'boolean')) then
					tbl_str[#tbl_str + 1] = tostring(val)
					tbl_str[#tbl_str + 1] = ',\n'		
				elseif type(val) == 'string' then
					tbl_str[#tbl_str + 1] = IntegratedbasicSerialize(val)
					tbl_str[#tbl_str + 1] = ',\n'
				elseif type(val) == 'nil' then -- won't ever happen, right?
					tbl_str[#tbl_str + 1] = 'nil,\n'
				elseif type(val) == 'table' then
					if tableshow_tbls[val] then
						tbl_str[#tbl_str + 1] = tostring(val) .. ' already defined: ' .. tableshow_tbls[val] .. ',\n'
					else
						tableshow_tbls[val] = loc ..  '[' .. IntegratedbasicSerialize(ind) .. ']'
						tbl_str[#tbl_str + 1] = tostring(val) .. ' '
						tbl_str[#tbl_str + 1] = tableShow(val,  loc .. '[' .. IntegratedbasicSerialize(ind).. ']', indent .. '    ', tableshow_tbls)
						tbl_str[#tbl_str + 1] = ',\n'  
					end
				elseif type(val) == 'function' then
					if debug and debug.getinfo then
						local fcnname = tostring(val)
						local info = debug.getinfo(val, "S")
						if info.what == "C" then
							tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', C function') .. ',\n'
						else 
							if (string.sub(info.source, 1, 2) == [[./]]) then
								tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', defined in (' .. info.linedefined .. '-' .. info.lastlinedefined .. ')' .. info.source) ..',\n'
							else
								tbl_str[#tbl_str + 1] = string.format('%q', fcnname .. ', defined in (' .. info.linedefined .. '-' .. info.lastlinedefined .. ')') ..',\n'
							end
						end
						
					else
						tbl_str[#tbl_str + 1] = 'a function,\n'	
					end
				else
					tbl_str[#tbl_str + 1] = 'unable to serialize value type ' .. IntegratedbasicSerialize(type(val)) .. ' at index ' .. tostring(ind)
				end
			end
			
			tbl_str[#tbl_str + 1] = indent .. '}'
			return table.concat(tbl_str)
		end
	end

	function dumpTableAIEN(fname, tabledata, varInt)
		
        if AIEN_lfs and AIEN_io then
            local fdir = AIEN_lfs.writedir() .. [[Logs\]] .. fname
            local f = AIEN_io.open(fdir, 'w')
            local str = nil
            if varInt then
                if varInt == "basic" then
                    str = IntegratedbasicSerialize(tabledata)
                elseif varInt == "cycles" then
                    str = IntegratedserializeWithCycles(fname, tabledata)
                elseif varInt == "int" then
                    str = Integratedserialize(fname, tabledata)
                else
                    str = IntegratedserializeWithCycles(fname, tabledata)
                end
            else
                str = IntegratedserializeWithCycles(fname, tabledata)
            end
            
            if f then
                f:write(str)
                f:close()
            end
		end
	end		

	AIEN.loggers.get(AIEN.Id):info("desanitized additional function loaded")
end

local function round(num, idp)
    local mult = 10^(idp or 0)
    return math.floor(num * mult + 0.5) / mult
end 

local function getReactionTime(avg_skill)
    if avg_skill then -- 
        local multiplier = 10/(avg_skill/10)/4
        local min = math.floor(rndMinRT_xper*multiplier)
        local max = math.floor(rndMacRT_xper*multiplier)
        return aie_random(min, max)
    else
        return aie_random(3, 15)
    end
end

local function groupAllowedForAI(group)
    if group and group:isExist() and group:getUnits() and #group:getUnits() > 0 then
        if contains(group:getName(), AIEN.config.AIEN_xcl_tag) then
            return false
        end
        if AIEN.config.AIEN_icl_tag then
            if contains(group:getName(), AIEN.config.AIEN_icl_tag) then
                return true
            else
                return false
            end
        end
    end
    return true
end

--###### GROUP AI QUERY FUNCTIONS ##################################################################

-- Below functions has been created to query ground groups for informations about them, most of them used in the key getSA functions that
-- try to built the "situational awareness" of a group. When done, each time the FSM cycle goes to that group,  it register bunch of info 
-- so that these would be fastly accessibile during reactions or decision making.


--## CAPABILITY CHECKS -- these exist to identify some key characteristics of the group.
-- revTODO the code below is not used; an error? -> Chromium: check this out -> nope will be used
local function group_hasAttribute(group, attribute) -- group tbl, attribute string (reference on DCS attributes) 
    if group then		
        local units = group:getUnits()
		if units and #units > 0 then
			for _, uData in pairs(units) do
				if uData:hasAttribute(attribute) then
					return true
				end
			end
			return false
		else
		    AIEN.loggers.get(AIEN.Id):trace("group_hasAttribute, no units retrievable")
			
			return false
		end
	else
        AIEN.loggers.get(AIEN.Id):trace("group_hasAttribute, missing variable")
        
		return false		
    end
end

-- revTODO the code below is not used; an error? -> Chromium: check this out -> nope will be used
local function group_hasSensors(group, sensor) -- group tbl, attribute string (reference on DCS attributes) 
    if group then		
        local units = group:getUnits()
		if units and #units > 0 then
		
			--[[
			Unit.SensorType = {
			  OPTIC     = 0,
			  RADAR     = 1,
			  IRST      = 2,
			  RWR       = 3
			}
			
			Unit.OpticType = {
			  TV     = 0, --TV-sensor
			  LLTV   = 1, --Low-level TV-sensor
			  IR     = 2  --Infra-Red optic sensor
			}		

			Unit.RadarType = {
			  AS    = 0, --air search radar
			  SS    = 1 --surface/land search radar
			}		
			--]]--			
		
			local optic, ir, radar, irst
			for _, uData in pairs(units) do
				if uData:hasSensors(0) then
					optic = true
				end
				if uData:hasSensors(0, 2) then
					ir = true
				end
				if uData:hasSensors(1) then
					radar = true
				end
				if uData:hasSensors(2) then
					irst =  true
				end
			end
			
			return optic, ir, radar, irst
		else
		    AIEN.loggers.get(AIEN.Id):trace("hasSensors no units retrievable")
			
			return false
		end
	else
        AIEN.loggers.get(AIEN.Id):trace("hasSensors missing variable")
        
		return false		
    end
end


--## INFORMATIVE CHECKS -- these are basic informative "get" functions
local function groupStatus(group)
    if group and group:isExist() == true then
        local units = group:getUnits()
		local curLife 	= 0
		local initLife 	= 0
		if units and #units > 0 then
			for _, uData in pairs(units) do
				if uData:isExist() then
					curLife = curLife + uData:getLife()
					initLife = initLife + uData:getLife0()
				end
			end
		end
	
        if curLife == initLife then
            return false, 1, curLife
        else
			local ratio = math.floor(curLife/initLife*10)/10
            return true, ratio, curLife
        end
    end
end

local function groupLowAmmo(group)
    if group and group:isExist() == true then
        local tblUnits = group:getUnits()
        local groupSize = group:getSize()
        local groupOutAmmo = 0
        
        if tblUnits and groupSize then
            if table.getn(tblUnits) > 0 then
                for uId, uData in pairs(tblUnits) do
                    local uAmmo = uData:getAmmo()
                    if uAmmo then
                        for aId, aData in pairs(uAmmo) do
                            if aData.count == 0 then
                                groupOutAmmo = groupOutAmmo + 1
                            end
                        end
                    else    
                        groupOutAmmo = groupOutAmmo + 1
                    end
                end
            else
                AIEN.loggers.get(AIEN.Id):info("AIEN.groupLowAmmo, tblUnits is 0")
                return false				
            end
        else
            AIEN.loggers.get(AIEN.Id):info("AIEN.groupLowAmmo, missing tblUnits or groupSize")
            return false		
        end

        local fraction = groupOutAmmo/tonumber(groupSize)
        if fraction then
            if fraction > AIEN.config.outAmmoLowLevel then
                return true
            else
                return false
            end
        else
            AIEN.loggers.get(AIEN.Id):info("AIEN.groupLowAmmo, error calculating fraction")
            return false		
        end
    end
end

local function groupHasLosses(group)
    if group and group:isExist() == true then
        local curSize = group:getSize()
        local iniSize = group:getInitialSize()
        if iniSize == curSize then
            return false, 1
        else
			local ratio = math.floor(curSize/iniSize*10)/10
            return true, ratio
        end
    end
end

local function hasTargets(group, report)
	if group and group:isExist() == true then
		local tblUnits = Group.getUnits(group)

		if table.getn(tblUnits) > 0 then
			local tbltargets = {}
			for _, uData in pairs(tblUnits) do
				local uController = uData:getController()
				local utblTargets = uController:getDetectedTargets()
				if utblTargets then
					if table.getn(utblTargets) > 0 then
						if report and report == true then
							return true
						else
							for _, utData in pairs(utblTargets) do
                                if utData.object and utData.object:isExist() == true then
								    tbltargets[utData.object.id_] = utData
                                end
							end
						end
					end
				end
			end
			
			if tbltargets and tbltargets ~= {} then
				return true, tbltargets
			end
			
			return false
			
		else
			AIEN.loggers.get(AIEN.Id):info("AIEN.hasTargets: tblUnits has 0 units")
			return false			
		end
	else
		AIEN.loggers.get(AIEN.Id):info("AIEN.hasTargets: group is nil")
		return false	
	end	
end

local function getGroupClass(group) 

	if group and group:isExist() == true then     
		local units = group:getUnits()
		local coa = group:getCoalition()
		local cls = "none"

		if units and coa then
            local clsCount = {}

			for uId, unit in pairs(units) do -- first unit define group class
				if unit:hasAttribute("Ground Units") then
					if unit:hasAttribute("Tanks") then
						if clsCount["MBT"] then
                            clsCount["MBT"] = clsCount["MBT"] + 1
                        else
                            clsCount["MBT"] = 1
                        end
                        if uId == 1 then
                            cls = "MBT"
                        end
                    elseif unit:hasAttribute("ATGM") then
						if clsCount["ATGM"] then
                            clsCount["ATGM"] = clsCount["ATGM"] + 1
                        else
                            clsCount["ATGM"] = 1
                        end
                        if uId == 1 then
                            cls = "ATGM"
                        end                        						
					elseif unit:hasAttribute("Indirect fire") and not unit:hasAttribute("SS_missile") then
						if unit:hasAttribute("MLRS") then
                            if clsCount["MLRS"] then
                                clsCount["MLRS"] = clsCount["MLRS"] + 1
                            else
                                clsCount["MLRS"] = 1
                            end
                            if uId == 1 then
                                cls = "MLRS"
                            end   
                        elseif unit:hasAttribute("Artillery") then
                            if clsCount["ARTY"] then
                                clsCount["ARTY"] = clsCount["ARTY"] + 1
                            else
                                clsCount["ARTY"] = 1
                            end
                            if uId == 1 then
                                cls = "ARTY"
                            end   
                        end						
					elseif unit:hasAttribute("SS_missile") then
                        if clsCount["MISSILE"] then
                            clsCount["MISSILE"] = clsCount["MISSILE"] + 1
                        else
                            clsCount["MISSILE"] = 1
                        end
                        if uId == 1 then
                            cls = "MISSILE"
                        end    
					elseif unit:hasAttribute("MANPADS") then
                        if clsCount["MANPADS"] then
                            clsCount["MANPADS"] = clsCount["MANPADS"] + 1
                        else
                            clsCount["MANPADS"] = 1
                        end
                        if uId == 1 then
                            cls = "MANPADS"
                        end                          
					elseif unit:hasAttribute("Air Defence vehicles") then
                        if clsCount["SHORAD"] then
                            clsCount["SHORAD"] = clsCount["SHORAD"] + 1
                        else
                            clsCount["SHORAD"] = 1
                        end
                        if uId == 1 then
                            cls = "SHORAD"
                        end  						
                    elseif unit:hasAttribute("AAA") then    
                        if clsCount["AAA"] then
                            clsCount["AAA"] = clsCount["AAA"] + 1
                        else
                            clsCount["AAA"] = 1
                        end
                        if uId == 1 then
                            cls = "AAA"
                        end                         
                    elseif unit:hasAttribute("SAM elements") then    
                        if clsCount["SAM"] then
                            clsCount["SAM"] = clsCount["SAM"] + 1
                        else
                            clsCount["SAM"] = 1
                        end
                        if uId == 1 then
                            cls = "SAM"
                        end   
                    elseif unit:hasAttribute("Armored vehicles") then
						if unit:hasAttribute("IFV") then
                            if clsCount["IFV"] then
                                clsCount["IFV"] = clsCount["IFV"] + 1
                            else
                                clsCount["IFV"] = 1
                            end
                            if uId == 1 then
                                cls = "IFV"
                            end   
                        elseif unit:hasAttribute("APC") then
                            if clsCount["APC"] then
                                clsCount["APC"] = clsCount["APC"] + 1
                            else
                                clsCount["APC"] = 1
                            end
                            if uId == 1 then
                                cls = "APC"
                            end   
                        end
					elseif unit:hasAttribute("Armed vehicles") then
                        if clsCount["RECCE"] then
                            clsCount["RECCE"] = clsCount["RECCE"] + 1
                        else
                            clsCount["RECCE"] = 1
                        end
                        if uId == 1 then
                            cls = "RECCE"
                        end   						
					elseif unit:hasAttribute("Unarmed vehicles") and unit:hasAttribute("Trucks") then
                        if clsCount["LOGI"] then
                            clsCount["LOGI"] = clsCount["LOGI"] + 1
                        else
                            clsCount["LOGI"] = 1
                        end
                        if uId == 1 then
                            cls = "LOGI"
                        end                            
                    elseif unit:hasAttribute("Infantry") then
                        if clsCount["INF"] then
                            clsCount["INF"] = clsCount["INF"] + 1
                        else
                            clsCount["INF"] = 1
                        end
                        if uId == 1 then
                            cls = "INF"
                        end 
                    else
                        if clsCount["UNKN"] then
                            clsCount["UNKN"] = clsCount["UNKN"] + 1
                        else
                            clsCount["UNKN"] = 1
                        end
                        if uId == 1 then
                            cls = "UNKN"
                        end                     
					end
                elseif unit:hasAttribute("Air") then
                    cls = "ARBN"
                elseif unit:hasAttribute("Ships") then
                    cls = "SHIP"
				end
			end

            local mClass = nil
            local mVal = 2
            for class, num in pairs(clsCount) do
                if num > mVal then -- at least 3 units
                    mClass = class
                    mVal = num
                end
            end
            if mClass then
                cls = mClass
            end
            
            AIEN.loggers.get(AIEN.Id):trace("getGroupClass, group %s class %s", group and group:getName(), cls)
            
			return cls

		else
			AIEN.loggers.get(AIEN.Id):info("getGroupClass, missing units")
			return false
		end
	else
		AIEN.loggers.get(AIEN.Id):info("getGroupClass, missing group")
		return false
	end
end

local function getUnitClass(unit) 

	if unit and unit:isExist() == true then     
		local coa = unit:getCoalition()
		local cls = "none"

		if coa then
            if unit:hasAttribute("Air") then
                cls = "ARBN"
            elseif unit:hasAttribute("Ships") then
                cls = "SHIP"
            elseif unit:hasAttribute("Ground Units") then
                if unit:hasAttribute("Tanks") then
                    cls = "MBT"
                elseif unit:hasAttribute("Indirect fire") and not unit:hasAttribute("SS_missile") then
                    if unit:hasAttribute("MLRS") then
                        cls = "MLRS"
                    elseif unit:hasAttribute("Artillery") then
                        cls = "ARTY"
                    end						
                elseif unit:hasAttribute("SS_missile") then
                    cls = "MISSILE"
                elseif unit:hasAttribute("MANPADS") then
                    cls = "MANPADS"                         
                elseif unit:hasAttribute("Air Defence vehicles") then
                    cls = "SHORAD"
                elseif unit:hasAttribute("AAA") then    
                    cls = "AAA"                        
                elseif unit:hasAttribute("SAM elements") then    
                    cls = "SAM" 
                elseif unit:hasAttribute("ATGM") then
                    cls = "ATGM"                       
                elseif unit:hasAttribute("Armored vehicles") then
                    if unit:hasAttribute("IFV") then
                        cls = "IFV" 
                    elseif unit:hasAttribute("APC") then
                        cls = "APC" 
                    end                    
                elseif unit:hasAttribute("Armed vehicles") then
                    cls = "RECCE"                    
                elseif unit:hasAttribute("Unarmed vehicles") and unit:hasAttribute("Trucks") then
                    cls = "LOGI"                    
                elseif unit:hasAttribute("Infantry") then
                    cls = "INF"
                else
                    cls = "UNKN"
                end
            end
            
            AIEN.loggers.get(AIEN.Id):trace("getUnitClass, unit %s class %s", unit and unit:getName(), cls)
            
			return cls

		else
			AIEN.loggers.get(AIEN.Id):info("getUnitClass, missing coa")
			return false
		end
	else
		AIEN.loggers.get(AIEN.Id):info("getUnitClass, missing unit")
		return false
	end
end

local function getGroupSkillNum(g) -- important: this try to create an "average skill scoring number" that will be used a lot elsewhere, i.e. for defining reaction time or even the available reactions. AIEN does not handle well the "Random" skill value (cause DCS skill is not available in real time): for best purpose, you should define the skill value of your ground units in the ME.
    local id = g:getID()
    --AIEN.loggers.get(AIEN.Id):info("getGroupSkillNum: skLevel %s", g and g:getName())
	for _,coalition in pairs(env.mission["coalition"]) do
		for _,country in pairs(coalition["country"]) do
			for attrID,attr in pairs(country) do
				if (type(attr)=="table") then
					if attrID == "vehicle" then
						for _,group in pairs(attr["group"]) do
							if (group) then	
                                if group.groupId == id then
                                    --AIEN.loggers.get(AIEN.Id):info("getGroupSkillNum: skLevel %s group found", g and g:getName())
                                    local skLevel = 0
                                    local unitsCount = 0
                                
                                    for _, unit in pairs(group["units"]) do
                                        local skTbl = skills[unit.skill]
                                        if skTbl then
                                            local val = skTbl.skillVal
                                            if unit.skill == "Random" then
                                                val = aie_random(4,12)
                                            end
                                            skLevel = skLevel + val
                                            unitsCount = unitsCount + 1
                                            --AIEN.loggers.get(AIEN.Id):info("getGroupSkillNum: skLevel %s, unit num %s", skLevel, unitsCount)
                                        end
                                    end

                                    if skLevel > 0 then
                                        local k =  math.floor((skLevel/unitsCount)*10)/10
                                        AIEN.loggers.get(AIEN.Id):trace("getGroupSkillNum: skLevel %s", k)
                                        
                                        return k
                                    else
                                        return 3
                                    end
                                end
							end
						end	
					end
				end
			end
		end
	end	
    --AIEN.loggers.get(AIEN.Id):info("getGroupSkillNum: sklevel not retournable, going random")
    return aie_random(2,5)
end

local function getRanges(group)
	if group and group:isExist() == true then
		local units = group:getUnits()
        local maxDec = 0
        local maxThr = 0
        for _, uData in pairs(units) do
            local t = uData:getTypeName()
            if t then
                local tData = tblThreatsRange[t]
                if tData then
                    if tData.detection and tData.detection > maxDec then
                        maxDec = tData.detection
                    end
                    if tData.threat and tData.threat > maxThr then
                        maxThr = tData.threat
                    end
                end
            end
        end

        if maxDec == 0 then
            maxDec = nil
        end
        if maxThr == 0 then
            maxThr = nil
        end

        return maxDec, maxThr
		
	else
		AIEN.loggers.get(AIEN.Id):trace("getRanges failed, group variable is nil")
		
		
	end
end

local function getLeadPos(group)

	if group and group:isExist() == true then
		local units = group:getUnits()

		local leader = units[1]
		if leader then
			if not Unit.isExist(leader) then	-- SHOULD be good, but if there is a bug, this code future-proofs it then.
				local lowestInd = math.huge
				for ind, unit in pairs(units) do
					if Unit.isExist(unit) and ind < lowestInd then
						lowestInd = ind
						return unit:getPosition().p
					end
				end
			end
		end
		if leader and Unit.isExist(leader) then	-- maybe a little too paranoid now...
			return leader:getPosition().p
		end
	else
		AIEN.loggers.get(AIEN.Id):trace("getLeadPos failed, group variable is nil")
		
		
	end
end

local function getTroops(group)
	if group and group:isExist() then
		if group:getUnits() and #group:getUnits() > 0 then
			local troopsTbl = {}
			for _, uData in pairs(group:getUnits()) do              
				local mount = mountedDb[tostring(uData:getID())]
				if mount and uData then
					troopsTbl[uData:getID()] = {u = uData, t = mount}
				end
			end
			
			if troopsTbl and next(troopsTbl) ~= nil then
                AIEN.loggers.get(AIEN.Id):trace("AIEN.getTroops, returning troopstbl for: %s", group and group:getName())
                
				return troopsTbl
			end
		end
	end
	return nil
end

local function getDangerClose(vec3, coa, range)
    if vec3 and type(vec3) == "table" and coa then
        if vec3.x and vec3.y and vec3.z then
            if not range or type(range) ~= "number" then
                AIEN.loggers.get(AIEN.Id):trace("getDangerClose: range missing reverted to 500 m ")
                
                range = 500
            end

            -- check targets   
            local firePoint = vec3
            local friendly = nil
            local _volume = {
                id = world.VolumeType.SPHERE,
                params = {
                    point = firePoint,
                    radius = range,
                },
            }

            local _search = function(_obj)
                pcall(function()
                    if _obj ~= nil and _obj:isExist() and Object.getCategory(_obj) == 1 and _obj:getCoalition() == coa then
                        friendly = true
                        AIEN.loggers.get(AIEN.Id):trace("getDangerClose: found friendly unit")
                        
                        return -- is this ok?
                    end
                end)
            end
            world.searchObjects(Object.Category.UNIT, _volume, _search)

            if friendly == true then
                return true
            end
            return false

        end
    end
end

local function groupInZone(group)
    local point = getLeadPos(group)
    local zone = nil
    
    if point then
    
        if env.mission and env.mission.triggers and env.mission.triggers.zones and #env.mission.triggers.zones > 0 then
            for _, zData in pairs(env.mission.triggers.zones) do
                if zData.name == AIEN.config.AIEN_zoneFilter then
                    zone = zData
                    zone.center = {x = zone.x, y = land.getHeight({x = zone.x, y = zone.y}), z = zone.y}
                end
            end
        end

        if not zone then
            return true
        else
            if zone.verticies then
                if pointInPolygon(point, zone.verticies) == true then
                    return true
                else
                    return false
                end
            elseif zone.radius then
                if getDist(point, zone.center) < zone.radius then
                    return true
                else
                    return false
                end
            end

        end
    end
end

--## AWARENESS CONSTRUCTION FOR FSM USE -- the core of the reaction decision making behaviour: this functions use the upper ones to try to built a virtual situational awareness, and also collect for faster access some key informations.

local function getSA(group) -- built a situational awareness check
    
	if group and group:isExist() == true then
        local dbEntry = groundgroupsDb[group:getID()] or droneunitDb[group:getID()]

        if dbEntry then

            local sa = {}

            -- derivable functions
            sa.enInContact, sa.targets 	= hasTargets(group)
            sa.loss 		            = groupHasLosses(group)
            sa.dmg, sa.life, sa.str     = groupStatus(group) -- str must be added
            sa.low_ammo 	            = groupLowAmmo(group)
            sa.pos			            = getLeadPos(group)
            sa.coa                      = group:getCoalition()
            sa.det                      = dbEntry["detection"]
            sa.rng                      = dbEntry["threat"]
            sa.cls                      = dbEntry["class"]
            sa.nearAlly                 = nil -- table {n = amount, p = nearest position, s = strength as life count}
            sa.nearEnemy                = nil -- table {n = amount, p = nearest position, s = strength as life count}

            if sa.pos and sa.coa then

                -- fix potential det and range issue
                if not sa.rng then
                    if sa.cls == "ARTY" then
                        sa.rng = 15000
                    elseif sa.cls == "MLRS" then
                        sa.rng = 30000
                    elseif sa.cls == "ATGM" then
                        sa.rng = 4000
                    elseif sa.cls == "UAV" then
                        sa.rng = 8000                
                    else
                        sa.rng = 2000
                    end
                end
                if not sa.det then
                    if sa.cls == "ARTY" then
                        sa.det = 2000
                    elseif sa.cls == "MLRS" then
                        sa.det = 2000
                    elseif sa.cls == "ATGM" then
                        sa.det = 4000
                    elseif sa.cls == "UAV" then
                        sa.rng = 40000                
                    else
                        sa.det = 2000
                    end
                end 
            
                -- nearby allies (within proxyUnitsDistance)
                local an = 0
                local as = 0
                local near_a = nil
                local _volume = {
                    id = world.VolumeType.SPHERE,
                    params = {
                        point = sa.pos,
                        radius = AIEN.config.proxyUnitsDistance,
                    },
                }
                local _search = function(_obj)
                    pcall(function()
                        if _obj ~= nil and Object.getCategory(_obj) == 1 and _obj:isExist() then
                            local o_coa = _obj:getCoalition()
                            local o_pos = _obj:getPosition().p
                            local o_str = _obj:getLife()
                            if o_coa and o_pos and o_str then
                                if o_coa ~= 0 then -- skip neutral
                                    if o_coa == sa.coa then -- ally
                                        local md = AIEN.config.proxyUnitsDistance
                                        local d = getDist(sa.pos, o_pos)
                                        an = an + 1
                                        as = as + o_str
                                        if d < md then
                                            md = d
                                            near_a = o_pos
                                        end
                                    end
                                end
                            end
                        end
                    end)
                end
                world.searchObjects(Object.Category.UNIT, _volume, _search)	
                if an and near_a and as then
                    sa.nearAlly = {n = an, p = near_a, s = as}
                end

                -- update intelDb
                if sa.targets and sa.targets ~= {} then
                    for _, tgtData in pairs(sa.targets) do
                        local tgt = tgtData.object

                        local check = pcallGetCategory(tgt)

                        if check == 1 then
                            if tgt and tgt:isExist() then
                                local t_id = tgt:getID()
                                local t_pos = tgt:getPosition().p
                                local t_coa = tgt:getCoalition()
                                local t_life = tgt:getLife()
                                local t = math.floor(timer.getTime())
                                local s = vecmag(tgt:getVelocity())

                                local knownType = tgt.type
                                local t_type = nil
                                if knownType == true then
                                    t_type = tgt:getTypeName()
                                else
                                    t_type = "unknown"
                                end

                                local ob_cat = pcallGetCategory(initiator)
                                local t_ucat = nil
                                local t_scat = nil
                                if ob_cat and ob_cat == 1 then
                                    t_ucat = tgt:getCategory()
                                elseif ob_cat and ob_cat == 3 then
                                    t_scat = tgt:getCategory()
                                end

                                local ob_desc = tgt:getDesc()
                                local t_attr = nil
                                if ob_desc and ob_desc.attributes then
                                    t_attr = ob_desc.attributes
                                end

                                local t_cls = getUnitClass(tgt)
                                intelDb[t_id] = {obj = tgt, pos = t_pos, coa = t_coa, life = t_life, record = t, speed = s, type = t_type, ucat = t_ucat, scat = t_scat, attr = t_attr, cls = t_cls, identifier = sa.cls}
                            
                            end
                        end
                    end           
                end        

                -- nearby enemies (use combined intel source DBs) + update intelDb
                local near_e = nil
                local es = 0
                local en = 0
                local dist = nil
                for iId, iData in pairs(intelDb) do
                    if iData.obj:isExist() then
                        if iData.coa ~= sa.coa and iData.coa ~= 0 and (timer.getTime() - iData.record) < AIEN.config.intelDbTimeout then
                            local d = getDist(sa.pos, iData.pos)
                            if d < AIEN.config.proxyUnitsDistance then
                                en = en + 1
                                es = es + iData.life
                                if d < AIEN.config.proxyUnitsDistance then
                                    near_e = iData.pos
                                    dist = d
                                end
                            end                
                        end
                    else
                        intelDb[iId] = nil -- removing the non existing object anymore. Do this any available cycle of intelDb
                    end
                end
                if en and near_e and es then
                    sa.nearEnemy = {n = en, p = near_e, s = es, d = dist}
                end

                return sa
            else
                return false
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("group not in db")
            
            return false
        end
	else
		AIEN.loggers.get(AIEN.Id):trace("group doesn't exist")
		
		return false
	end
end


--###### GROUP AI COMMAND FUNCTIONS ################################################################

-- Below functions has been created to give command to ground groups, from the most basic to the more advanced. 
-- These functions are the core of the things that will be done by your units.


--## BASIC STATE ACTION -- these are basic command for the group.
-- revTODO the code below is not used; an error? -> Chromium: check this out -> nope will be used
local function groupGoQuiet(group)
    if group and group:isExist() == true then	
        local gController = group:getController()
        gController:setOption(AI.Option.Ground.id.ALARM_STATE, 1) -- green -- Ground or GROUND?
        gController:setOption(AI.Option.Ground.id.ROE, 3) -- return fire -- Ground or GROUND?
        AIEN.loggers.get(AIEN.Id):trace("AIEN.groupGoQuiet status quiet")
        
    end
end

-- revTODO the code below is not used; an error? -> Chromium: check this out -> nope will be used
local function groupGoActive(group)
    if group and group:isExist() == true then
        local gController = group:getController()
        gController:setOption(AI.Option.Ground.id.ALARM_STATE, 2) -- red -- Ground or GROUND?
        gController:setOption(AI.Option.Ground.id.ROE, 3) -- return fire -- Ground or GROUND?
        AIEN.loggers.get(AIEN.Id):trace("AIEN.groupGoActive status active and return fire")
        
    end
end

local function groupGoShoot(group)
    if group and group:isExist() == true then		
        local gController = group:getController()
        gController:setOption(AI.Option.Ground.id.ALARM_STATE, 2) -- red -- Ground or GROUND?
        gController:setOption(AI.Option.Ground.id.ROE, 2) -- open fire -- Ground or GROUND?
        AIEN.loggers.get(AIEN.Id):trace("AIEN.groupGoShoot status fire at will")
        
    end
end

local function groupAllowDisperse(group)
    if group and group:isExist() == true then
        local gController = group:getController()
        if gController then
            gController:setOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, AIEN.config.disperseActionTime) -- Ground or GROUND?
            AIEN.loggers.get(AIEN.Id):trace("AIEN.groupAllowDisperse will allow dispersal")
            
        else
            AIEN.loggers.get(AIEN.Id):info("AIEN.groupAllowDisperse, missing controller for: %s", group and group:getName())
        end	
    else
        AIEN.loggers.get(AIEN.Id):info("AIEN.groupAllowDisperse, missing group")
    end
end

local function groupPreventDisperse(group)
    if group and group:isExist() == true then
        local gController = group:getController()
        if gController then
            gController:setOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, false) -- Ground or GROUND?
            AIEN.loggers.get(AIEN.Id):trace("AIEN.groupPreventDisperse will prevent dispersal")
            
        else
            AIEN.loggers.get(AIEN.Id):info("AIEN.groupPreventDisperse, missing controller for: %s", group and group:getName())
        end
    else
        AIEN.loggers.get(AIEN.Id):info("AIEN.groupPreventDisperse, missing group")
    end
end

local function groupSuppress(group) -- quite important: provide random "suppression" effect by enabling the ROE "hold fire" for a limited amount of time that will depend from group skill
    if group and group:isExist() == true then
        local c = group:getController()
        if c then
			local s = getGroupSkillNum(group)
			local st = getReactionTime(s)*2
			
            c:setOption(AI.Option.Ground.id.ROE, 4)
            AIEN.loggers.get(AIEN.Id):trace("AIEN.groupSuppress group has been suppressed %s", group and group:getName())
            
            local back = function()
                c:setOption(AI.Option.Ground.id.ROE, 2)
            end
            if back then
                timer.scheduleFunction(back, {}, timer.getTime() + st)
            end
        end
    end
end 


--## MISSION ACTION -- these are more advanced command for groups
local function groupfireAtPoint(var)
    local group = var[1] -- groupTableCheck(var[1])
    AIEN.loggers.get(AIEN.Id):trace("groupfireAtPoint group check")
    
    if group and group:isExist() then
        AIEN.loggers.get(AIEN.Id):trace("groupfireAtPoint group name: %s", group and group:getName())
        
        local gController = group:getController()
        local vec3 = vec3Check(var[2])
        local qty = var[3]
        local desc = var[4]
        local radi = var[5]

        if gController and vec3 then
            AIEN.loggers.get(AIEN.Id):trace("groupfireAtPoint controller and vec3 identified")
            
            local expd = true
            
            if not var[3] then
                expd = false
                qty = nil
            end

            if not radi then
                radi = 50
            end

            local _tgtVec2 =  { x = vec3.x  , y = vec3.z} 
            local _task = { 
                id = 'FireAtPoint', 
                params = { 
                point = _tgtVec2,
                radius = 50,
                expendQty = qty,
                expendQtyEnabled = expd,
                alt_type = 1,
                }
            } 

            AIEN.loggers.get(AIEN.Id):trace("groupfireAtPoint variables set")
            

            gController:pushTask(_task)
            AIEN.loggers.get(AIEN.Id):trace("groupfireAtPoint fire mission planned")
            
            
            -- message feedback
            if AIEN.config.message_feed == true then

                local lat, lon = coord.LOtoLL(vec3)
                local MGRS = coord.LLtoMGRS(coord.LOtoLL(vec3))
                if lat and lon then

                    local LL_string = tostringLL(lat, lon, 0, true)
                    local MGRS_string = tostringMGRS(MGRS ,4)

                    local txt = ""
                    txt = txt .. "C2, " .. tostring(group:getName()) .. ", request fire mission, fire for Effect. coordinates:"
                    txt = txt .. "\n" .. tostring(MGRS_string) .. "\n" .. tostring(LL_string)
                    if desc and type(desc) == "string" then
                        txt = txt .. "\n" .. desc
                    end
                    if expd and qty and type(qty) == "number" then
                        txt = txt .. "\n" .. tostring(qty) .. " rounds"
                    end                    
                    txt = txt .. "\n" .. "Cleared for fire when ready"
                    
                    local vars = {"text", txt, 20, nil, nil, nil, group:getCoalition()}

                    multyTypeMessage(vars)

                end
            end

            -- mark on map for coalition
            if AIEN.config.mark_on_f10_map == true then

                local lat, lon = coord.LOtoLL(vec3)
                local MGRS = coord.LLtoMGRS(coord.LOtoLL(vec3))
                if lat and lon then

                    local LL_string = tostringLL(lat, lon, 0, true)
                    local MGRS_string = tostringMGRS(MGRS ,4)
                    local txt = ""
                    txt = txt .. tostring(group:getName())
                    txt = txt .. "\n" .. "Fire mission, coordinates:"
                    txt = txt .. "\n" .. tostring(MGRS_string) .. "\n" .. tostring(LL_string)

                    markIdStart = markIdStart + 1
                    trigger.action.markToCoalition(markIdStart, txt, vec3, group:getCoalition(), false, false)

                end
            end


        else
            AIEN.loggers.get(AIEN.Id):info("groupfireAtPoint, missing controller or for: %s", group and group:getName())
        end	
    else
        AIEN.loggers.get(AIEN.Id):info("groupfireAtPoint, missing group")
    end
end


--## ROUTE AND PATHFINDING

local function checkValidTerrainSurface(vec3)
    if vec3 then
        if type(vec3) == 'table' then -- assuming name
            if vec3.x and vec3.y and vec3.z then
                local l = land.getSurfaceType({x = vec3.x, y = vec3.z})
                if l then
                    if l == 1 or l == 4 or l == 5 then
                        return true, l
                    else
                        return false, l
                    end
                else
                    AIEN.loggers.get(AIEN.Id):info("checkValidDestination: l not identified!")
                    return false
                end
            else
                AIEN.loggers.get(AIEN.Id):info("checkValidDestination: wrong vector format")
                return false
            end
        else
            AIEN.loggers.get(AIEN.Id):info("checkValidDestination: wrong variable")
            return false
        end
    else
        AIEN.loggers.get(AIEN.Id):info("checkValidDestination: missing variable")
        return false
    end
end

local function getMEroute(group) -- basically a copy of getGroupRoute
    -- refactor to search by groupId and allow groupId and groupName as inputs
	local gpId = nil
    if group and group:isExist() == true then
        gpId = group:getID()
	end
	
	if gpId then
		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_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_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
											if group_data.route and group_data.route.points and #group_data.route.points > 0 then
												local points = {}

												for point_num, point in pairs(group_data.route.points) do
													local routeData = {}
													routeData.name = point.name
													if not point.point then
														routeData.x = point.x
														routeData.y = point.y
													else
														routeData.point = point.point	--it's possible that the ME could move to the point = Vec2 notation.
													end
													routeData.form = point.action
													routeData.speed = point.speed
													routeData.alt = point.alt
													routeData.alt_type = point.alt_type
													routeData.airdromeId = point.airdromeId
													routeData.helipadId = point.helipadId
													routeData.type = point.type
													routeData.action = point.action
													routeData.task = point.task
													points[point_num] = routeData
												end

												return points
											end
											return
										end	--if group_data and group_data.name and group_data.name == 'groupname'
									end --for group_num, group_data in pairs(obj_type_data.group) do
								end --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
							end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then
						end --for obj_type_name, obj_type_data in pairs(cntry_data) do
					end --for cntry_id, cntry_data in pairs(coa_data.country) do
				end --if coa_data.country then --there is a country table
			end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then
		end --for coa_name, coa_data in pairs(mission.coalition) do
	else
		return nil
	end
end

local function groupRoadOnly(group)
    if group and group:isExist() == true  then
        local units = group:getUnits()
        for _, uData in pairs(units) do
            if uData:hasAttribute("Trucks") or uData:hasAttribute("Cars") or uData:hasAttribute("Unarmed vehicles") then
                AIEN.loggers.get(AIEN.Id):trace("AIEN.groupRoadOnly found at least one road only unit!")
                
                return true
            end
        end
    end
    AIEN.loggers.get(AIEN.Id):trace("AIEN.groupRoadOnly no road only unit found, or no group")
    
    
    return false
end

local function goRoute(group, path)
    if group and path and group:isExist() == true then
        local misTask = {
            id = 'Mission',
            params = {
                route = {
                    points = deepCopy(path),
                },
            },
        }

        local groupCon = group:getController()
        if groupCon then
            groupCon:setTask(misTask)
            return true
        end
        return false
    end
end

local function moveToPoint(group, Vec3destination, destRadius, destInnerRadius, reqUseRoad, formation, haltContact, issuedByClient, clientCoa, groupSpeed) -- move the group to a point or, if the point is missing, to a random position at about 2 km
    
    AIEN.loggers.get(AIEN.Id):trace("moveToPoint Vec3destination x = %s", Vec3destination.x)
    AIEN.loggers.get(AIEN.Id):trace("moveToPoint Vec3destination y = %s", Vec3destination.y)
    AIEN.loggers.get(AIEN.Id):trace("moveToPoint Vec3destination z = %s", Vec3destination.z)
    if Vec3destination then
        local vt, vv = checkValidTerrainSurface(Vec3destination) 
        if vt == false then
            local newX, newZ = land.getClosestPointOnRoads('roads', Vec3destination.x, Vec3destination.z)
            local newY = land.getHeight({x = newX, y = newZ})
            Vec3destination = {x = newX, y = newY, z = newZ}
            AIEN.loggers.get(AIEN.Id):trace("moveToPoint Vec3destination corrected for land, was type %s", vv)
        else
            AIEN.loggers.get(AIEN.Id):trace("moveToPoint Vec3destination is identified as land, type %s", vv)
        end
    end
    
    if group and group:isExist() == true then

        local unit1 = group:getUnit(1)
        if unit1 then
            local curPoint = unit1:getPosition().p
            local point = Vec3destination --required
            local dist = getDist(point,curPoint)
            
            -- start answer
            local msg = ""
            if clientCoa and issuedByClient then
                msg = msg .. tostring(tostring(group:getName()))
            end

            if clientCoa and issuedByClient then
                local latitude, longitude, elev = coord.LOtoLL(point)
                local LL_string = tostringLL(latitude, longitude, 0, true)
                msg = msg .. " move to " .. tostring(LL_string) .. "\n"
            end

            -- checking and define road routing requests
            local useRoads = false
            if issuedByClient == true then
                if not reqUseRoad or reqUseRoad == false then
                    if env.mission.weather.clouds.iprecptns > 0 then
                        useRoads= true
                        if clientCoa then
                            msg = msg .. "unable to move on open ground due to weather, will use road" .. "\n"
                        end                
                    elseif groupRoadOnly(group) == true then
                        useRoads= true
                        if clientCoa then
                            msg = msg .. "unable to move on open ground due to vehicle spec, will use roads" .. "\n"
                        end
                    elseif dist > 30000 then
                        useRoads = true
                        if clientCoa then
                            msg = msg .. "unable to move in open ground due to distance, will use road instead" .. "\n"
                        end
                    else
                        useRoads = false
                        if clientCoa then
                            msg = msg .. "will move on open ground" .. "\n"
                        end
                    end
                else
                    useRoads = true
                    if clientCoa then
                        msg = msg .. "using roads " .. "\n"
                    end
                    -- check if possible and the relative answer
                end
            else
                if not reqUseRoad or reqUseRoad == false then
                    if env.mission.weather.clouds.iprecptns > 0 then
                        useRoads= true            
                    elseif groupRoadOnly(group) == true then
                        useRoads= true
                    elseif dist > 5000 then
                        useRoads = true
                    else
                        useRoads = false
                    end
                else
                    useRoads = true
                end                
            end        

            local rndCoord = nil
            if point == nil then
                point = getRandTerrainPointInCircle(group:getPosition().p, AIEN.config.rndFleeDistance*1.3, AIEN.config.rndFleeDistance*0.9)
                rndCoord = point
            end
        
            if point then	

                local radius = destRadius or 10
                local innerRadius = destInnerRadius or 1		
                
                -- define formation
                local form = formation or 'Offroad'
                if issuedByClient == true and clientCoa and formation then
                    if useRoads == false then
                        msg = msg .. "will deploin in " .. tostring(form) .. " formation " .. "\n"
                    else
                        msg = msg .. "when in open ground, will deploin in " .. tostring(form) .. " formation " .. "\n"
                    end
                end  

                -- define heading
                local heading = math.random()*2*math.pi
                if heading >= 2*math.pi then
                    heading = heading - 2*math.pi
                end

                -- define speed
                local speed = groupSpeed
                if not speed then
                    if useRoads == false then
                        speed = AIEN.config.outRoadSpeed
                    else
                        speed = AIEN.config.inRoadSpeed
                    end
                end

                if issuedByClient == true and clientCoa then
                    msg = msg .. "moving at " .. tostring(math.floor(speed/3.6*10)/10) .. " kmh \n"
                end                

                local path = {}
                if not rndCoord then
                    rndCoord = getRandTerrainPointInCircle(point, radius, innerRadius)
                end
                
                if rndCoord then

                    local offset = {}
                    local posStart = getLeadPos(group)
                    if posStart then
                        offset.x = round(math.sin(heading - (math.pi/2)) * 50 + rndCoord.x, 3)
                        offset.z = round(math.cos(heading + (math.pi/2)) * 50 + rndCoord.y, 3)
                        path[#path + 1] = buildWP(posStart, form, speed)


                        if useRoads == true and ((point.x - posStart.x)^2 + (point.z - posStart.z)^2)^0.5 > radius * 1.3 then
                            path[#path + 1] = buildWP({x = posStart.x + 11, z = posStart.z + 11}, 'off_road', AIEN.config.outRoadSpeed)
                            path[#path + 1] = buildWP(posStart, 'on_road', speed)
                            path[#path + 1] = buildWP(offset, 'on_road', speed)
                        else
                            path[#path + 1] = buildWP({x = posStart.x + 25, z = posStart.z + 25}, form, speed)
                        end

                        path[#path + 1] = buildWP(offset, form, speed)
                        path[#path + 1] = buildWP(rndCoord, form, speed)

                        goRoute(group, path)

                        if issuedByClient == true and clientCoa then
                            trigger.action.outTextForCoalition(clientCoa, msg, 30)
                            AIEN.loggers.get(AIEN.Id):trace("AIEN.moveToPoint msg %s", msg)
                            
                        end                     

                        return
                    end
                else
                    AIEN.loggers.get(AIEN.Id):info("moveToPoint failed, no valid coord available")
                end
            else
                AIEN.loggers.get(AIEN.Id):info("moveToPoint failed, no valid destination available")
            end
        else
            AIEN.loggers.get(AIEN.Id):info("moveToPoint failed, unit1 not available")
        end
    end
end


--###### COUNTER BATTERY FIRE ######################################################################

local function counterBattery(hitPos, tgtPos, coa) -- this function emulates counter battery fire
    -- this function is not about simulating the counter battery fire process, which involves projectiles radar detection,
    -- balistic calculations and then defining a shooter position. Instead, for performance purposes, the process is "hinted" using the following method, and start only if the shooter is an "indirect fire" attributes units
    -- this way:
    -- * first, since we don't want to do calc, we took the shooter current position when the hit event occours, to use it later if allowed, called tgtPos   
    -- * second, same reason, we pre-check if a free arty is available in range for fire on shooter position.
    -- * if arty is ok, and given the hit position, we look for the presence of a suitable radar ("SAM SR", "SAM TR", "EWR" since DCS world doesn't have the right kind of unit) within 50km 
    -- * if it's there, since we don't want to do calc much, we simply apply some random math formula that depends on distance as a probabilty of trajectory calc IRL and, also, the accuracy
    
    -- * if the random pass, the tgt is the passed for arty fire after a random timing that is counterBatteryPlanDelay+-35%.
    if hitPos and tgtPos and coa then
        if type(hitPos) == "table" and type(tgtPos) == "table" then
            if hitPos.x and hitPos.z and tgtPos.x and tgtPos.z then

                local arty = nil
                for _, og in pairs(groundgroupsDb) do
                    if og.coa == coa and og.tasked == false then
                        if og.class == "ARTY" or og.class == "MLRS" then --  or og.class == "MLRS" -- not considering MLRS as they're intended for more area or tactical fire
                            if og.group and og.group:isExist() == true then
                                local d = getDist(og.sa.pos, tgtPos)
                                if d < og.threat*0.9 then
                                    og.tasked = true
                                    og.taskTime = timer.getTime()
                                    og.firePoint = tgtPos
                                    
                                    AIEN.loggers.get(AIEN.Id):trace("counterBattery artillery potentially available")
                                    
                                    arty = og.group
                                    break
                                end
                            end
                        end
                    end
                end

                if arty then

                    -- check for near radar within 50 km, if there, return closer distance
                    local closestRange = AIEN.config.counterBatteryRadarRange
                    local _volume = {
                        id = world.VolumeType.SPHERE,
                        params = {
                            point = hitPos,
                            radius = AIEN.config.counterBatteryRadarRange,
                        },
                    }

                    local _search = function(_obj)
                        pcall(function()
                            if _obj ~= nil and Object.getCategory(_obj) == 1 and _obj:isExist() and _obj:getCoalition() == coa then
                                if _obj:hasAttribute("SAM SR") or _obj:hasAttribute("SAM TR") or _obj:hasAttribute("EWR") then
                                    local d = getDist(_obj:getPoint(), hitPos)
                                    if d < closestRange then
                                        closestRange = d
                                    end
                                end
                            end
                        end)
                    end
                    world.searchObjects(Object.Category.UNIT, _volume, _search)

                    if closestRange < AIEN.config.counterBatteryRadarRange then
                        local f = math.floor((1-(closestRange/AIEN.config.counterBatteryRadarRange)^2)*100)
                        local r = aie_random(1,100)
                        if f > r then
                            local a = math.floor( ((closestRange/AIEN.config.counterBatteryRadarRange)^1.5)*300)
                            local fpos = getRandTerrainPointInCircle(tgtPos, a, 1)
                            if fpos then
                                local t = aie_random(math.floor(AIEN.config.counterBatteryPlanDelay*0.65), math.floor(AIEN.config.counterBatteryPlanDelay*1.35))
                                
                                if AIEN.config.message_feed == true then

                                    local lat, lon = coord.LOtoLL(hitPos)
                                    local MGRS = coord.LLtoMGRS(coord.LOtoLL(hitPos))
                                    if lat and lon then
                    
                                        local LL_string = tostringLL(lat, lon, 0, true)
                                        local MGRS_string = tostringMGRS(MGRS ,4)
                    
                                        local txt = ""
                                        txt = txt .. "C2, " .. tostring(arty:getName()) .. ", identified enemy artillery fire. coordinates:"
                                        txt = txt .. "\n" .. tostring(MGRS_string) .. "\n" .. tostring(LL_string)                  
                                        txt = txt .. "\n" .. "Trying to evaluate enemy position. Please wait"
                                        
                                        local vars = {"text", txt, 20, nil, nil, nil, coa}
                    
                                        multyTypeMessage(vars)
                    
                                    end
                                end

                                local func = function()
                                    groupfireAtPoint({arty, fpos, 20, "Counter battery fire"})
                                end
                                timer.scheduleFunction(func, nil, timer.getTime() + t)

                            else
                                AIEN.loggers.get(AIEN.Id):trace("counterBattery failed fpos calculation")
                                
                                return false
                            end

                        else
                            AIEN.loggers.get(AIEN.Id):trace("counterBattery f=%s, r=%s failed", f, r)
                            
                            return false
                        end

                    end

                else
                    AIEN.loggers.get(AIEN.Id):trace("counterBattery artillery not available")
                    
                    return false
                end
            else
                AIEN.loggers.get(AIEN.Id):trace("counterBattery variable x and z missing")
                
                return false
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("counterBattery variables wrong format")
            
            return false
        end

    else
        AIEN.loggers.get(AIEN.Id):trace("ac_fireMissionOnShooter return false due to missing variable")
        
        return false
    end        


end



--###### DISMOUNT FUNCTIONS ########################################################################

-- This part of the code, basically modified from CTLD ones, handle the automatic troop dismount-remount behaviour.
-- Those functions are maybe the most "delicate" of the code, so think about them at least three times before touching ;)

local function createUnit(_x, _y, _angle, _n, _t)

    local id = aie_random(1,4) -- had to do this cause random is not defined once in game
    local sk = nil
    if id == 1 then
        sk = "Average"
    elseif id == 2 then
        sk = "Good"
    elseif id == 3 then
        sk = "High"
    elseif id == 4 then
        sk = "Excellent"
    end

    local _newUnit = {
        ["y"] = _y,
        ["type"] = _t,
        ["name"] = _n,
        ["heading"] = _angle,
        ["playerCanDrive"] = true,
        ["skill"] = sk,
        ["x"] = _x,
    }

    return _newUnit
end

local function findNearestEnemy(_side, _point, _searchDistance, _reposition)

    local repoOffset
    if not _reposition then
        repoOffset = 3
    else
        repoOffset = AIEN.config.droppedReposition
    end

    local mindistance = _searchDistance
    local enemyPos = nil
    local volS = {
    id = world.VolumeType.SPHERE,
    params = {
        point = _point,
        radius = _searchDistance
        }
    }
    
    local ifFound = function(foundItem, val)
        local itemPos = foundItem:getPosition().p
        local itemCoa = foundItem:getCoalition()
        if itemPos and itemCoa and itemCoa ~= _side and itemCoa ~= 0 then
            local dist = getDist(itemPos, _point)
            if dist < mindistance then
                mindistance = dist
                enemyPos = foundItem:getPosition().p
            end
        end
    end
    world.searchObjects(Object.Category.UNIT, volS, ifFound)    

    if enemyPos ~= nil then
        local _x = enemyPos.x + aie_random(1, 20) - aie_random(1, 20)
        local _z = enemyPos.z + aie_random(1, 20) - aie_random(1, 20)
        local _y = enemyPos.y + aie_random(1, 20) - aie_random(1, 20)
        return { x = _x, y =_y, z = _z}

    else
        local _x = _point.x + aie_random(1, repoOffset) - aie_random(1, repoOffset)
        local _z = _point.z + aie_random(1, repoOffset) - aie_random(1, repoOffset)
        local _y = _point.y + aie_random(1, repoOffset) - aie_random(1, repoOffset)
        return { x = _x, y =_y, z = _z}
    end    

end

local function getAliveGroup(_group)
    if _group and _group:isExist() == true and #_group:getUnits() > 0 then
        return _group
    end
    return nil
end

local function orderInfantryToMoveToPoint(_group, _destination)

    local _start = getLeadPos(_group)
    local _path = {}

    local routing = 'Off Road'
    local volS = {
        id = world.VolumeType.SPHERE,
        params = {
            point = _start,
            radius = AIEN.config.infantrySearchDist,
        }
    }
    local _count = 0
    local _search = function(_obj)
        pcall(function()

            -- filters to avoid objects that are not exactly valuable
            local _d = _obj:getDesc()
            if _d then
                if _d.attributes and _d.attributes.Buildings then -- must be building
                    _count = _count + 1
                end
            end
        end)
        return true
    end
    world.searchObjects(Object.Category.SCENERY, volS, _search)     
    
    AIEN.loggers.get(AIEN.Id):trace("AIEN.orderInfantryToMoveToPoint buildings _count = %s", _count)
    

    if _count > 5 then
        routing = 'on_road'
        AIEN.loggers.get(AIEN.Id):trace("AIEN.orderInfantryToMoveToPoint buildings identified, moving on road")
        
    end

    local _dTbl
    if _destination then
        local _x = _destination.x + aie_random(1, 5) - aie_random(1, 5)
        local _z = _destination.z + aie_random(1, 5) - aie_random(1, 5)
        local _y = _destination.y + aie_random(1, 5) - aie_random(1, 5)
        _dTbl = { x = _x, y =_y, z = _z}        
    end


    table.insert(_path, ground_buildWP(_start, routing, AIEN.config.infantrySpeed))
    table.insert(_path, ground_buildWP(_dTbl, routing, AIEN.config.infantrySpeed))
    if routing == 'on_road' then
        table.insert(_path, ground_buildWP(_dTbl, 'Off Road', 5))
    end

    local _mission = {
        id = 'Mission',
        params = {
            route = {
                points =_path
            },
        },
    }

    timer.scheduleFunction(function(_arg)
        local _grp = getAliveGroup(_arg[1])

        if _grp ~= nil then
            local _controller = _grp:getController();
            Controller.setOption(_controller, AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO)
            Controller.setOption(_controller, AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE)
            _controller:setTask(_arg[2])
        end
    end
        , {_group, _mission}, timer.getTime() + 2)

end

local function defineTroopsNumber(unit)
    if unit and unit:isExist() then
        local num = 0
        for dId, dNum in pairs(dismountCarriers) do
            if unit:hasAttribute(dId) then
                if num < dNum then
                    num = dNum
                end
            end
        end
        if num and num > 0 then
            return num
        end
    end
    return 0
end

local function deployTroops(unit, exactPos)
	
    local _point = unit:getPoint()
    local _onboard = mountedDb[unit:getID()]
    local _coa = unit:getCoalition()

    local function deploy(team, num)
        if team then
            local isMortar = false
            local _groupName = unit:getName() .. "_dismounted_" .. tostring(num)
            local _group = {
                ["visible"] = false,
                ["hidden"] = false,
                ["units"] = {},
                ["name"] = _groupName,
                ["task"] = {},
            }

            local _pos = _point
            for _i, _soldier in ipairs(team) do
                if contains(_soldier, "mortar") then
                    isMortar = true
                end            

                local _angle = math.pi * 2 * (_i - 1) / #team
                local _xOffset = math.cos(_angle) * 15 + num
                local _yOffset = math.sin(_angle) * 15 + num
                local _name = _groupName .. "_".. tostring(_i)

                _group.units[_i] = createUnit(_pos.x + _xOffset, _pos.z + _yOffset, _angle, _name, _soldier)
            end


            _group.category = Group.Category.GROUND;
            _group.country = unit:getCountry();

            local _spawnedGroup = Group.getByName(dynAdd(_group).name)

            if _spawnedGroup then
                if exactPos then
                    if isMortar == false then
                        orderInfantryToMoveToPoint(_spawnedGroup, exactPos)
                    end
                else
                    local _enemyPos = findNearestEnemy(_coa, _point, AIEN.config.infantrySearchDist)

                    if _enemyPos and isMortar == false then
                        orderInfantryToMoveToPoint(_spawnedGroup, _enemyPos)
                    end

                    mountedDb[unit:getID()] = nil
                    AIEN.loggers.get(AIEN.Id):trace("AIEN.deployTroops units deployed for unit %s", unit and unit:getName())
                    
                end
                return _spawnedGroup
            end
        end
    end

    if _onboard then
        for _g, _team in ipairs(_onboard) do
            deploy(_team, _g)
        end

        infcarrierDb[unit:getID()] = defineTroopsNumber(unit)
    end



end

local function extractTroops(unit)

    if unit and unit:isExist() then
        if unit:hasAttribute("IFV") or unit:hasAttribute("APC") or unit:hasAttribute("Trucks") then
            local people = infcarrierDb[unit:getID()]
            if not people then
                infcarrierDb[unit:getID()] = defineTroopsNumber(unit)
            end
            local uCoa = unit:getCoalition()
            if people and people > 0 then

                local foundAnything = true    
                local done = {}

                local function loadTeam()
                    
                    local mindistance = AIEN.config.infantryExtractDist

                    local volS = {
                    id = world.VolumeType.SPHERE,
                    params = {
                        point = unit:getPoint(),
                        radius = AIEN.config.infantryExtractDist
                        }
                    }

                    local nearestU = nil
                    local ifFound = function(foundItem, val)
                        if foundItem:getCoalition() == uCoa then
                            local fgId = foundItem:getGroup():getID()
                            if not done[fgId] then
                                if foundItem:hasAttribute("Infantry") then
                                    local posu = foundItem:getPoint()
                                    local dist = getDist(posu, unit:getPoint())
                                    if dist < mindistance then
                                        mindistance = dist
                                        nearestU = foundItem
                                    end
                                end
                            end
                        end
                    end
                    world.searchObjects(Object.Category.UNIT, volS, ifFound)

                    local typ = nil
                    local gtbl = nil
                    if nearestU then
                        local foundg = nearestU:getGroup()
                        if foundg then
                            if not done[foundg:getID()] then
                                local units = foundg:getUnits()
                                if units and #units > 0 then
                                    local tot = 0
                                    local inf = 0
                                    for _, u in pairs(units) do
                                        if u:hasAttribute("Infantry") then
                                            inf = inf + 1
                                            if not typ then
                                                typ = {}
                                            end
                                            typ[#typ+1] = u:getTypeName()
                                        end
                                        tot = tot + 1
                                    end

                                    if inf == tot and inf <= people then
                                        people = people - inf
                                        gtbl = foundg
                                    end
                                end    
                            end
                        end    
                    end

                    if typ and gtbl then
                        infcarrierDb[unit:getID()] = people
                        local loadedGroups = mountedDb[unit:getID()] or {}
                        loadedGroups[#loadedGroups+1] = typ
                        mountedDb[unit:getID()] = loadedGroups
                        AIEN.loggers.get(AIEN.Id):trace("AIEN.groupExtractTroop unit %s, extracted %s, people: %s", unit and unit:getName(), gtbl and gtbl:getName(), people)
                        
                        done[gtbl:getID()] = true
                        gtbl:destroy()
                    else
                        AIEN.loggers.get(AIEN.Id):trace("AIEN.groupExtractTroop extraction found anything" )
                        
                        foundAnything = false
                    end
                end

                while people >= 4 and foundAnything == true do
                    AIEN.loggers.get(AIEN.Id):trace("AIEN.groupExtractTroop unit %s, launching loadTeam, people %s", unit and unit:getName(), people)
                    
                    loadTeam()
                end

                if AIEN.config.AIEN_debugProcessDetail and AIEN_io and AIEN_lfs then
                    dumpTableAIEN("infcarrierDb.lua", infcarrierDb, "int")
                    dumpTableAIEN("mountedDb.lua", mountedDb, "int")
                end

            end
        end
    end

end

local function groupExtractTroop(group)

    if group and group:isExist() == true and #group:getUnits() > 0 then
        local units = group:getUnits()
        for _, uData in pairs(units) do
            if mountedDb[uData:getID()] == nil then
                AIEN.loggers.get(AIEN.Id):trace("AIEN.groupExtractTroop units extracting troops %s", uData and uData:getName())
                
                extractTroops(uData)
            end
        end
    end
    return nil
end

local function groupCarryInfantry(group) -- needed?
    if group and group:isExist() == true and #group:getUnits() > 0 then
        local units = group:getUnits()
        local isCarrying = false
        for _, uData in pairs(units) do
            if mountedDb[uData:getID()] then
                isCarrying = true
                break
            end
        end
        return isCarrying 
    end
end

local function mountTeam(unit)

	local uName = unit:getName()
	local volS = {
		id = world.VolumeType.SPHERE,
		params = {
			point = unit:getPoint(),
			radius = AIEN.config.infantrySearchDist
		}
	}

	local commandIssued = {}
	local groupMoving = 0
	local ifFound = function(foundItem, val)
		if contains(foundItem:getName(), uName) then
			AIEN.loggers.get(AIEN.Id):trace("groupMountTeam, %s recognized for %s", foundItem and foundItem:getName(), uName)
			
			local foundg = foundItem:getGroup()
			if not commandIssued[foundg:getID()] then
				if foundg and foundg:isExist() == true and #foundg:getUnits() > 0 then
					orderInfantryToMoveToPoint(foundg, unit:getPoint())
					commandIssued[foundg:getID()] = true
					groupMoving = groupMoving + 1
				end
			end
		end
	end
	world.searchObjects(Object.Category.UNIT, volS, ifFound)

	AIEN.loggers.get(AIEN.Id):trace("groupMountTeam, %s groups have been ordered to move nearby %s", groupMoving, uName)
	

	if groupMoving > 0 then
		timer.scheduleFunction(extractTroops, unit, timer.getTime() + 600)
	end


end

local function groupMountTeam(group) 
    -- this differs substantially from "Extract": basically it's calling the deployed teams to go back to its original vehicle
    -- the original vehicle is defined in a very "stupid" way: by searching unit name in the others groups nearby, which should be there
    -- in the dismounted group name. It's a very "hardcoded" convetion, I know, but still less complicated than many other solutions
    -- and does not require to track troops in another separated db (which already are too many to me)
    -- timing of 7 mins (420 s) seems reasonable to me for the regrouping of the dismounted troops, given the 2 km range.

    if group and group:isExist() == true and #group:getUnits() > 0 then
        for _, uData in pairs(group:getUnits()) do 
			mountTeam(uData)
        end
    end
end

local function groupDeployTroop(group, nocomeback, exactPos)
    if group and group:isExist() == true and group:getUnits() and #group:getUnits() > 0 then
        local units = group:getUnits()
        for _, uData in pairs(units) do

            local id = uData:getID()
            if id then
                if mountedDb[uData:getID()] then
                    deployTroops(uData, exactPos)

                    if not nocomeback then
                        timer.scheduleFunction(groupMountTeam, group, timer.getTime() + AIEN.config.remountTime)
                    end

                    return true
                end
            end
        end
    end
    return nil
end

local function groupCheckForManpad(group)
	if group and group:isExist() then
		local unitsWithTroops = getTroops(group)
		if unitsWithTroops and next(unitsWithTroops) ~= nil then
            AIEN.loggers.get(AIEN.Id):trace("AIEN.groupCheckForManpad, unitsWithTroops available")
            
			local manpadTeams = {}
			for uId, uData in pairs(unitsWithTroops) do 
				for _, teams in pairs(uData.t) do
					for _, soldier in pairs(teams) do
						if contains(soldier, "manpad") then
                            AIEN.loggers.get(AIEN.Id):trace("AIEN.groupCheckForManpad, has manpads")
                            
							manpadTeams[uId] = uData.u
						end
					end
				end
			end

            return manpadTeams
			
		end
	end
    return nil
end

local function groupDeployManpad(group) -- this won't trigger the deploy of any kind of troops, but only for the manpad team (if there)
	if group and group:isExist() then
		local manpadTeams = groupCheckForManpad(group)
        AIEN.loggers.get(AIEN.Id):trace("AIEN.groupDeployManpad, manpadTeams: %s", manpadTeams)
        
		if manpadTeams and next(manpadTeams) ~= nil then 
            AIEN.loggers.get(AIEN.Id):trace("AIEN.groupDeployManpad, confirmed deployable manpads team")
            
			for _, manpads in pairs(manpadTeams) do
				deployTroops(manpads)
                timer.scheduleFunction(groupMountTeam, group, timer.getTime() + AIEN.config.remountTime)			
			end
		end
	end
end

-- ## externally access command, by script

function AIEN_groupDeploy(gName, noremount) -- this one is global, to provide any user to make a group manually dismount via script or trigger action (do script) if remountVar is true, the dismounted group will go back to its vehicle after about 10 mins.
    if gName and type(gName) == "string" then
        local g = Group.getByName(gName)
        if g then
            groupDeployTroop(g, noremount)
        end
    end
end



--###### MISSION REACTIONS #########################################################################

--[[ Reactions is probably the most important behaviour change you will notice using this scripts. Reactions are (currently) triggered only by an hit event on one of the group unit. 
    Obvioulsy optimizable, the code structure basically works this way:
    1. when the hit event happen, some info are gathered in the event function event_hit that will launch executeActions
    2. the function executeActions basically will "try" to execute each of the below actions, in a priority order defined in the event_hit
    3. before defining priorities, all these functions are "filtered" by group skills (less skilleg group won't have the most refined solutions) and prioritized by conditions and available informations
    4. the first function that return as a "success" is then executed, and the behaviour take place.

    Side note: suppression, dismount (of all or only manpads) effect are NOT dependand to the scoring model and will take place in parallel.
--]]--


local function ac_accelerate(group, ownPos, tgtPos, resume, sa, skill) -- self-explanatory
    -- doesn't stop a moving group, it simply set its speed as fast as possible. If the group is stationary, it does nothing
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_accelerate launched", group and group:getName())
    
    
    if group then
        local s, ms = getGroupSpeed(group)
        if s and s > 0 then
            local c = group:getController()
            if c then
                c:setSpeed(30, true) -- 30 m/s = 108 km/h
                return true
            else
                AIEN.loggers.get(AIEN.Id):trace("ac_accelerate controller not found")
                
                return false
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("ac_accelerate failed to get speed, returning true assuming stationary")
            
            return true
        end
    end
    return false
end

local function ac_disperse(group, ownPos, tgtPos, resume, sa, skill) -- basically simply allow for dispersion
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_disperse launched, move randomly", group and group:getName())
    
    
    if group then
        groupAllowDisperse(group)
        return true
    else
        return false
    end
end

local function ac_panic(group, ownPos, tgtPos, resume, sa, skill) -- this will make the group to run away randomly.. that mean sometimes even toward the enemy.
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_panic launched, move randomly", group and group:getName())
    
    if group and group:isExist() and ownPos then

        if AIEN.config.dismount == true then
            groupDeployTroop(group, false)
        end

        local funcDoAction = function()
            if group:isExist() then
                local np = nil
                local maxTries = 1000
                while not np do
                    AIEN.loggers.get(AIEN.Id):trace("ac_panic creating point...")
                    
                    maxTries = maxTries - 1
                    if maxTries < 0 then
                        break
                    end
                    np = getRandTerrainPointInCircle(ownPos, AIEN.config.repositionDistance*10, AIEN.config.repositionDistance*5, true)
                end
                
                moveToPoint(group, np, 50, 5)
            end 
        end

        local funcSetParameters = function()
            if group:isExist() then
                local c = group:getController()
                if c then
                    c:setSpeed(30, true) -- 30 m/s = 108 km/h
                end
            end
        end

        local delay = getReactionTime(skill)
        timer.scheduleFunction(funcDoAction, nil, timer.getTime() + delay)    
        timer.scheduleFunction(funcSetParameters, nil, timer.getTime() + delay + 5)  

        AIEN.loggers.get(AIEN.Id):trace("ac_panic group planned reaction")
        

        return true
    end
    return false
end

local function ac_dropSmoke(group, ownPos, tgtPos, resume, sa, skill) -- basically spawn smokes around the vehicle and move it for 20-30 meters, trying to hide from enemies
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_dropSmoke launched", group and group:getName())
    
    if group and group:isExist() and ownPos and sa then

        local units = group:getUnits()
        if units then
            -- check at least 50% units can use smoke
            local numTot = 0
            local numSmk = 0
            for _, iData in pairs(units) do
                numTot = numTot + 1
                if iData:hasAttribute("HeavyArmoredUnits") or iData:hasAttribute("IFV") then
                    numSmk = numSmk +1
                end
            end
            if numTot > 0 then
                if numSmk/numTot < 0.5 then
                    AIEN.loggers.get(AIEN.Id):trace("ac_dropSmoke dropped cause less than 50% units can do that")
                    
                    return false
                end
            end
        end

        local funcDoAction = function()
            
            if group:isExist() then

                if AIEN.config.smoke_source_num > 9 then
                    AIEN.config.smoke_source_num = 9
                elseif AIEN.config.smoke_source_num < 4 then
                    AIEN.config.smoke_source_num = 4
                end
                
                local units = group:getUnits()
                local smoked = false
                if units then

                    -- plan smoke
                    for _, uData in pairs(units) do

                        if uData:hasAttribute("HeavyArmoredUnits") or uData:hasAttribute("IFV") then

                            local uPos = uData:getPoint()

                            local points = genSmokePoints(uPos, aie_random(15, 30), AIEN.config.smoke_source_num)
                    
                            if points and #points > 0 then
                                
                                AIEN.loggers.get(AIEN.Id):trace("ac_dropSmoke points %s", #points)
                                

                                --phase 1 generate smoke
                                for _, pPos in pairs(points) do
                                    local f = function()
                                        trigger.action.smoke(pPos, 2)
                                    end
                                    timer.scheduleFunction(f, nil, timer.getTime() + aie_random(1, 5)) 
                                end
                    
                                --phase 2 move in a random point very near (20-30 mt)
                                smoked = true
                    
                            else
                                AIEN.loggers.get(AIEN.Id):trace("ac_dropSmoke unable to define smoke points")
                                
                                --return false
                            end   
                        end
                    end
                end
                
                if smoked == true then
                    moveToPoint(group, ownPos, 5, 14) 
                    AIEN.loggers.get(AIEN.Id):trace("ac_dropSmoke group planned reaction")
                    
                end
            end
        end

        local delay = getReactionTime(skill)
        timer.scheduleFunction(funcDoAction, nil, timer.getTime() + delay)    
        return true  
        
    else
        AIEN.loggers.get(AIEN.Id):trace("ac_dropSmoke missing variables")
        
        return false
    end
end

local function ac_withdraw(group, ownPos, tgtPos, resume, sa, skill) -- this will make the group to run away to the nearest allied ground group
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_withdraw launched, withdraw", group and group:getName())
    
    if group and ownPos then
        local bestPos = nil
        local maxDist = AIEN.config.withrawDist
        for _, og in pairs(groundgroupsDb) do
            if og.coa == group:getCoalition() then
                if og.n ~= group:getName() then
                    if og.group and og.group:isExist() == true then
                        local p     = og.sa.pos
                        --local td    = og.threat
                        if p then -- and td
                            -- within range
                            local d = getDist(p, ownPos)
                            AIEN.loggers.get(AIEN.Id):trace("ac_withdraw d %s", d)
                            
                            if d and d < maxDist and d > 2000 then
                                bestPos = p
                                maxDist = d
                                --break -- just the first one available
                            end
                        end
                    end
                end
            end
        end

        if bestPos then 
            local funcDoAction = function()
                if group:isExist() then
                    moveToPoint(group, bestPos, AIEN.config.repositionDistance*1.5, AIEN.config.repositionDistance*0.5, false) 
                end
            end
            AIEN.loggers.get(AIEN.Id):trace("ac_withdraw group planned reaction")
            
            local delay = getReactionTime(skill)
            timer.scheduleFunction(funcDoAction, nil, timer.getTime() + delay)      

            if resume then
                -- check if there's a route to be followed once action end
                if group:isExist() then
                    local destination = nil
                    local points = getMEroute(group)
                    if points and #points > 1 then
                        local last = #points
                        local data = points[last] 
                        if data and type(data) == "table" then
                            destination = { x = data.x, y = land.getHeight({x = data.x, y = data.y}), z = data.y}
                        end
                    end
                    if not destination then
                        destination = ownPos
                    end            
                    local funcresumeRoute = function()
                        if group:isExist() then
                            moveToPoint(group, destination, 200, 10)
                        end
                    end
                    AIEN.loggers.get(AIEN.Id):trace("ac_withdraw group planning coming back")
                    
                    timer.scheduleFunction(funcresumeRoute, nil, timer.getTime() + aie_random(600, 900))     
                end
            end

            return true
        else
            AIEN.loggers.get(AIEN.Id):trace("ac_withdraw return false due to missing widraw opportunities")
            
            return false
        end
    else
        AIEN.loggers.get(AIEN.Id):trace("ac_withdraw return false due to missing variable")
        
        return false
    end
end

local function ac_attack(group, ownPos, tgtPos, resume, sa, skill) -- this will make the group to run toward the shooting enemy and open fire
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_attack launched, move toward enemy", group and group:getName())
    
    if group and tgtPos then
        local funcDoAction = function()
            if group:isExist() then
                local speed = 10
                if AIEN.config.dismount == true then
                    local deployed = groupDeployTroop(group, false, tgtPos)
                    if deployed == true then
                        speed = 4
                    end
                end

                moveToPoint(group, tgtPos, 300, 500, false, "cone", nil, nil, nil, speed) 
            end
        end
        AIEN.loggers.get(AIEN.Id):trace("ac_attack group planned reaction")
        
        local delay = getReactionTime(skill)
        timer.scheduleFunction(funcDoAction, nil, timer.getTime() + delay)      
        groupGoShoot(group)

        if resume then
            -- check if there's a route to be followed once action end
            local destination = nil
            local points = getMEroute(group)
            if points and #points > 1 then
                local last = #points
                local data = points[last] 
                if data and type(data) == "table" then
                    destination = { x = data.x, y = land.getHeight({x = data.x, y = data.y}), z = data.y}
                end
            end
            if not destination then
                destination = ownPos
            end            
            local funcresumeRoute = function()
                if group:isExist() then
                    moveToPoint(group, destination, 200, 10, false)
                end
            end
            AIEN.loggers.get(AIEN.Id):trace("ac_attack group planning coming back")
            
            timer.scheduleFunction(funcresumeRoute, nil, timer.getTime() + aie_random(900, 1200))     
        end

        return true

    else
        AIEN.loggers.get(AIEN.Id):trace("ac_attack return false due to missing variable")
        
        return false
    end
end

local function ac_coverBuildings(group, ownPos, tgtPos, resume, sa, skill) -- this will make the group to run looking cover in a nearby urban area
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_coverBuildings launched", group and group:getName())
    
    if group and ownPos and sa then

		-- nearby building (within AIEN.config.proxyBuildingDistance)

        local pN1 = ownPos
        local pN2 = ownPos
        local pN3 = ownPos
        local pN4 = ownPos
        local pos1, pos2, pos3, pos4
        local gCoa = group:getCoalition()

        if pN1 and pN2 and pN3 and pN4 then
            pos1 = {x = pN1.x, y = pN1.y, z = pN1.z + AIEN.config.proxyBuildingDistance}
            pos2 = {x = pN2.x, y = pN2.y, z = pN2.z - AIEN.config.proxyBuildingDistance}
            pos3 = {x = pN3.x + AIEN.config.proxyBuildingDistance, y = pN3.y, z = pN3.z}
            pos4 = {x = pN4.x - AIEN.config.proxyBuildingDistance, y = pN4.y, z = pN4.z}

            local function countBld(p)
                local _volume = {
                    id = world.VolumeType.SPHERE,
                    params = {
                        point = p,
                        radius = AIEN.config.proxyBuildingDistance,
                    },
                }
                local count = 0
                local tblPos = {}
                local enemies = false
                
                local _searchB = function(_obj)
                    pcall(function()
                        if _obj ~= nil then
                            local o_desc = _obj:getDesc()
                            if o_desc then
                                if o_desc.attributes and o_desc.attributes.Buildings then
                                    count = count + 1
                                    tblPos[#tblPos+1] = _obj:getPoint()
                                end
                            end              
                        end
                    end)
                end

                local _searchU = function(_obj)
                    pcall(function()
                        if _obj ~= nil then
                            local o_coa = _obj:getCoalition()
                            AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings o_coa: %s", o_coa)
                            
                            if o_coa  then
                                if o_coa ~= gCoa then
                                    AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings enemies true! %s", enemies)
                                    
                                    enemies = true
                                end
                            end              
                        end
                    end)
                end                

                AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings enemies: %s", enemies)
                

                world.searchObjects(Object.Category.SCENERY, _volume, _searchB)	
                world.searchObjects(Object.Category.UNIT,    _volume, _searchU)

                if count > 3 and #tblPos > 3 and enemies == false then
                    AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings adding point, count: %s", count)
                    
                    local bestPos = avgVec3(tblPos)
                    return count, bestPos
                end
            end

            AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings starting c1p1")
            
            local _, p1 = countBld(pos1)
            AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings starting c2p2")
            
            local _, p2 = countBld(pos2)
            AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings starting c3p3")
            
            local _, p3 = countBld(pos3)
            AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings starting c4p4")
            
            local _, p4 = countBld(pos4)
            AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings done all P's")
            

            if p1 or p2 or p3 or p4 then -- at least one should exist
            local function findNearestPoint(p0, ...)
                    local points = {...}
                    local nearestPoint = nil
                    local minDist = nil
                
                    for _, point in ipairs(points) do
                        if point then
                            local dist = getDist(p0, point)
                            if not minDist or dist < minDist then
                                minDist = dist
                                nearestPoint = point
                            end
                        end
                    end
                
                    return nearestPoint
                end
                local dest = findNearestPoint(ownPos, p1, p2, p3, p4)
                
                if dest then
                    local funcDoAction = function()
                        if group:isExist() then
                            moveToPoint(group, dest, AIEN.config.repositionDistance, AIEN.config.repositionDistance*0.2) 
                        end
                    end
                    AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings group planned reaction")
                    
                    local delay = getReactionTime(skill)
                    timer.scheduleFunction(funcDoAction, nil, timer.getTime() + delay)    
                    
                    if resume then
                        -- check if there's a route to be followed once action end
                        local destination = nil
                        local points = getMEroute(group)
                        if points and #points > 1 then
                            local last = #points
                            local data = points[last] 
                            if data and type(data) == "table" then
                                destination = { x = data.x, y = land.getHeight({x = data.x, y = data.y}), z = data.y}
                            end
                        end
                        if not destination then
                            destination = ownPos
                        end            
                        local funcresumeRoute = function()
                            if group:isExist() then
                                AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings group planning coming back to original destination")
                                
                                moveToPoint(group, destination, 200, 10)
                            end
                        end
                        timer.scheduleFunction(funcresumeRoute, nil, timer.getTime() + aie_random(420, 900))     
                    end                    

                    return true
                else
                    AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings return false due to missing buildings area")
                    
                    return false
                end                    
            else
                AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings didn't found a suitable place")
                
                return false
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings return false due wrong math around the starting point")
            
            return false
        end
    else
        AIEN.loggers.get(AIEN.Id):trace("ac_coverBuildings return false due to missing variable")
        
        return false
    end
end

local function ac_groundSupport(group, ownPos, tgtPos, resume, sa, skill) -- this will make another ground group to come in support
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_groundSupport launched, move randomly", group and group:getName())
    
    if group and ownPos and sa then
        local bestVal = 0
        local bestTd  = 1000 
        local AllyGroup = nil
        for _, og in pairs(groundgroupsDb) do
            if og.coa == group:getCoalition() and og.n ~= group:getName() then
                if og.group and og.group:isExist() == true then
                    if supportGroundClasses[og.class] and supportGroundClasses[og.class] > bestVal then
                        local p     = og.sa.pos
                        local td    = og.threat
                        if p and td then
                            -- within range
                            local d = getDist(p, ownPos)
                            if d and d < AIEN.config.supportDistance and d > 4000 then
                                bestTd = td/2
                                bestVal = supportGroundClasses[og.class]
                                AllyGroup = og.group
                            end
                        end
                    end
                end
            end
        end

        if AllyGroup and bestTd then 
            local funcDoAction = function()
                if group:isExist() then
                    moveToPoint(AllyGroup, ownPos, bestTd*0.5, bestTd*0.3) 
                end
            end
            AIEN.loggers.get(AIEN.Id):trace("ac_groundSupport group planned reaction")
            
            local delay = getReactionTime(skill)
            timer.scheduleFunction(funcDoAction, nil, timer.getTime() + delay)      
 
            return true
        else
            AIEN.loggers.get(AIEN.Id):trace("ac_groundSupport return false due to missing widraw opportunities")
            
            return false
        end
    else
        AIEN.loggers.get(AIEN.Id):trace("ac_groundSupport return false due to missing variable")
        
        return false
    end
end

local function ac_coverADS(group, ownPos, tgtPos, resume, sa, skill) -- this will make the group to run into the effective range of an allied air defence group
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_coverADS launched", group and group:getName())
    
    if group and ownPos and sa then
        local bestPos = nil
        local bestVal = 0
        local bestTd  = 3000 
        for _, og in pairs(groundgroupsDb) do
            if og.coa == group:getCoalition()  and og.n ~= group:getName() then
                if og.group and og.group:isExist() == true then
                    if supportCounterAirClasses[og.class] and supportCounterAirClasses[og.class] > bestVal then
                        local p     = og.sa.pos
                        local td    = og.threat
                        if p and td then
                            -- within range
                            local d = getDist(p, ownPos)
                            if d and d < AIEN.config.supportDistance*1.5 and d > 2000 then
                                bestPos = p
                                bestTd = td
                                bestVal = supportCounterAirClasses[og.class]
                            end
                        end
                    end
                end
            end
        end

        if bestPos and bestTd then 
            local funcDoAction = function()
                if group:isExist() then
                    moveToPoint(group, bestPos, bestTd*0.3, bestTd*0.05) 
                end
            end
            AIEN.loggers.get(AIEN.Id):trace("ac_coverADS group planned reaction")
            
            local delay = getReactionTime(skill)
            timer.scheduleFunction(funcDoAction, nil, timer.getTime() + delay)      

            if resume then
                -- check if there's a route to be followed once action end
                local destination = nil
                local points = getMEroute(group)
                if points and #points > 1 then
                    local last = #points
                    local data = points[last] 
                    if data and type(data) == "table" then
                        destination = { x = data.x, y = land.getHeight({x = data.x, y = data.y}), z = data.y}
                    end
                end
                if not destination then
                    destination = ownPos
                end            
                local funcresumeRoute = function()
                    if group:isExist() then
                        moveToPoint(group, destination, 200, 10)
                    end
                end
                AIEN.loggers.get(AIEN.Id):trace("ac_coverADS group planning coming back")
                
                timer.scheduleFunction(funcresumeRoute, nil, timer.getTime() + aie_random(900, 1200))     
            end
 
            return true
        else
            AIEN.loggers.get(AIEN.Id):trace("ac_coverADS return false due to missing widraw opportunities")
            
            return false
        end
    else
        AIEN.loggers.get(AIEN.Id):trace("ac_coverADS return false due to missing variable")
        
        return false
    end
end

local function ac_fireMissionOnShooter(group, ownPos, tgtPos, resume, sa, skill) -- this will make an allied artillery in range to fire at enemy shooter position
    -- group is the group subject of the action
    -- pos is, when needed, the reference position for the actions, or own position
    -- resume is a boolean. If true, after some time the group will resume it's previous condition, else no.
    -- sa is the SA table passed from the group DB, which hold some useful information for addressing the action 
    AIEN.loggers.get(AIEN.Id):debug("%s - ac_fireMissionOnShooter launched, planning", group and group:getName())
    
    if tgtPos then
        for _, og in pairs(groundgroupsDb) do
            if og.coa == group:getCoalition() and og.tasked == false then
                if og.class == "ARTY" then --  or og.class == "MLRS" -- not considering MLRS as they're intended for more area or tactical fire
                    if og.group and og.group:isExist() == true then
                        local d = getDist(og.sa.pos, tgtPos)
                        if d < og.threat*0.8 then
                            og.tasked = true
                            og.taskTime = timer.getTime()
                            og.firePoint = tgtPos
                            groupfireAtPoint({og.group, tgtPos, 20, "Immediate suppression"})
                            AIEN.loggers.get(AIEN.Id):trace("ac_fireMissionOnShooter return true, planning the fire mission")
                            

                            return true
                       end
                    end
                end
            end
        end
        AIEN.loggers.get(AIEN.Id):trace("ac_fireMissionOnShooter return false being unable to plan the fire mission")
        
        return false
    else
        AIEN.loggers.get(AIEN.Id):trace("ac_fireMissionOnShooter return false due to missing variable")
        
        return false
    end
end

-- summary tablem holds the "scoring model points" for each condition. This can be seen as a decision matrix for evaluate best reaction available.
-- used for fast-filtering actions availability based on group leader skill. 
-- It basically is an array, where the actions are listed in order of complexity. 
-- This way, the skill could be converted into a number, and that number will became the maximum index available.
-- The higher the skill, the higher the index, the higher the actions that could be evaluated
local actionsDb = {
	[1] 	= { -- ac_accelerate
        ["name"] = "ac_accelerate",
        ["ac_function"] = ac_accelerate,
        ["message"] = "",
        ["resume"] = true,
        ["w_cat"] = { -- weapon category
            [0] = 0, -- shell
            [1] = 1, -- missile
            [2] = 1, -- rocket
            [3] = 0, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 1, -- airplane
            [1] = 0, -- helicopter
            [2] = 2, -- ground unit
            [3] = 1, -- ship
            [4] = 0, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 2, -- not an indirect fire unit
            [1] = 0, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 1, -- not so close
            [1] = 0, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 2, -- detailed shooter position not known
            [1] = 0, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 2,
            ["ATGM"] = 1,
            ["MLRS"] = 0,
            ["ARTY"] = 0,
            ["MISSILE"] = 0,
            ["MANPADS"] = 0,
            ["SHORAD"] = 3,
            ["AAA"] = 3,
            ["SAM"] = 5,
            ["IFV"] = 0,
            ["APC"] = 0,
            ["RECCE"] = 0,
            ["LOGI"] = 0,
            ["INF"] = 0,
            ["UNKN"] = 0,
        },  
        ["s_cls"] = { 
            ["MBT"] = 0.2,
            ["ATGM"] = 0.4,
            ["MLRS"] = 2.1,
            ["ARTY"] = 1.5,
            ["MISSILE"] = 3.1,
            ["MANPADS"] = 2,
            ["SHORAD"] = 2.2,
            ["AAA"] = 2.8,
            ["SAM"] = 2.3,
            ["IFV"] = 0.4,
            ["APC"] = 0.9,
            ["RECCE"] = 1.2,
            ["LOGI"] = 1.9,
            ["INF"] = 0.8,
            ["UNKN"] = 1,
            ["ARBN"] = 1.4,
        },         
    }, 
	[2] 	= { -- ac_panic
        ["name"] = "ac_panic",
        ["ac_function"] = ac_panic,
        ["message"] = "We're trying to escape fire%!",
        ["resume"] = true,
        ["w_cat"] = { -- weapon category
            [0] = 0.3, -- shell
            [1] = 2, -- missile
            [2] = 0.5, -- rocket
            [3] = 0, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 2, -- airplane
            [1] = 2.5, -- helicopter
            [2] = 1, -- ground unit
            [3] = 1.5, -- ship
            [4] = 0, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 0, -- not an indirect fire unit
            [1] = 0.5, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 0.5, -- not so close
            [1] = 0, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 2, -- detailed shooter position not known
            [1] = 0, -- detailed shooter position known
        },     
        ["o_cls"] = {
            ["MBT"] = 1.5,
            ["ATGM"] = 1.3,
            ["MLRS"] = 1,
            ["ARTY"] = 1,
            ["MISSILE"] = 2,
            ["MANPADS"] = 2,
            ["SHORAD"] = 0,
            ["AAA"] = 0,
            ["SAM"] = 0,
            ["IFV"] = 0.7,
            ["APC"] = 0.5,
            ["RECCE"] = 0,
            ["LOGI"] = 0.2,
            ["INF"] = 0,
            ["UNKN"] = 0.1,
        },  
        ["s_cls"] = { 
            ["MBT"] = 1.7,
            ["ATGM"] = 1.6,
            ["MLRS"] = 1.3,
            ["ARTY"] = 1,
            ["MISSILE"] = 0.5,
            ["MANPADS"] = 0,
            ["SHORAD"] = 0,
            ["AAA"] = 0,
            ["SAM"] = 0,
            ["IFV"] = 1.4,
            ["APC"] = 0.9,
            ["RECCE"] = 0.3,
            ["LOGI"] = 0.2,
            ["INF"] = 0.1,
            ["UNKN"] = 1,
            ["ARBN"] = 1.8,
        },            
    },     
	[3] 	= { -- ac_disperse
        ["name"] = "ac_disperse",
        ["ac_function"] = ac_disperse,
        ["message"] = "We're stuck here, we ask support if available",
        ["resume"] = true,
        ["w_cat"] = { -- weapon category
            [0] = 1, -- shell
            [1] = 2, -- missile
            [2] = 1, -- rocket
            [3] = 2, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 0, -- airplane
            [1] = 0, -- helicopter
            [2] = 2, -- ground unit
            [3] = 3, -- ship
            [4] = 0, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 0, -- not an indirect fire unit
            [1] = 2, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 1, -- not so close
            [1] = 0, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 0, -- detailed shooter position not known
            [1] = 0, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 1.5,
            ["ATGM"] = 1.3,
            ["MLRS"] = 1,
            ["ARTY"] = 1,
            ["MISSILE"] = 2,
            ["MANPADS"] = 2,
            ["SHORAD"] = 0,
            ["AAA"] = 0,
            ["SAM"] = 0,
            ["IFV"] = 0.7,
            ["APC"] = 0.5,
            ["RECCE"] = 0,
            ["LOGI"] = 0.2,
            ["INF"] = 0,
            ["UNKN"] = 0.1,
        }, 
        ["s_cls"] = { 
            ["MBT"] = 1,
            ["ATGM"] = 1.2,
            ["MLRS"] = 2.5,
            ["ARTY"] = 2.5,
            ["MISSILE"] = 2.8,
            ["MANPADS"] = 0.3,
            ["SHORAD"] = 1.5,
            ["AAA"] = 0.6,
            ["SAM"] = 0.6,
            ["IFV"] = 0.8,
            ["APC"] = 0.7,
            ["RECCE"] = 0.3,
            ["LOGI"] = 0.2,
            ["INF"] = 0.1,
            ["UNKN"] = 1,
            ["ARBN"] = 0.4,
        },          
    },     
	[4] 	= { -- ac_dropSmoke
        ["name"] = "ac_dropSmoke",
        ["ac_function"] = ac_dropSmoke,
        ["message"] = "Dropping smoke cover",
        ["resume"] = true,
        ["w_cat"] = { -- weapon category
            [0] = 0, -- shell
            [1] = 5, -- missile
            [2] = 1, -- rocket
            [3] = 2, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 3, -- airplane
            [1] = 5, -- helicopter
            [2] = 1, -- ground unit
            [3] = 0, -- ship
            [4] = 0, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 1, -- not an indirect fire unit
            [1] = 0, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 0, -- not so close
            [1] = 1, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 0, -- detailed shooter position not known
            [1] = 0, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 3,
            ["ATGM"] = 2,
            ["MLRS"] = 0,
            ["ARTY"] = 0,
            ["MISSILE"] = 0,
            ["MANPADS"] = 0,
            ["SHORAD"] = 0,
            ["AAA"] = 0,
            ["SAM"] = 0,
            ["IFV"] = 2,
            ["APC"] = 2.2,
            ["RECCE"] = 0,
            ["LOGI"] = 0,
            ["INF"] = 0,
            ["UNKN"] = 0,
        }, 
        ["s_cls"] = { 
            ["MBT"] = 2.1,
            ["ATGM"] = 1.8,
            ["MLRS"] = 0,
            ["ARTY"] = 0,
            ["MISSILE"] = 0,
            ["MANPADS"] = 0,
            ["SHORAD"] = 0,
            ["AAA"] = 0,
            ["SAM"] = 0,
            ["IFV"] = 2,
            ["APC"] = 2.3,
            ["RECCE"] = 0,
            ["LOGI"] = 0,
            ["INF"] = 0,
            ["UNKN"] = 0,
            ["ARBN"] = 3,
        },          
    },     
	[5] 	= { -- ac_withdraw
        ["name"] = "ac_withdraw",
        ["ac_function"] = ac_withdraw,
        ["message"] = "We're moving in safer area",
        ["resume"] = true,
        ["w_cat"] = { -- weapon category
            [0] = 3, -- shell
            [1] = 2, -- missile
            [2] = 1, -- rocket
            [3] = 1, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 0, -- airplane
            [1] = 0, -- helicopter
            [2] = 2, -- ground unit
            [3] = 2, -- ship
            [4] = 1, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 0, -- not an indirect fire unit
            [1] = 2, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 2, -- not so close
            [1] = 0, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 0, -- detailed shooter position not known
            [1] = 3, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 0,
            ["ATGM"] = 0,
            ["MLRS"] = 0,
            ["ARTY"] = 1,
            ["MISSILE"] = 1,
            ["MANPADS"] = 1,
            ["SHORAD"] = 2,
            ["AAA"] = 2,
            ["SAM"] = 0,
            ["IFV"] = 0,
            ["APC"] = 0,
            ["RECCE"] = 0,
            ["LOGI"] = 3,
            ["INF"] = 3,
            ["UNKN"] = 1,
        }, 
        ["s_cls"] = { 
            ["MBT"] = 0,
            ["ATGM"] = 1.8,
            ["MLRS"] = 0.6,
            ["ARTY"] = 0.4,
            ["MISSILE"] = 0.5,
            ["MANPADS"] = 0.1,
            ["SHORAD"] = 0.3,
            ["AAA"] = 0.2,
            ["SAM"] = 0.2,
            ["IFV"] = 1.5,
            ["APC"] = 0.7,
            ["RECCE"] = 0.3,
            ["LOGI"] = 0.2,
            ["INF"] = 0.1,
            ["UNKN"] = 1,
            ["ARBN"] = 0.7,
        },         
    },     
    [6] 	= { -- ac_attack
        ["name"] = "ac_attack",
        ["ac_function"] = ac_attack,
        ["message"] = "We're going to ambush the enemy",
        ["resume"] = true,
        ["w_cat"] = { -- weapon category
            [0] = 2, -- shell
            [1] = 0, -- missile
            [2] = 1, -- rocket
            [3] = 0, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 0, -- airplane
            [1] = 0, -- helicopter
            [2] = 2, -- ground unit
            [3] = 0, -- ship
            [4] = 2, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 2, -- not an indirect fire unit
            [1] = 0, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 1, -- not so close
            [1] = 2, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 2, -- detailed shooter position not known
            [1] = 0, -- detailed shooter position known
        },     
        ["o_cls"] = {
            ["MBT"] = 0,
            ["ATGM"] = 0,
            ["MLRS"] = 2,
            ["ARTY"] = 0,
            ["MISSILE"] = 0,
            ["MANPADS"] = 0,
            ["SHORAD"] = 2,
            ["AAA"] = 0,
            ["SAM"] = 0,
            ["IFV"] = 1,
            ["APC"] = 0,
            ["RECCE"] = 0,
            ["LOGI"] = 0,
            ["INF"] = 0,
            ["UNKN"] = 0,
        },  
        ["s_cls"] = { 
            ["MBT"] = 0,
            ["ATGM"] = 0,
            ["MLRS"] = 3,
            ["ARTY"] = 2.5,
            ["MISSILE"] = 3,
            ["MANPADS"] = 3,
            ["SHORAD"] = 1.3,
            ["AAA"] = 2.2,
            ["SAM"] = 2.2,
            ["IFV"] = 0.6,
            ["APC"] = 0.9,
            ["RECCE"] = 1.7,
            ["LOGI"] = 4,
            ["INF"] = 2.4,
            ["UNKN"] = 1,
            ["ARBN"] = 0,
        },          
    },    
    [7] 	= { -- ac_coverBuildings
        ["name"] = "ac_coverBuildings",
        ["ac_function"] = ac_coverBuildings,
        ["message"] = "We're moving nearby the closest urbanized area for concealment",
        ["resume"] = true,
        ["w_cat"] = { -- weapon category
            [0] = 2, -- shell
            [1] = 2, -- missile
            [2] = 2, -- rocket
            [3] = 2, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 2, -- airplane
            [1] = 2, -- helicopter
            [2] = 1, -- ground unit
            [3] = 2, -- ship
            [4] = 0, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 2, -- not an indirect fire unit
            [1] = 0, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 1, -- not so close
            [1] = 2, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 0, -- detailed shooter position not known
            [1] = 2, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 0,
            ["ATGM"] = 1,
            ["MLRS"] = 2,
            ["ARTY"] = 1,
            ["MISSILE"] = 0,
            ["MANPADS"] = 3,
            ["SHORAD"] = 3,
            ["AAA"] = 2,
            ["SAM"] = 0,
            ["IFV"] = 2,
            ["APC"] = 3,
            ["RECCE"] = 3,
            ["LOGI"] = 3,
            ["INF"] = 3,
            ["UNKN"] = 3,
        },  
        ["s_cls"] = { 
            ["MBT"] = 1.3,
            ["ATGM"] = 1.8,
            ["MLRS"] = 2,
            ["ARTY"] = 2,
            ["MISSILE"] = 0.5,
            ["MANPADS"] = 0.1,
            ["SHORAD"] = 0.3,
            ["AAA"] = 0.2,
            ["SAM"] = 0.2,
            ["IFV"] = 1.4,
            ["APC"] = 1,
            ["RECCE"] = 0.3,
            ["LOGI"] = 0.2,
            ["INF"] = 0.1,
            ["UNKN"] = 1,
            ["ARBN"] = 2,
        },         
    },
    [8] 	= { -- ac_groundSupport
        ["name"] = "ac_groundSupport",
        ["ac_function"] = ac_groundSupport,
        ["message"] = "We asked for ground support, they're on the way",
        ["resume"] = true,
        ["w_cat"] = { -- weapon category
            [0] = 0, -- shell
            [1] = 1, -- missile
            [2] = 0, -- rocket
            [3] = 0, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 0, -- airplane
            [1] = 0, -- helicopter
            [2] = 4, -- ground unit
            [3] = 0, -- ship
            [4] = 2, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 1, -- not an indirect fire unit
            [1] = 0, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 0, -- not so close
            [1] = 2, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 0, -- detailed shooter position not known
            [1] = 0, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 1,
            ["ATGM"] = 1,
            ["MLRS"] = 2,
            ["ARTY"] = 2,
            ["MISSILE"] = 3,
            ["MANPADS"] = 2,
            ["SHORAD"] = 2,
            ["AAA"] = 2,
            ["SAM"] = 2,
            ["IFV"] = 1,
            ["APC"] = 2,
            ["RECCE"] = 1,
            ["LOGI"] = 2,
            ["INF"] = 3,
            ["UNKN"] = 1,
        }, 
        ["s_cls"] = { 
            ["MBT"] = 1.8,
            ["ATGM"] = 1.4,
            ["MLRS"] = 3,
            ["ARTY"] = 2.5,
            ["MISSILE"] = 3,
            ["MANPADS"] = 3,
            ["SHORAD"] = 1.3,
            ["AAA"] = 1,
            ["SAM"] = 0.9,
            ["IFV"] = 2,
            ["APC"] = 2.2,
            ["RECCE"] = 1.7,
            ["LOGI"] = 1.1,
            ["INF"] = 0.4,
            ["UNKN"] = 1,
            ["ARBN"] = 0,
        },         
    },
    [9] 	= { -- ac_coverADS
        ["name"] = "ac_coverADS",
        ["ac_function"] = ac_coverADS,
        ["resume"] = false,
        ["message"] = "Attack comes from airborne asset, we are moving within the closest air defence covered area",
        ["w_cat"] = { -- weapon category
            [0] = 1, -- shell
            [1] = 3, -- missile
            [2] = 2, -- rocket
            [3] = 3, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 5, -- airplane
            [1] = 5, -- helicopter
            [2] = 0, -- ground unit
            [3] = 0, -- ship
            [4] = 0, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 2, -- not an indirect fire unit
            [1] = 2, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 2, -- not so close
            [1] = 2, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 0, -- detailed shooter position not known
            [1] = 0, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 3,
            ["ATGM"] = 2,
            ["MLRS"] = 3,
            ["ARTY"] = 3,
            ["MISSILE"] = 3,
            ["MANPADS"] = 1,
            ["SHORAD"] = 0,
            ["AAA"] = 1,
            ["SAM"] = 0,
            ["IFV"] = 3,
            ["APC"] = 3,
            ["RECCE"] = 3,
            ["LOGI"] = 3,
            ["INF"] = 3,
            ["UNKN"] = 3,
        },  
        ["s_cls"] = { 
            ["MBT"] = 1.8,
            ["ATGM"] = 1.4,
            ["MLRS"] = 3,
            ["ARTY"] = 2.5,
            ["MISSILE"] = 3,
            ["MANPADS"] = 3,
            ["SHORAD"] = 1.3,
            ["AAA"] = 1,
            ["SAM"] = 0.9,
            ["IFV"] = 2,
            ["APC"] = 2.2,
            ["RECCE"] = 1.7,
            ["LOGI"] = 1.1,
            ["INF"] = 0.4,
            ["UNKN"] = 1,
            ["ARBN"] = 5,
        },         
    },
    [10] 	= { -- ac_fireMissionOnShooter
        ["name"] = "ac_fireMissionOnShooter",
        ["ac_function"] = ac_fireMissionOnShooter,
        ["resume"] = true,
        ["message"] = "We got the enemy position and asked for indirect fire mission.",
        ["w_cat"] = { -- weapon category
            [0] = 3, -- shell
            [1] = 3, -- missile
            [2] = 3, -- rocket
            [3] = 3, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 0, -- airplane
            [1] = 3, -- helicopter
            [2] = 2, -- ground unit
            [3] = 3, -- ship
            [4] = 5, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 2, -- not an indirect fire unit
            [1] = 0, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 5, -- not so close
            [1] = 0, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 0, -- detailed shooter position not known
            [1] = 5, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 1,
            ["ATGM"] = 1,
            ["MLRS"] = 3,
            ["ARTY"] = 3,
            ["MISSILE"] = 1,
            ["MANPADS"] = 1,
            ["SHORAD"] = 1,
            ["AAA"] = 1,
            ["SAM"] = 1,
            ["IFV"] = 1,
            ["APC"] = 1,
            ["RECCE"] = 1,
            ["LOGI"] = 1,
            ["INF"] = 1,
            ["UNKN"] = 2,
            ["ARBN"] = 1,
        },  
        ["s_cls"] = { 
            ["MBT"] = 1.8,
            ["ATGM"] = 1.4,
            ["MLRS"] = 3,
            ["ARTY"] = 2.5,
            ["MISSILE"] = 3,
            ["MANPADS"] = 3,
            ["SHORAD"] = 1.3,
            ["AAA"] = 1,
            ["SAM"] = 0.9,
            ["IFV"] = 2,
            ["APC"] = 2.2,
            ["RECCE"] = 1.7,
            ["LOGI"] = 1.1,
            ["INF"] = 0.4,
            ["UNKN"] = 1,
            ["ARBN"] = 5,
        },          
    },
    -- revTODO interesting, does this mean that you planned the "call for air support" feature? -> Chromium: check this out -> yes, it will be added as a client request
    --[[
    [11] 	= {
        ["name"] = "ac_airSupport",
        ["ac_function"] = ac_airSupport,
        ["w_cat"] = { -- weapon category
            [0] = 2, -- shell
            [1] = 2, -- missile
            [2] = 2, -- rocket
            [3] = 2, -- bomb
        }, 				
        ["s_cat"] = { -- unit category
            [0] = 0, -- airplane
            [1] = 2, -- helicopter
            [2] = 4, -- ground unit
            [3] = 0, -- ship
            [4] = 3, -- structure
        }, 				
        ["s_indirect"] = { -- unit category
            [0] = 1, -- not an indirect fire unit
            [1] = 1, -- is an indirect fire unit
        }, 			
        ["s_close"] = { -- shooter is within wpn range
            [0] = 2, -- not so close
            [1] = 3, -- close
        }, 	     	      
        ["s_fireMis"] = { -- shooter position and speed
            [0] = 0, -- detailed shooter position not known
            [1] = 5, -- detailed shooter position known
        },     
        ["o_cls"] = { 
            ["MBT"] = 2,
            ["ATGM"] = 2,
            ["MLRS"] = 2,
            ["ARTY"] = 2,
            ["MISSILE"] = 2,
            ["MANPADS"] = 1,
            ["SHORAD"] = 2,
            ["AAA"] = 2,
            ["SAM"] = 2,
            ["IFV"] = 2,
            ["APC"] = 2,
            ["RECCE"] = 2,
            ["LOGI"] = 1,
            ["INF"] = 1,
            ["UNKN"] = 0,
        },  
        ["o_cls"] = { 
            ["MBT"] = 3,
            ["ATGM"] = 3,
            ["MLRS"] = 3,
            ["ARTY"] = 3,
            ["MISSILE"] = 3,
            ["MANPADS"] = 0.4,
            ["SHORAD"] = 0.2,
            ["AAA"] = 0.4,
            ["SAM"] = 0,
            ["IFV"] = 2.8,
            ["APC"] = 2,
            ["RECCE"] = 2,
            ["LOGI"] = 1,
            ["INF"] = 1,
            ["UNKN"] = 0,
            ["ARBN"] = 1.9,
        },          
    },
    --]]--
}

-- the functions that handles the reactions, using priorities
local function executeActions(gr, ownPos, tgtPos, actTbl, saTbl, skill)
    if gr and gr:isExist() and ownPos and tgtPos and actTbl and saTbl and skill then
        if actTbl and #actTbl>0 then
            for _, aData in pairs(actTbl) do 
                for _, dbActData in pairs(actionsDb) do
                    if aData.name == dbActData.name then
                        local f = dbActData.ac_function
                        if f then

                            trigger.action.groupContinueMoving(gr)
                            local success = f(gr, ownPos, tgtPos, dbActData.resume, saTbl, skill)
                            AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions, action success = %s", success)
                            
                            if success and success == true then
                                -- message feedback
                                if AIEN.config.message_feed == true then

                                    local lat, lon = coord.LOtoLL(ownPos)
                                    local MGRS = coord.LLtoMGRS(coord.LOtoLL(ownPos))
                                    if lat and lon then

                                        local LL_string = tostringLL(lat, lon, 0, true)
                                        local MGRS_string = tostringMGRS(MGRS ,4)


                                        local txt = ""
                                        local txt = txt .. "C2, " .. tostring(gr:getName()) .. ", report under attack. Coordinates: " .. tostring(LL_string) .. ", " .. tostring(MGRS_string) .. "." .. dbActData.message
                                        local vars = {"text", txt, 30, nil, nil, nil, gr:getCoalition()}

                                        multyTypeMessage(vars)

                                    end
                                end

                                return dbActData.name
                            end
                        end
                    end
                end
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions, actTbl missing or void")
            
            return false
        end
    else
        AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions error, missing one or more variables:")
        AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions error: %s", gr)
        AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions error: %s", ownPos)
        AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions error: %s", tgtPos)
        AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions error: %s", actTbl)
        AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions error: %s", saTbl)
        AIEN.loggers.get(AIEN.Id):trace("AIEN.executeActions error: %s", skill)
        return false
    end
end

-- this 'global' test function let you test a specific reaction of your choice, using the group name and the reaction name to execute. 
-- for those action where the shooter is required for evaluation, the test function will look for the nearest target within 20 km.
-- if data are not gathered, it will print an advice 

function AIEN_testActions(groupName, actionName)
    
    if groupName and type(groupName) == "string" and actionName and type(actionName) == "string" then
        --local gr = Group.getByName(groupName)

        -- get group info
        local gr 			= nil
        local saTbl 		= nil
        local ownPos 		= nil
        local skill 		= nil
		local coa 			= nil
        for _, gData in pairs(groundgroupsDb) do
            if groupName == gData.n then
				gr 		= gData.group
				saTbl 	= gData.sa
				ownPos	= gData.sa.pos
				skill	= gData.skill
				coa		= gData.coa
            end
        end

        -- get action info
		local actionFunc	= nil
		local actionResume	= nil
		local actionMess	= nil
        for _, aData in pairs(actionsDb) do
            if actionName == aData.name then
				actionFunc = aData.ac_function
				actionResume = aData.resume
				actionMess	 = aData.message
            end
        end

		-- filter conditions
		if gr and saTbl and ownPos and skill and coa and actionFunc then

			-- get nearest enemy
			local tgtPos = nil
			local maxDist = 20000
			local _volume = {
				id = world.VolumeType.SPHERE,
				params = {
					point = ownPos,
					radius = 20000,
				},
			}

			local _search = function(_obj)
				pcall(function()
					if _obj ~= nil and Object.getCategory(_obj) == 1 and _obj:isExist() and _obj:getCoalition() ~= coa then
						local _objPos = _obj:getPoint()
						if _objPos then
							local d = getDist(_objPos, ownPos)
							if d and d < maxDist then
								maxDist = d
								tgtPos = _objPos
							end
						end
					end
				end)
			end
			world.searchObjects(Object.Category.UNIT, _volume, _search)		
			
			-- tgtPos might be unnecessary, therefore I don't check it.
			local success = actionFunc(gr, ownPos, tgtPos, actionResume, saTbl, skill)
			AIEN.loggers.get(AIEN.Id):trace("AIEN.AIEN_testActions, result %s", success)
            
			if success and success == true then
				-- message feedback
				if AIEN.config.message_feed == true then

					local lat, lon = coord.LOtoLL(ownPos)
					if lat and lon then

						local LL_string = tostringLL(lat, lon, 0, true)

						local txt = ""
						local txt = txt .. "C2, " .. tostring(gr:getName()) .. ", report under attack. Coordinates: " .. tostring(LL_string) .. "." .. tostring(actionMess)
						local vars = {"text", txt, 20, nil, nil, nil, gr:getCoalition()}

						multyTypeMessage(vars)

					end
				end

				return actionName
			end
			
		end
		return nil

    end
end


--###### DB CONSTRUCTION & HANDLING ################################################################

--[[ DB structure
    each db element is added as this: [objectID] = {group = Group Object, class = Result of getGroupClass function, i = index in table}
    They're not array to speed up the object calls when needed, cause you can simply do a referencedDB[objectID] call w/o coding for table loop
    when a db is referred to a unit, to skip units loop in the group (i.e. droneunitDb), the "group" key is replaced by "unit"

    DBs are used mostly for FSM loops, that are needed to keep a low impact on the process (FSM 1st level will loop db's, while FSM 2nd level will loop each entry one every phaseCycleTimer timer (default 0.2 seconds)).
    Not all the DBs are used in loops, some are only event-related like the ones used for dismount options.

]]--

local function populate_Db() -- this one is launched once at mission start and collect everything relevant that is already there.

	-- only ground groups
	groundgroupsDb = {}
	for i = 0, 2 do
		for _, gp in pairs(coalition.getGroups(i,2)) do -- ground only
			if gp:isExist() then

                local c = getGroupClass(gp)
                local gpcoa = gp:getCoalition()
                -- classes reminder from getGroupClass:
                -- MBT
                -- ATGM
                -- IFV
                -- APC
                -- RECCE
                -- LOGI
                -- MLRS
                -- ARTY
                -- MISSILE
                -- MANPADS
                -- SHORAD
                -- AAA
                -- SAM
                -- INF
                -- UNKN
                
                local s = getGroupSkillNum(gp)
                AIEN.loggers.get(AIEN.Id):info("populate_Db: s %s", s)
                local det, thr = getRanges(gp)
                if c then
                    --local r = getMEroute(gp)
                    groundgroupsDb[gp:getID()] = {group = gp, class = c, n = gp:getName(), coa = gpcoa, detection = det, threat = thr, tasked = false, skill = s}  --, route = r
                    AIEN.loggers.get(AIEN.Id):info("populate_Db: adding to groundgroupsDb %s, class %s", gp and gp:getName(), c)
                else
                    AIEN.loggers.get(AIEN.Id):info("populate_Db: skipping group due to unable to identify class %s", gp and gp:getName())
                end

                -- dismount dbs
                if AIEN.config.dismount == true then
                    if gp:getUnits() and #gp:getUnits() > 0 then
                        for _, un in pairs(gp:getUnits()) do
                            if un:hasAttribute("IFV") or un:hasAttribute("APC") or un:hasAttribute("Trucks") then
                                -- define dismount capacity
                                local people = defineTroopsNumber(un)
                                infcarrierDb[un:getID()] = people

                                -- define pre-loaded groups!
                                local function loadTeam(unit)
                                    local refTbl = dismountTeamsWest
                                    if gpcoa == 1 then
                                        refTbl = dismountTeamsEast
                                    end


                                    local r = aie_random(1,100)
                                    --AIEN.loggers.get(AIEN.Id):info("populate_Db: random for %s: %s", unit and unit:getName(), r)
                                    local c = nil
                                    local i = nil
                                    local lim = 0
                                    for _, tData in pairs(refTbl) do
                                        if r > tData.p and tData.p > lim then
                                            c = tData.c
                                            i = tData.id
                                            lim = tData.p
                                            --AIEN.loggers.get(AIEN.Id):info("populate_Db: found %s", i)
                                        end
                                    end

                                    if c then
                                        local curMount = mountedDb[unit:getID()] or {}
                                        AIEN.loggers.get(AIEN.Id):info("populate_Db: adding %s to %s", i, unit and unit:getName())
                                        curMount[#curMount+1] = c
                                        mountedDb[unit:getID()] = curMount
                                        
                                        local freePlace = infcarrierDb[un:getID()]
                                        freePlace = freePlace - #c
                                        infcarrierDb[un:getID()] = freePlace
                                    end
                                end

                                local loadings = math.floor(people/4)
                                for i = 1, loadings do
                                    if infcarrierDb[un:getID()] >=4 then
                                        loadTeam(un)
                                    end
                                end

                            end
                        end
                    end
                end

                -- set prevent disperse
                groupPreventDisperse(gp)

			end
		end
	end
	
	-- only drone units
	droneunitDb = {}	
	for i = 0, 2 do
		for _, gp in pairs(coalition.getGroups(i,0)) do -- airplane only
			if gp:isExist() then
                local c = nil
                if gp:getUnits() then
                    for _, un in pairs(gp:getUnits()) do
                        if un:hasAttribute("UAVs") then -- drone only
                            c = "UAV"
                        end
                    end
                end
                if c then
                    AIEN.loggers.get(AIEN.Id):info("populate_Db: adding to droneunitDb %s", gp and gp:getName())
                    droneunitDb[gp:getID()] = {group = gp, class = c, n = gp:getName(), coa = gp:getCoalition()}
                end				
			end
		end
	end


    if AIEN.config.AIEN_debugProcessDetail and AIEN_io and AIEN_lfs then
        dumpTableAIEN("groundgroupsDb.lua", groundgroupsDb, "int")
        dumpTableAIEN("droneunitDb.lua", droneunitDb, "int")
        dumpTableAIEN("infcarrierDb.lua", infcarrierDb, "int")
        dumpTableAIEN("mountedDb.lua", mountedDb, "int")
    end
	
end


--###### FINITE STATE MACHINE LOOP #################################################################

--[[ FSM is the key element that allow this script to be as lightweight as possibile (for my low skills), cause basically make all the recurring functions to run each every "n" time instead of all-together at once every second. 
    There are 2 levels of FSM:
    - 1st level is the "bigger" one that is divided in phases: each phase update a DB table, plus a fourth one that handle the artillery groups fire missions.
    - 2nd level is the "group cycle", that handle each database entry update.

    Each time a 2nd level cycle is complete, the subsequent 1st level start and at the end it will simply re-start from the first. Check AIEN.performPhaseCycle() for 1st level cycle.

]]--

-- utils
local function createIterator(t)
    local keys = {}
    for key in pairs(t) do
        table.insert(keys, key)
    end
    table.sort(keys)
    return keys
end

local function getNextKey(keys, currentKey)
    for i, key in ipairs(keys) do
        if key == currentKey then
            return keys[i + 1]
        end
    end
    return nil
end

-- 2ND LEVEL CYCLE FUNCTIONS

-- SA update, PHASE "A"
local function update_GROUND()
    if PHASE == "A" then -- confirm correct PHASE of performPhaseCycle
        if groundgroupsDb and next(groundgroupsDb) ~= nil then -- check that table exist and that it's not void
            if not phase_index then -- escape condition from the 2nd loop!
                AIEN.changePhase()
                timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
                -- debug steps
                if AIEN.config.AIEN_debugProcessDetail and AIEN_io and AIEN_lfs then
                    dumpTableAIEN("groundgroupsDb.lua", groundgroupsDb, "int")
                end
                AIEN.loggers.get(AIEN.Id):trace("update_GROUND: phase A completed")
                

            else
                local gData = groundgroupsDb[phase_index]
                if gData then
                    local remove = false
                    if gData.group then
                        if gData.group and gData.group:isExist() == true and gData.group:getUnits() then

                            -- filter under attack, SA already gained and need to focus on reactions
                            if not underAttack[phase_index] then 

                                -- update/create sa
                                gData.sa = getSA(gData.group)

                                -- check tasked
                                if gData.tasked == true and gData.taskTime then
                                    if timer.getTime() - gData.taskTime >= AIEN.config.taskTimeout then
                                        AIEN.loggers.get(AIEN.Id):trace("update_GROUND, group name %s is still tasked. Removing it", gData.n)
                                        
                                        gData.tasked = false
                                        gData.taskTime = nil
                                    end
                                end
                            else
                                local t = timer.getTime() - underAttack[phase_index]
                                if t > AIEN.config.taskTimeout*2 then
                                    underAttack[phase_index] = nil
                                    AIEN.loggers.get(AIEN.Id):trace("update_GROUND, group name %s removed from the under attack table", gData.n)
                                    
                                else
                                    AIEN.loggers.get(AIEN.Id):trace("update_GROUND, group name %s is still under attack", gData.n)
                                    
                                end
                            end

                        else
                            AIEN.loggers.get(AIEN.Id):trace("update_GROUND, group name %s other variables does not exist, remove true", gData.n)
                            
                            remove = true
                        end
                    else
                        AIEN.loggers.get(AIEN.Id):trace("update_GROUND, group name %s gData.group does not exist, remove true", gData.n)
                        
                        remove = true
                    end

                    if remove == true then
                        AIEN.loggers.get(AIEN.Id):trace("update_GROUND, group name %s missing. Removing it", gData.n)
                        
                        groundgroupsDb[phase_index] = nil
                        phase_keys = createIterator(groundgroupsDb)                        
                    end

                end
                phase_index = getNextKey(phase_keys, phase_index)
                timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
            end
        else
            PHASE = "Initialization"
            AIEN.loggers.get(AIEN.Id):trace("update_GROUND, reinizializzazione dei DB, poiché groundgroupsDb sembra vuoto o inesistente!")
            
            timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
        end
    end
end

-- ISR update, PHASE "B"

local function update_ISR() -- basically clean old ISR data
    if PHASE == "B" then -- confirm correct PHASE of performPhaseCycle
        if intelDb and next(intelDb) ~= nil then -- check that table exist and that it's not void
            if not phase_index then -- escape condition from the 2nd loop!
                AIEN.changePhase()
                timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
                -- debug steps
                if AIEN.config.AIEN_debugProcessDetail and AIEN_io and AIEN_lfs then
                    dumpTableAIEN("intelDb.lua", intelDb, "int")
                end
                AIEN.loggers.get(AIEN.Id):trace("update_ISR: fase B completed")
                

            else
                local tData = intelDb[phase_index]
                if tData then
                    --local remove = false
                    if not tData.obj or tData.obj:isExist() == false then
                        AIEN.loggers.get(AIEN.Id):trace("update_ISR, target id %s missing. Removing it", phase_index)
                        
                        intelDb[phase_index] = nil
                        phase_keys = createIterator(intelDb) 
                    else
                        if tData.targeted then
                            if type(tData.targeted) == "number" then
                                if timer.getTime() - tData.targeted >= AIEN.config.targetedTimeout then
                                    AIEN.loggers.get(AIEN.Id):trace("update_ISR, target id %s is still targeted. Removing it", phase_index)
                                    
                                    intelDb[phase_index].targeted = nil
                                end
                            end
                        end
                    end
                end
                phase_index = getNextKey(phase_keys, phase_index)
                timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
            end
        else
            AIEN.changePhase()
            timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
            -- debug steps
            if AIEN.config.AIEN_debugProcessDetail and AIEN_io and AIEN_lfs then
                dumpTableAIEN("intelDb.lua", intelDb, "int")
            end
            AIEN.loggers.get(AIEN.Id):trace("update_ISR: fase B skipped")
            
        end
    end
end

-- DRONE update, PHASE "C"
local function update_DRONE()
    if PHASE == "C" then -- confirm correct PHASE of performPhaseCycle
        if droneunitDb and next(droneunitDb) ~= nil then -- check that table exist and that it's not void
            if not phase_index then -- escape condition from the 2nd loop!
                AIEN.changePhase()
                timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
                -- debug steps
                if AIEN.config.AIEN_debugProcessDetail and AIEN_io and AIEN_lfs then
                    dumpTableAIEN("droneunitDb.lua", droneunitDb, "int")
                end
                AIEN.loggers.get(AIEN.Id):trace("update_DRONE: fase B completata")
                

            else
                local dData = droneunitDb[phase_index]
                if dData then
                    local remove = false
                    if dData.group then
                        if dData.group and dData.group:isExist() == true then
                            -- update/create sa
                            AIEN.loggers.get(AIEN.Id):trace("update_DRONE, add SA %s", dData.n)
                            
                            dData.sa = getSA(dData.group)
                        else
                            remove = true
                        end
                    else
                        remove = true
                    end

                    if remove == true then
                        AIEN.loggers.get(AIEN.Id):trace("update_DRONE, group name %s missing. Removing it", dData.n)
                        
                        droneunitDb[phase_index] = nil
                        phase_keys = createIterator(droneunitDb)                        
                    end

                end
                phase_index = getNextKey(phase_keys, phase_index)
                timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("update_DRONE, no drone available!")
            
            AIEN.changePhase()
            timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
            AIEN.loggers.get(AIEN.Id):trace("update_DRONE: fase B completata")
            
        end
    end

end

-- ARTY update, PHASE "D"
local function update_ARTY()
    if PHASE == "D" then -- confirm correct PHASE of performPhaseCycle
        if groundgroupsDb and next(groundgroupsDb) ~= nil then -- check that table exist and that it's not void
            if not phase_index or AIEN.config.firemissions == false then -- escape condition from the 2nd loop!
                AIEN.changePhase()
                timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
                AIEN.loggers.get(AIEN.Id):trace("update_ARTY: phase D completed or skipped")
                
            else
                if not underAttack[phase_index] then 
                    local gData = groundgroupsDb[phase_index]
                    if gData then

                        local AI_consent = true
                        if gData.coa == 2 and AIEN.config.blueAI == false then
                            AI_consent = false
                        end
                        if gData.coa == 1 and AIEN.config.redAI == false then
                            AI_consent = false
                        end                
                        local remove = false
                        
                        if AI_consent == true and groupAllowedForAI(gData.group) == true then -- both coalition AI should be on and group exclusion tag shouldn't be there
                            if gData.group then
                                if gData.group and gData.group:isExist() == true and gData.sa then
                                    if not underAttack[phase_index] and gData.tasked == false then
                                        if gData.class == "MLRS" or gData.class == "ARTY" then
                                            if gData.threat then
                                                -- check ammo
                                                local ammoAvail = 0
                                                local units = gData.group:getUnits()
                                                for _, uData in pairs(units) do
                                                    local ammoTbl = uData:getAmmo()
                                                    if ammoTbl then
                                                        for aId, aData in pairs(ammoTbl) do 
                                                            if aId == 1 then
                                                                if aData.count > ammoAvail then
                                                                    ammoAvail = aData.count + ammoAvail
                                                                end
                                                            end
                                                        end
                                                    end
                                                end
                                                local roundsToFire = 0
                                                if ammoAvail > 30 then
                                                    roundsToFire = 30
                                                else
                                                    roundsToFire = ammoAvail
                                                end


                                                if roundsToFire > 0 then

                                                    -- check targets   
                                                    local firePoint = nil
                                                    local targetId = nil
                                                    local _volume = {
                                                        id = world.VolumeType.SPHERE,
                                                        params = {
                                                            point = gData.sa.pos,
                                                            radius = gData.threat*0.85,
                                                        },
                                                    }

                                                    local curPri = 0
                                                    local _search = function(_obj)
                                                        -- revTODO warning with "pcall", it's a costly feature -> Chromium: check this out  -> wanted to avoid risk of weirdness over DCS bugs
                                                        pcall(function()
                                                            if _obj ~= nil and Object.getCategory(_obj) == 1 and _obj:isExist() and _obj:getCoalition() ~= gData.coa then
                                                                local _obj_id = _obj:getID()
                                                                local report = intelDb[_obj_id]
                                                                if report and report.speed < 1 and report.targeted == nil then
                                                                    local lastContact = (timer.getTime() - report.record )
                                                                    if lastContact < AIEN.config.artyFireLastContactThereshold then
                                                                        local timeFactor = (AIEN.config.artyFireLastContactThereshold-lastContact)/AIEN.config.artyFireLastContactThereshold
                                                                        local pri = classPriority[report.cls]
                                                                        if not pri then
                                                                            pri = 0.5
                                                                        end
                                                                        pri = pri * timeFactor
                                                                        if pri > curPri then
                                                                            local go = getDangerClose(report.pos, gData.coa)
                                                                            if go == false then
                                                                                curPri = pri
                                                                                firePoint = report.pos
                                                                                targetId = report.cls
                                                                                report.targeted = timer.getTime()
                                                                            else
                                                                                AIEN.loggers.get(AIEN.Id):trace("update_ARTY, target skipped for danger close")
                                                                                
                                                                            end
                                                                        end
                                                                    end
                                                                end
                                                            end
                                                        end)
                                                    end
                                                    world.searchObjects(Object.Category.UNIT, _volume, _search)
                                                    
                                                    -- issuing mission
                                                    if firePoint then
                                                        AIEN.loggers.get(AIEN.Id):trace("update_ARTY, suitable target found for : %s: %s, will fire %s rounds", gData.n, targetId, roundsToFire)
                                                        
                                                        gData.tasked = true
                                                        gData.taskTime = timer.getTime()
                                                        gData.firePoint = firePoint
                                                        local description = nil
                                                        if targetId then
                                                            description = "Target is " .. tostring(targetId)
                                                        end

                                                        groupfireAtPoint({gData.group, firePoint, roundsToFire, description})
                                                    end
                                                end

                                            else
                                                AIEN.loggers.get(AIEN.Id):trace("update_ARTY, threat range not available")
                                                
                                            end
                                        end
                                    end
                                else
                                    remove = true
                                end
                            else
                                remove = true
                            end
                        end

                        if remove == true then
                            AIEN.loggers.get(AIEN.Id):trace("update_ARTY, group name %s missing. Removing it", gData.n)
                            
                            groundgroupsDb[phase_index] = nil
                            phase_keys = createIterator(groundgroupsDb)                        
                        end

                    end
                end
                phase_index = getNextKey(phase_keys, phase_index)
                timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
            end
        else
            PHASE = "Initialization"
            AIEN.loggers.get(AIEN.Id):trace("update_ARTY, reinizializzazione dei DB, poiché groundgroupsDb sembra vuoto o inesistente!")
            
            timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)
        end
    end
end

-- 1ST LEVEL CYCLE FUNCTIONS

function AIEN.changePhase()
    if PHASE == "Initialization" then -- udpate terrain data
        PHASE = "A"
        AIEN.loggers.get(AIEN.Id):trace("AIEN.changePhase, new PHASE: %s", PHASE)
        

    elseif PHASE == "A" then -- udpate terrain data
        PHASE = "B"
        phase_keys = nil
        phase_keys = createIterator(intelDb) -- focus phase_keys on groundgroupsDb
        phase_index = phase_keys[1]
        AIEN.loggers.get(AIEN.Id):trace("AIEN.changePhase, new PHASE: %s", PHASE)
        

    elseif PHASE == "B" then
        PHASE = "C"
        phase_keys = nil
        phase_keys = createIterator(droneunitDb) -- focus phase_keys on groundgroupsDb
        phase_index = phase_keys[1]
        AIEN.loggers.get(AIEN.Id):trace("AIEN.changePhase, new PHASE: %s", PHASE)
        

    elseif PHASE == "C" then
        PHASE = "D"
        phase_keys = nil
        phase_keys = createIterator(groundgroupsDb) -- focus phase_keys on groundgroupsDb
        phase_index = phase_keys[1]
        AIEN.loggers.get(AIEN.Id):trace("AIEN.changePhase, new PHASE: %s", PHASE)
        
    
    elseif PHASE == "D" then
        PHASE = "Z" -- LAST STEP
        AIEN.changePhase()

    elseif PHASE == "Z" then
        PHASE = "A"
        phase_keys = nil
        phase_keys = createIterator(groundgroupsDb) -- focus phase_keys on groundgroupsDb
        phase_index = phase_keys[1]
        AIEN.loggers.get(AIEN.Id):trace("AIEN.changePhase, new PHASE: %s", PHASE)
        


    end
end

function AIEN.performPhaseCycle()

    if PHASE == "Initialization" then
        populate_Db()
        phase_keys = nil
        phase_keys = createIterator(groundgroupsDb) -- focus phase_keys on groundgroupsDb
        phase_index = phase_keys[1]        
        AIEN.changePhase()
        timer.scheduleFunction(AIEN.performPhaseCycle, {}, timer.getTime() + phaseCycleTimer)

    elseif PHASE == "A" then
        update_GROUND()

    elseif PHASE == "B" then
        update_ISR()

    elseif PHASE == "C" then
        update_DRONE()

    elseif PHASE == "D" then
        update_ARTY()

    end
end

--###### EVENT HANDLER FUNCTIONS ###################################################################

-- I believe this is self explanatory. Still, the event_hit function holds a lot on reactions and decision making. I'm sorry if it appear confuse, but currently it fits my condition XD.

local function event_hit(unit, shooter, weapon) -- this functions run eacht time a unit gets an hit. Unit only, no statics. That's basically the core for reactions

    if AIEN.config.reactions == true then

        local unitCat = pcallGetCategory(unit)
        local shooterCat = pcallGetCategory(shooter)

        if unitCat == 1 and shooterCat == 1 then

            local vehicle       = unit:hasAttribute("Vehicles")
            local infantry      = unit:hasAttribute("Infantry")
            local armoured      = unit:hasAttribute("Armored vehicles")
            local position      = unit:getPoint()

            local ground_unit   = nil
            if vehicle == true then --  or infantry == true
                ground_unit = true
            end

            if ground_unit then
                local group     = unit:getGroup()
            
                if group and group:isExist() and groupAllowedForAI(group) == true then -- filtering both for existance and for exclusion tag being not there
                    
                    local AI_consent = true

                    -- filter for coalition
                    if group:getCoalition() == 2 and AIEN.config.blueAI == false then
                        AI_consent = false
                    end
                    if group:getCoalition() == 1 and AIEN.config.redAI == false then
                        AI_consent = false
                    end  

                    AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, coalition check return AI_consent %s", AI_consent)
                    

                    if AI_consent == true then
                        if AIEN.config.AIEN_zoneFilter and AIEN.config.AIEN_zoneFilter ~= "" then
                            AI_consent = groupInZone(group)
                            AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group zone check return AI_consent %s", AI_consent)
                            
                        end
                    end
                    
                    if AI_consent == true then

                        trigger.action.groupStopMoving(group)

                        -- suppression part
                        if AIEN.config.suppression == true and armoured then
                            local suppressEffects = false
                            if shooter:hasAttribute("Air") or shooter:hasAttribute("Ships") or shooter:hasAttribute("Indirect fire") then
                                suppressEffects = true
                            end
                            if suppressEffects == true then
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group is suppressed: %s", group and group:getName())
                                
                                groupSuppress(group)
                            end
                        end

                        -- dismount part
                        if AIEN.config.dismount == true then
                            if not underAttack[group:getID()] then
                                if shooter:hasAttribute("Air") then
                                    timer.scheduleFunction(groupDeployManpad, group, timer.getTime() + aie_random(8, 15))
                                    AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, shooter is airborne, manpad dismount happens")
                                    
                                elseif shooter:hasAttribute("Ground Units") then
                                    local d = AIEN.config.infantrySearchDist
                                    local dist = getDist(shooter:getPoint(), position)
                                    if dist < d then
                                        timer.scheduleFunction(groupDeployTroop, group, timer.getTime() + aie_random(8, 15))
                                        AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, distance is close, infantry dismount happens")
                                        
                                    end                            
                                end
                            end
                        end

                        -- reaction part
                        local choosenAct = nil
                        if not underAttack[group:getID()] then -- if a group has already been identified as "attacked", it won't repeat all the whole process every time or it could became a freaking mess in case of multiple hits
                            
                            AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s", group and group:getName())
                            

                            -- retrieve SA & Controller
                            local con = group:getController()
                            local db_group = groundgroupsDb[group:getID()]
                            if con and db_group and db_group.sa then

                                -- define if the attacker is known and and with what details
                                local s_detected, s_visible, s_lastTime, s_type, s_distance, s_lastPos, s_lastVel, s_cat, w_cat, o_cat, s_indirect, s_close, s_fireMis, a_pos, o_cls, s_cls, o_pos
                                w_cat       = 0
                                s_cat       = nil
                                s_indirect  = 0
                                s_close     = 0
                                s_fireMis   = 0
                                o_cls       = db_group.sa.cls
                                s_cls       = "UNKN"
                                o_pos       = unit:getPoint()
                                
                                -- define weapon info, used to identify arty attack
                                if weapon and weapon:isExist() then
                                    w_cat = weapon:getDesc().category
                                    --[[-- 
                                        Weapon.Category
                                        SHELL     0
                                        MISSILE   1
                                        ROCKET    2
                                        BOMB      3
                                    --]]--
                                    AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, w_cat: %s", group and group:getName(), w_cat)
                                    
                                end

                                if shooter and con then
                                    AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, shooter known")
                                    

                                    -- revise a_pos
                                    a_pos = shooter:getPoint()

                                    -- parameters identification
                                    s_detected , s_visible , s_lastTime , s_type , s_distance , s_lastPos , s_lastVel = con:isTargetDetected(shooter)

                                    o_cat, s_cat = shooter:getCategory()
                                    s_cls = getUnitClass(shooter)


                                    --[[ o_cat: 
                                        UNIT    1
                                        WEAPON  2
                                        STATIC  3
                                        BASE    4
                                        SCENERY 5
                                        Cargo   6
                                    --]]--                                        
                                    
                                    --[[ s_cat: 
                                        AIRPLANE      = 0,
                                        HELICOPTER    = 1,
                                        GROUND_UNIT   = 2,
                                        SHIP          = 3,
                                        STRUCTURE     = 4
                                    --]]--
                                    
                                    -- shooter is indirect fire
                                    if shooter:hasAttribute("Indirect fire") then
                                        s_indirect = 1
                                    end

                                    -- shooter is close
                                    if a_pos and position then
                                        local d = db_group.sa.rng or 1500
                                        local dist = getDist(a_pos, position)
                                        if dist < d then
                                            s_close = 1
                                        end
                                    end      

                                    -- position and speed
                                    --[[ removed cause of issues with isTargetDetected function returned variables
                                    if a_pos and s_lastVel and s_lastTime then
                                        if timer.getTime() - s_lastTime < 30 and s_lastVel < 1 then
                                            s_fireMis = 1
                                        end
                                    end
                                    --]]--
                                    local rnd = math.random(1,100)
                                    if rnd > 70 then
                                        s_fireMis = 1
                                    end


                                else -- try to address things when the shooter is unknown, based on weapon and effects
                                    AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, shooter unknown")
                                    

                                    if w_cat then
                                        --[[-- 
                                            Weapon.Category
                                            SHELL     0
                                            MISSILE   1
                                            ROCKET    2
                                            BOMB      3
                                        --]]--
                                        if w_cat == 0 or w_cat == 2 then -- shooter is unknown, and the weapon is a shell or a rocket: artillery is possibile
                                            s_indirect = 1
                                            AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, shooter unknown but arty fire possibile")
                                            
                                        elseif w_cat == 1 or w_cat == 3 then -- shooter is unknown, and the weapon is a missile or a bomb: airborne threat is possibile
                                            s_cls = "ARBN"
                                            AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, shooter unknown but airborne fire possibile")
                                            
                                        end

                                    end
                                end

                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, w_cat: %s", group and group:getName(), w_cat)
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, s_cat: %s", group and group:getName(), s_cat)
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, s_indirect: %s", group and group:getName(), s_indirect)
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, s_close: %s", group and group:getName(), s_close)
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, s_fireMis: %s", group and group:getName(), s_fireMis)
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, o_cls: %s", group and group:getName(), o_cls)
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, s_cls: %s", group and group:getName(), s_cls)
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, group %s, a_pos: %s", group and group:getName(), a_pos)

                                local av_ac = deepCopy(actionsDb) 

                                -- remove not doable actions due to missin informations
                                if s_fireMis < 1 or AI_consent == false then -- shooter position is not sufficiently recent
                                    AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, s_fireMis is 0, won't be able to call fire support")
                                    
                                    av_ac[9] = nil
                                end 
                                if not a_pos or not s_detected then -- enemy position unknown
                                    AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, enemy not detected, won't be able to move toward the enemy")
                                    
                                    av_ac[5] = nil
                                end
                                if s_cat == 0 or s_cat == 1 or s_cls == "ARBN" then -- shooter is airborne
                                    AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, shooter is airborne, removing less sensed decision")
                                    
                                    av_ac[5] = nil -- remove attack
                                    av_ac[7] = nil -- remove ground support
                                    av_ac[3] = nil -- remove disperse
                                end
                                if s_cls ~= "ARBN" then -- shooter is not airborne
                                    av_ac[8] = nil -- remove counter ADS
                                end
                                if not unit:hasAttribute("Armored vehicles") then
                                    av_ac[4] = nil -- remove drop smoke
                                end
                            
                                -- filter available actions by skill
                                local filter = db_group.skill
                                if AIEN.config.skill_action_const == false then
                                    filter = filter * 2
                                end

                                for aSk, action in pairs(av_ac) do
                                    if aSk > filter then
                                        av_ac[aSk] = nil
                                    end
                                end
                                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, available actions %s", #av_ac)
                                
                                
                                -- calculate points for each remaining actions
                                local bc_ac = {}
                                for _, aData in pairs(av_ac) do
                                    local points = 0
                                    local px1 = aData["w_cat"][w_cat] or 0
                                    local px2 = aData["s_cat"][s_cat] or 0
                                    local px3 = aData["s_indirect"][s_indirect] or 0
                                    local px4 = aData["s_close"][s_close] or 0
                                    local px5 = aData["s_fireMis"][s_fireMis] or 0
                                    local px6 = aData["o_cls"][o_cls] or 0
                                    local px7 = aData["s_cls"][s_cls] or 0

                                    points = px1 + px2 + px3 + px4 + px5 + px6 + px7 
                                    --AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, %s, points for w_cat: %s", aData.name, aData["w_cat"][w_cat])
                                    --AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, %s, points for s_cat: %s", aData.name, aData["s_cat"][s_cat])
                                    --AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, %s, points for s_indirect: %s", aData.name, aData["s_indirect"][s_indirect])
                                    --AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, %s, points for s_close: %s", aData.name, aData["s_close"][s_close])
                                    --AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, %s, points for s_fireMis: %s", aData.name, aData["s_fireMis"][s_fireMis])
                                    --AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, %s, points for o_cls: %s", aData.name, aData["o_cls"][o_cls])
                                    --AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, %s, points for s_cls: %s", aData.name, aData["s_cls"][s_cls])

                                    bc_ac[#bc_ac+1] = {name = aData.name, action = aData.action, rank = points}
                                end
                                table.sort(bc_ac, function(a,b)
                                    if a.rank and b.rank then
                                        return a.rank > b.rank 
                                    end
                                end)

                                -- record the attack, for preventing phases to act for 10 mins
                                underAttack[group:getID()] = timer.getTime()

                                choosenAct = executeActions(group, o_pos, a_pos, bc_ac, db_group.sa, db_group.skill)

                            end

                        end

                        -- counter battery part
                        if choosenAct ~= "ac_fireMissionOnShooter" then
                            if AIEN.config.firemissions == true then
                                if shooter:getPoint() and position then
                                    counterBattery(position, shooter:getPoint(), group:getCoalition())
                                end
                            end
                        end

                    else
                        AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, S_EVENT_HIT, AI consent is false")
                        
                    end
                end
            else
                AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, missing unit")
                
            end
        else
            AIEN.loggers.get(AIEN.Id):trace("AIEN.event_hit, either shooter or unit are not valid units")
            
        end
    end

end

local function event_birth(initiator)
    
    local check = pcallGetCategory(initiator)
    
    if check then
        local objCat = nil
        local subCat = nil
        objCat, subCat = initiator:getCategory()
        if objCat == 1 and subCat == 2 then -- unit, ground unit    
            if not initiator:hasAttribute("Infantry") then
                local gp = initiator:getGroup()
                if gp then
                    if not groundgroupsDb[gp:getID()] then -- since event is launched for each unit, this prevent re-adding the same group multiple times
                        local c = getGroupClass(gp)
                        local det, thr = getRanges(gp)
                        local s = getGroupSkillNum(gp)
                        groupPreventDisperse(gp)
                        --AIEN.loggers.get(AIEN.Id):info("event_birth: s %s", s)
                        groundgroupsDb[gp:getID()] = {group = gp, class = c, n = gp:getName(), coa = gp:getCoalition(), detection = det, threat = thr, tasked = false, skill = s}
                        --AIEN.loggers.get(AIEN.Id):info("event_birth: adding to groundgroupsDb %s", gp and gp:getName())
                    end
                end
            elseif objCat == 1 and subCat == 0 then -- unit, plane unit (drone)	
                local gp = initiator:getGroup() 
                if gp then				
                    local c = nil
                    if gp:getUnits() and #gp:getUnits() > 0 then
                        for _, un in pairs(gp:getUnits()) do
                            if un:hasAttribute("UAVs") then -- drone only
                                c = "UAV"
                            end
                        end
                    end
                    if c then
                        AIEN.loggers.get(AIEN.Id):trace("event_birth: adding to droneunitDb %s", un and un:getName())
                        
                        
                        droneunitDb[gp:getID()] = {group = gp, class = c, n = gp:getName(), coa = gp:getCoalition()}
                    end                        					
                end
            end	
        end
    end
end

local function event_dead(initiator)
    
    -- revTODO why do this twice (once below and then calling Object.getCategory lower)? -> Chromium: check this out -> cause pcallGetCategory only return the objCat and not subCat 
    local check = pcallGetCategory(initiator)
    
    if check then
        local objCat = nil
        local subCat = nil
        objCat, subCat =  Object.getCategory(initiator)
        if objCat and subCat then
            if objCat == 1 and subCat == 2 then -- unit, ground unit    
                mountedDb[initiator:getID()] = nil
                infcarrierDb[initiator:getID()] = nil

            end
        end
    end
end

--## EVENTS HANDLING CALLS

AIEN.eventHandler = {} -- define event based real time unit reactions. I prefer to have 1 single handler that then will route itself on the right directions event based.
function AIEN.eventHandler:onEvent(event)	

    if event.id == world.event.S_EVENT_HIT then 
        local u = event.target
        local s = event.initiator
		local w	= event.weapon 

        event_hit(u, s, w)

    elseif event.id == world.event.S_EVENT_BIRTH then 
        local i = event.initiator
        if i then
            event_birth(i)
        end

    elseif event.id == world.event.S_EVENT_DEAD or event.id == world.event.S_EVENT_UNIT_LOST then 
        local i = event.initiator
        if i then
            event_dead(i)
        end

    end
end
world.addEventHandler(AIEN.eventHandler)



--## INIT SCRIPT
if AIEN.config.dontInitialize then
    AIEN.loggers.get(AIEN.Id):info("Loaded (BUT NOT INITIALIZED) %s.%s.%s, released %s", MainVersion, SubVersion, Build, Date)
else
	AIEN.performPhaseCycle()
	AIEN.loggers.get(AIEN.Id):info("Loaded %s.%s.%s, released %s", MainVersion, SubVersion, Build, Date)
end


--~=
