
do
    if STTS ~= nil and STTS.DIRECTORY == "C:\\Users\\Ciaran\\Dropbox\\Dev\\DCS\\DCS-SRS\\install-build" then
        STTS.DIRECTORY = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio"
    end

    math.random(math.ceil(timer.getTime0()+timer.getTime()))
    for i=1,math.random(2,5) do
        math.random(math.random(math.floor(math.random()*300),300),math.random(math.floor(math.random()*10000),10000))
    end
end

do
    HOUND = {
        VERSION = "0.4.3",
        DEBUG = false,
        ELLIPSE_PERCENTILE = 0.6,
        DATAPOINTS_NUM = 30,
        DATAPOINTS_INTERVAL = 30,
        CONTACT_TIMEOUT = 900,
        MAX_ANGULAR_RES_DEG = 20,
        ANTENNA_FACTOR = 1.0,
        MGRS_PRECISION = 5,
        EXTENDED_INFO = true,
        USE_LEGACY_MARKERS = true,
        MARKER_MIN_ALPHA = 0.05,
        MARKER_MAX_ALPHA = 0.2,
        MARKER_LINE_OPACITY = 0.3,
        MARKER_TEXT_POINTER = "⇙ ", -- "¤ « "
        TTS_ENGINE = {'STTS','GRPC'},
        MENU_PAGE_LENGTH = 9,
        ENABLE_BETTER_SCORE = true,
        REF_DIST = 75000, -- Do not change, used for datapoint weighting
        ENABLE_WLS = false,
        ENABLE_KALMAN = false,
        AUTO_ADD_PLATFORM_BY_PAYLOAD = true, -- if true, will automatically add platforms that have ELINT payloads (currently, due to DCS limits, only works for units spawning with the required pods)
    }

    HOUND.MARKER = {
        NONE = 0,
        SITE_ONLY = 1,
        POINT = 2,
        CIRCLE = 3,
        DIAMOND = 4,
        OCTAGON = 5,
        POLYGON = 6
    }

    HOUND.EVENTS = {
        NO_CHANGE     = 0,
        HOUND_ENABLED = 1,
        HOUND_DISABLED = 2,
        PLATFORM_ADDED = 3,
        PLATFORM_REMOVED = 4,
        PLATFORM_DESTROYED = 5,
        TRANSMITTER_ADDED = 6,
        TRANSMITTER_REMOVED = 7,
        TRANSMITTER_DESTROYED = 8,
        RADAR_NEW = 9,
        RADAR_DETECTED = 10,
        RADAR_UPDATED = 11,
        RADAR_DESTROYED = 12,
        RADAR_ALIVE = 13,
        RADAR_ASLEEP = 14,
        SITE_NEW = 15,
        SITE_CREATED = 16,
        SITE_UPDATED = 17,
        SITE_CLASSIFIED = 18,
        SITE_REMOVED = 19,
        SITE_ALIVE = 20,
        SITE_ASLEEP = 21,
        SITE_LAUNCH = 22,
    }

    HOUND.INSTANCES = {}

    function HOUND.getInstance(InstanceId)
        if HOUND.setContains(HOUND.INSTANCES,InstanceId) then
            return HOUND.INSTANCES[InstanceId]
        end
        return nil
    end

    function HOUND.setMgrsPresicion(value)
        if type(value) == "number" then
            HOUND.MGRS_PRECISION = math.min(1,math.max(5,math.floor(value)))
        end
    end

    function HOUND.showExtendedInfo(value)
        if type(value) == "boolean" then
            HOUND.EXTENDED_INFO = value
        end
    end

    function HOUND.addEventHandler(handler)
        HOUND.EventHandler.addEventHandler(handler)
    end

    function HOUND.removeEventHandler(handler)
        HOUND.EventHandler.removeEventHandler(handler)
    end

    HOUND.Contact = {}
    HOUND.Comms = {}

    function HOUND.inheritsFrom( baseClass )

        local new_class = {}
        local class_mt = { __index = new_class }

        function new_class:create()
            local newinst = {}
            setmetatable( newinst, class_mt )
            return newinst
        end

        if nil ~= baseClass then
            setmetatable( new_class, { __index = baseClass } )
        end

        function new_class:class()
            return new_class
        end

        function new_class:superClass()
            return baseClass
        end

        function new_class:isa( theClass )
            local b_isa = false
            local cur_class = new_class

            while ( nil ~= cur_class ) and ( false == b_isa ) do
                if cur_class == theClass then
                    b_isa = true
                else
                    cur_class = cur_class:superClass()
                end
            end
            return b_isa
        end
        return new_class
    end

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

    function HOUND.setContains(set, key)
        if not set or not key then return false end
        return set[key] ~= nil
    end

    function HOUND.setContainsValue(set,value)
        if not set or not value then return false end
        for _,v in pairs(set) do
            if v == value then
                return true
            end
        end
        return false
    end

    function HOUND.setIntersection(a,b)
        local res = {}
        for k in pairs(a) do
          res[k] = b[k]
        end
        return res
      end

    function HOUND.Gaussian(mean, sigma)
        return math.sqrt(-2 * sigma * math.log(math.random())) *
                   math.cos(2 * math.pi * math.random()) + mean
    end

    function HOUND.reverseLookup(tbl,value)
        if type(tbl) ~= "table" or type(value) == "nil" then return end
        for k,v in pairs(tbl) do
            if v == value then return k end
        end
    end

    function string.split(str, delim)
        if not str or type(str) ~= "string" then return {str} end
        if not delim then
            delim = "%S"
        end
        local chunks = {}
        for substring in str:gmatch("[^" .. delim .. "]+") do
            table.insert(chunks, substring)
        end
        return chunks
    end
end
do
    local l_env = env

    HOUND.Logger = {
        level = 3
    }
    HOUND.Logger.__index = HOUND.Logger

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

    function HOUND.Logger.setBaseLevel(level)
        if HOUND.setContainsValue(HOUND.Logger.LEVEL,level) then
            HOUND.Logger.level = level
        end
    end

    function HOUND.Logger.formatText(text, ...)
        if not text then
            return ""
        end
        if type(text) ~= 'string' then
            text = tostring(text)
        else
            if arg and arg.n and arg.n > 0 then
                local pArgs = {}
                for index,value in ipairs(arg) do
                    pArgs[index] = tostring(value)
                end
                text = text:format(unpack(pArgs))
            end
        end
        local fName = nil
        local cLine = nil
        if debug then
            local dInfo = debug.getinfo(3)
            fName = dInfo.name
            cLine = dInfo.currentline
        end
        if fName and cLine then
            return fName .. '|' .. cLine .. ': ' .. text
        elseif cLine then
            return cLine .. ': ' .. text
        else
            return ' ' .. text
        end
    end

    function HOUND.Logger.print(level, text)
        local texts = {text}
        local levelChar = 'E'
        local logFunction = l_env.error
        if level == HOUND.Logger.LEVEL["warning"] then
            levelChar = 'W'
            logFunction = l_env.warning
        elseif level == HOUND.Logger.LEVEL["info"] then
            levelChar = 'I'
            logFunction = l_env.info
        elseif level == HOUND.Logger.LEVEL["debug"] then
            levelChar = 'D'
            logFunction = l_env.info
        elseif level == HOUND.Logger.LEVEL["trace"] then
            levelChar = 'T'
            logFunction = l_env.info
        end
        for i = 1, #texts do
            if i == 1 then
                logFunction('[Hound](' .. levelChar.. ') - ' .. texts[i])
            else
                logFunction(texts[i])
            end
        end
    end

    function HOUND.Logger.error(text, ...)
        if HOUND.Logger.level >= 1 then
            text = HOUND.Logger.formatText(text, unpack(arg))
            HOUND.Logger.print(1, text)
        end
    end

    function HOUND.Logger.warn(text, ...)
        if HOUND.Logger.level >= 2 then
            text = HOUND.Logger.formatText(text, unpack(arg))
            HOUND.Logger.print(2, text)
        end
    end

    function HOUND.Logger.info(text, ...)
        if HOUND.Logger.level >= 3 then
            text = HOUND.Logger.formatText(text, unpack(arg))
            HOUND.Logger.print(3, text)
        end
    end

    function HOUND.Logger.debug(text, ...)
        if HOUND.Logger.level >= 4 then
            text = HOUND.Logger.formatText(text, unpack(arg))
            HOUND.Logger.print(4, text)
        end
    end

    function HOUND.Logger.trace(text, ...)
        if HOUND.Logger.level >= 5 then
            text = HOUND.Logger.formatText(text, unpack(arg))
            HOUND.Logger.print(5, text)
        end
    end

    function HOUND.Logger.onScreenDebug(text,time)
        if type(text) ~= "string" then return end
        if type(time) ~= "number" then
            time = 15
        end
        trigger.action.outText(text,math.ceil(time))
    end

    if HOUND.DEBUG then
        HOUND.Logger.setBaseLevel(HOUND.Logger.LEVEL.trace)
    end
end
do
    local l_math = math
    HOUND.Mist = {}
    HOUND.Mist.__index = HOUND.Mist

    function HOUND.Mist.getNorthCorrection(gPoint)	--gets the correction needed for true north
		local point = HOUND.Mist.utils.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 l_math.atan2(north_posit.z - point.z, north_posit.x - point.x)
	end

function HOUND.Mist.getAvgPoint(points)
	local avgX, avgY, avgZ, totNum = 0, 0, 0, 0
	for i = 1, #points do
        local nPoint = HOUND.Mist.utils.makeVec3(points[i])
		if nPoint.z then
			avgX = avgX + nPoint.x
			avgY = avgY + nPoint.y
			avgZ = avgZ + nPoint.z
			totNum = totNum + 1
		end
	end
	if totNum ~= 0 then
		return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum}
	end
end

function HOUND.Mist.getAvgPos(unitNames)
	local avgX, avgY, avgZ, totNum = 0, 0, 0, 0
	for i = 1, #unitNames do
		local unit
		if Unit.getByName(unitNames[i]) then
			unit = Unit.getByName(unitNames[i])
		elseif StaticObject.getByName(unitNames[i]) then
			unit = StaticObject.getByName(unitNames[i])
		end
		if unit and unit:isExist() == true then
			local pos = unit:getPosition().p
			if pos then -- you never know O.o
				avgX = avgX + pos.x
				avgY = avgY + pos.y
				avgZ = avgZ + pos.z
				totNum = totNum + 1
			end
		end
	end
	if totNum ~= 0 then
		return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum}
	end
end

function HOUND.Mist.getAvgGroupPos(groupName)
	if type(groupName) == 'string' and Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then
		groupName = Group.getByName(groupName)
	end
	local units = {}
	for i = 1, groupName:getSize() do
		table.insert(units, groupName:getUnit(i):getName())
	end

	return HOUND.Mist.getAvgPos(units)

end

end

do -- HOUND.Mist.vec scope
	HOUND.Mist.vec = {}

	function HOUND.Mist.vec.add(vec1, vec2)
		return {x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z}
	end

	function HOUND.Mist.vec.sub(vec1, vec2)
		return {x = vec1.x - vec2.x, y = vec1.y - vec2.y, z = vec1.z - vec2.z}
	end

	function HOUND.Mist.vec.scalarMult(vec, mult)
		return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult}
	end

	HOUND.Mist.vec.scalar_mult = HOUND.Mist.vec.scalarMult

	function HOUND.Mist.vec.dp (vec1, vec2)
		return vec1.x*vec2.x + vec1.y*vec2.y + vec1.z*vec2.z
	end

	function HOUND.Mist.vec.cp(vec1, vec2)
		return { x = vec1.y*vec2.z - vec1.z*vec2.y, y = vec1.z*vec2.x - vec1.x*vec2.z, z = vec1.x*vec2.y - vec1.y*vec2.x}
	end

	function HOUND.Mist.vec.mag(vec)
		return (vec.x^2 + vec.y^2 + vec.z^2)^0.5
	end

	function HOUND.Mist.vec.getUnitVec(vec)
		local mag = HOUND.Mist.vec.mag(vec)
		return { x = vec.x/mag, y = vec.y/mag, z = vec.z/mag }
	end

	function HOUND.Mist.vec.rotateVec2(vec2, theta)
		return { x = vec2.x*math.cos(theta) - vec2.y*math.sin(theta), y = vec2.x*math.sin(theta) + vec2.y*math.cos(theta)}
	end

    function HOUND.Mist.vec.normalize(vec3)
        local mag =  HOUND.Mist.vec.mag(vec3)
        if mag ~= 0 then
            return HOUND.Mist.vec.scalar_mult(vec3, 1.0 / mag)
        end
    end
end

do -- HOUND.Mist.util scope
 	HOUND.Mist.utils = {}

	function HOUND.Mist.utils.toDegree(angle)
		return angle*180/math.pi
	end

	function HOUND.Mist.utils.toRadian(angle)
		return angle*math.pi/180
	end

	function HOUND.Mist.utils.metersToNM(meters)
		return meters/1852
	end

	function HOUND.Mist.utils.metersToFeet(meters)
		return meters/0.3048
	end

	function HOUND.Mist.utils.NMToMeters(nm)
		return nm*1852
	end

	function HOUND.Mist.utils.feetToMeters(feet)
		return feet*0.3048
	end

	function HOUND.Mist.utils.hexToRGB(hex, l) -- because for some reason the draw tools use hex when everything is rgba 0 - 1
        local int = 255
        if l then
         int = 1
        end
        if hex and type(hex) == 'string' then
            local val = {}
            hex = string.gsub(hex, '0x', '')
            if string.len(hex) == 8 then
                val[1] = tonumber("0x"..hex:sub(1,2)) / int
                val[2] = tonumber("0x"..hex:sub(3,4)) / int
                val[3] = tonumber("0x"..hex:sub(5,6)) / int
                val[4] = tonumber("0x"..hex:sub(7,8)) / int

                return val
            end
        end
   end

	function HOUND.Mist.utils.makeVec2(vec)
		if vec.z then
			return {x = vec.x, y = vec.z}
		else
			return {x = vec.x, y = vec.y}	-- it was actually already vec2.
		end
	end

	function HOUND.Mist.utils.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

	function HOUND.Mist.utils.makeVec3GL(vec, offset)
		local adj = offset or 0

		if not vec.z then
			return {x = vec.x, y = (land.getHeight(vec) + adj), z = vec.y}
		else
			return {x = vec.x, y = (land.getHeight({x = vec.x, y = vec.z}) + adj), z = vec.z}
		end
	end

    function HOUND.Mist.utils.getHeadingPoints(point1, point2, north) -- sick of writing this out.
        if north then
			local p1 = HOUND.Mist.utils.get3DDist(point1)
            return HOUND.Mist.utils.getDir(HOUND.Mist.vec.sub(HOUND.Mist.utils.makeVec3(point2), p1), p1)
        else
            return HOUND.Mist.utils.getDir(HOUND.Mist.vec.sub(HOUND.Mist.utils.makeVec3(point2), HOUND.Mist.utils.makeVec3(point1)))
        end
    end

	function HOUND.Mist.utils.getDir(vec, point)
		local dir = math.atan2(vec.z, vec.x)
		if point then
			dir = dir + HOUND.Mist.getNorthCorrection(point)
		end
		if dir < 0 then
			dir = dir + 2 * math.pi	-- put dir in range of 0 to 2*pi
		end
		return dir
	end

	function HOUND.Mist.utils.get2DDist(point1, point2)
        if not point1 then
            log:warn("HOUND.Mist.utils.get2DDist  1st input value is nil")
        end
        if not point2 then
            log:warn("HOUND.Mist.utils.get2DDist  2nd input value is nil")
        end
		point1 = HOUND.Mist.utils.makeVec3(point1)
		point2 = HOUND.Mist.utils.makeVec3(point2)
		return HOUND.Mist.vec.mag({x = point1.x - point2.x, y = 0, z = point1.z - point2.z})
	end

	function HOUND.Mist.utils.get3DDist(point1, point2)
        if not point1 then
            log:warn("HOUND.Mist.utils.get2DDist  1st input value is nil")
        end
        if not point2 then
            log:warn("HOUND.Mist.utils.get2DDist  2nd input value is nil")
        end
		return HOUND.Mist.vec.mag({x = point1.x - point2.x, y = point1.y - point2.y, z = point1.z - point2.z})
	end

	function HOUND.Mist.utils.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

	function HOUND.Mist.utils.round(num, idp)
		local mult = 10^(idp or 0)
		return math.floor(num * mult + 0.5) / mult
	end

function HOUND.Mist.utils.basicSerialize(var)
    if var == nil then
        return "\"\""
    else
        if ((type(var) == 'number') or
                (type(var) == 'boolean') or
                (type(var) == 'function') or
                (type(var) == 'table') or
                (type(var) == 'userdata') ) then
                    return tostring(var)
        elseif type(var) == 'string' then
            var = string.format('%q', var)
            return var
        end
    end
end

function HOUND.Mist.utils.tableShowSorted(tbls, v)
	local vars = v or {}
	local loc = vars.loc or ""
	local indent = vars.indent or ""
	local tableshow_tbls = vars.tableshow_tbls or {}
	local tbl = tbls 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'

		local sorted = {}
		local function byteCompare(str1, str2)
			local shorter = string.len(str1)
			if shorter > string.len(str2) then
				 shorter = string.len(str2)
			end
			for i = 1, shorter do
				local b1 = string.byte(str1, i)
				local b2 = string.byte(str2, i)

				if b1 < b2 then
					return true
				elseif b1 > b2 then
					return false
				end

			end
			return false
		end
		for ind, val in pairs(tbl) do -- serialize its fields
			local indS = tostring(ind)
			local ins = {ind = indS, val = val}
			local index
			if #sorted > 0 then
				local found = false
				for i = 1, #sorted do
					if byteCompare(indS, tostring(sorted[i].ind)) == true then
						index = i
						break
					end
				end
			end
			if index then
				table.insert(sorted, index, ins)
			else
				table.insert(sorted, ins)
			end
		end
		for i = 1, #sorted do
			local ind = sorted[i].ind
			local val = sorted[i].val

			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] = HOUND.Mist.utils.basicSerialize(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] = HOUND.Mist.utils.basicSerialize(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] = ' already defined: ' .. tableshow_tbls[val] .. ',\n'
				else
					tableshow_tbls[val] = loc .. '["' .. ind .. '"]'
					tbl_str[#tbl_str + 1] = HOUND.Mist.utils.tableShowSorted(val, {loc =  loc .. '["' .. ind .. '"]', indent = indent .. '    ', tableshow_tbls = 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] =  ', C function\n'
					else
						if (string.sub(info.source, 1, 2) == [[./]]) then
							tbl_str[#tbl_str + 1] = string.format('%q',  'function, defined in (' ..  '-' .. info.lastlinedefined .. ')' .. info.source) ..',\n'
						else
							tbl_str[#tbl_str + 1] = string.format('%q', 'function, defined in (' ..  '-' .. 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 ' .. HOUND.Mist.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)
			end
		end

		tbl_str[#tbl_str + 1] = indent .. '}'
		return table.concat(tbl_str)
	end

end

function HOUND.Mist.utils.tableShow(tbl, loc, indent, tableshow_tbls) --based on serialize_slmod, this is a _G serialization
	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
			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] = HOUND.Mist.utils.basicSerialize(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] = HOUND.Mist.utils.basicSerialize(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 ..	'[' .. HOUND.Mist.utils.basicSerialize(ind) .. ']'
					tbl_str[#tbl_str + 1] = tostring(val) .. ' '
					tbl_str[#tbl_str + 1] = HOUND.Mist.utils.tableShow(val,	loc .. '[' .. HOUND.Mist.utils.basicSerialize(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 ' .. HOUND.Mist.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)
			end
		end

		tbl_str[#tbl_str + 1] = indent .. '}'
		return table.concat(tbl_str)
	end
end

do
    HOUND.Mist.shape = {}
    function HOUND.Mist.shape.insideShape(shape1, shape2, full)
        if shape1.radius then -- probably a circle
            if shape2.radius then
                 return HOUND.Mist.shape.circleInCircle(shape1, shape2, full)
            elseif shape2[1] then
                 return HOUND.Mist.shape.circleInPoly(shape1, shape2, full)
            end

        elseif shape1[1] then -- shape1 is probably a polygon
            if shape2.radius then
                return  HOUND.Mist.shape.polyInCircle(shape1, shape2, full)
            elseif shape2[1] then
                return  HOUND.Mist.shape.polyInPoly(shape1, shape2, full)
            end
        end
        return false
    end

    function HOUND.Mist.shape.circleInCircle(c1, c2, full)
        if not full then -- quick partial check
            if HOUND.Mist.utils.get2DDist(c1.point, c2.point) <= c2.radius then
                return true
            end
        end
        local theta = HOUND.Mist.utils.getHeadingPoints(c2.point, c1.point) -- heading from
        if full then
            return  HOUND.Mist.utils.get2DDist(HOUND.Mist.projectPoint(c1.point, c1.radius, theta), c2.point) <= c2.radius
        else
            return HOUND.Mist.utils.get2DDist(HOUND.Mist.projectPoint(c1.point, c1.radius, theta + math.pi), c2.point) <= c2.radius
        end
        return false
    end
    function HOUND.Mist.shape.circleInPoly(circle, poly, full)

        if poly and type(poly) == 'table' and circle and type(circle) == 'table' and circle.radius and circle.point then
            if not full then
                for i = 1, #poly do
                    if HOUND.Mist.utils.get2DDist(circle.point, poly[i]) <= circle.radius then
                        return true
                    end
                end
            end
            local count = 0
            for i = 1, #poly do
                local theta -- heading of each set of points
                if i == #poly then
                    theta = HOUND.Mist.utils.getHeadingPoints(poly[i],poly[1])
                else
                    theta = HOUND.Mist.utils.getHeadingPoints(poly[i],poly[i+1])
                end
                local pPoint = HOUND.Mist.projectPoint(circle.point, circle.radius, theta - (math.pi/180))
                local oPoint = HOUND.Mist.projectPoint(circle.point, circle.radius, theta + (math.pi/180))
                if HOUND.Mist.pointInPolygon(pPoint, poly) == true then
                     if (full and HOUND.Mist.pointInPolygon(oPoint, poly) == true) or not full then
                        return true

                    end

                end
            end
        end
        return false
    end
    function HOUND.Mist.shape.polyInPoly(p1, p2, full)
        local count = 0
        for i = 1, #p1 do

            if HOUND.Mist.pointInPolygon(p1[i], p2) then
                count = count + 1
            end
            if (not full) and count > 0 then
                return true
            end
        end
        if count == #p1 then
            return true
        end

        return false
    end

    function HOUND.Mist.shape.polyInCircle(poly, circle, full)
            local count = 0
            for i = 1, #poly do
                if HOUND.Mist.utils.get2DDist(circle.point, poly[i]) <= circle.radius then
                    if full then
                        count = count + 1
                    else
                       return true
                    end
                end
            end
            if count == #poly then
                return true
            end

        return false
    end

    function HOUND.Mist.shape.getPointOnSegment(point, seg, isSeg)
        local p = HOUND.Mist.utils.makeVec2(point)
        local s1 = HOUND.Mist.utils.makeVec2(seg[1])
        local s2 = HOUND.Mist.utils.makeVec2(seg[2])
        local cx, cy = p.x - s1.x, p.y - s1.y
        local dx, dy = s2.x - s1.x, s2.y - s1.y
        local d = (dx*dx + dy*dy)

        if d == 0 then
           return {x = s1.x, y = s1.y}
        end
        local u = (cx*dx + cy*dy)/d
        if isSeg then
           if u < 0 then
                u = 0
            elseif u > 1 then
                u = 1
            end
        end
        return {x = s1.x + u*dx, y = s1.y + u*dy}
    end

    function HOUND.Mist.shape.segmentIntersect(seg1, seg2)
        local segA = {HOUND.Mist.utils.makeVec2(seg1[1]), HOUND.Mist.utils.makeVec2(seg1[2])}
        local segB = {HOUND.Mist.utils.makeVec2(seg2[1]), HOUND.Mist.utils.makeVec2(seg2[2])}

        local dx1, dy1 = segA[2].x - segA[1].x, segA[2].y - segA[1].y
        local dx2, dy2 = segB[2].x - segB[1].x, segB[2].y - segB[1].y
        local dx3, dy3 = segA[1].x - segB[1].x, segA[1].y - segB[1].y

        local d = dx1*dy2 - dy1*dx2

        if d == 0 then
           return false
        end
        local t1 = (dx2*dy3 - dy2*dx3)/d
        if t1 < 0 or t1 > 1 then
          return false
        end
        local t2 = (dx1*dy3 - dy1*dx3)/d
        if t2 < 0 or t2 > 1 then
          return false
        end
          return true, {x = segA[1].x + t1*dx1, y = segA[1].y + t1*dy1}
    end
    function HOUND.Mist.pointInPolygon(point, poly, maxalt) --raycasting point in polygon. Code from http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm

        point = HOUND.Mist.utils.makeVec3(point)
        local px = point.x
        local pz = point.z
        local cn = 0
        local newpoly = HOUND.Mist.utils.deepCopy(poly)

        if not maxalt or (point.y <= maxalt) then
            local polysize = #newpoly
            newpoly[#newpoly + 1] = newpoly[1]

            newpoly[1] = HOUND.Mist.utils.makeVec3(newpoly[1])

            for k = 1, polysize do
                newpoly[k+1] = HOUND.Mist.utils.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
        else
            return false
        end
    end

	function HOUND.Mist.projectPoint(point, dist, theta)
		local newPoint = {}
		if point.z then
		   newPoint.z = HOUND.Mist.utils.round(math.sin(theta) * dist + point.z, 3)
		   newPoint.y = HOUND.Mist.utils.deepCopy(point.y)
		else
		   newPoint.y = HOUND.Mist.utils.round(math.sin(theta) * dist + point.y, 3)
		end
		newPoint.x = HOUND.Mist.utils.round(math.cos(theta) * dist + point.x, 3)

		return newPoint
	end
end

do
	HOUND.Mist.time = {}
	function HOUND.Mist.time.getDHMS(timeInSec)
		if timeInSec and type(timeInSec) == 'number' then
			local tbl = {d = 0, h = 0, m = 0, s = 0}
			if timeInSec > 86400 then
				while timeInSec > 86400 do
					tbl.d = tbl.d + 1
					timeInSec = timeInSec - 86400
				end
			end
			if timeInSec > 3600 then
				while timeInSec > 3600 do
					tbl.h = tbl.h + 1
					timeInSec = timeInSec - 3600
				end
			end
			if timeInSec > 60 then
				while timeInSec > 60 do
					tbl.m = tbl.m + 1
					timeInSec = timeInSec - 60
				end
			end
			tbl.s = timeInSec
			return tbl
		else
			log:error("Didn't recieve number")
			return
		end
	end
end
end

do
    HOUND.Matrix = {}
    HOUND.Matrix.__index = HOUND.Matrix

    function HOUND.Matrix:new( rows, columns, value )
        if type( rows ) == "table" then
            if type(rows[1]) ~= "table" then -- expect a vector
                return setmetatable( {{rows[1]},{rows[2]},{rows[3]}},HOUND.Matrix )
            end
            return setmetatable( rows,HOUND.Matrix )
        end
        local mtx = {}
        local value = value or 0
        if columns == "I" then
            for i = 1,rows do
                mtx[i] = {}
                for j = 1,rows do
                    if i == j then
                        mtx[i][j] = 1
                    else
                        mtx[i][j] = 0
                    end
                end
            end
        else
            for i = 1,rows do
                mtx[i] = {}
                for j = 1,columns do
                    mtx[i][j] = value
                end
            end
        end
        return setmetatable( mtx,HOUND.Matrix )
    end

    setmetatable( HOUND.Matrix, { __call = function( ... ) return HOUND.Matrix.new( ... ) end } )

    function HOUND.Matrix.add( m1, m2 )
        local mtx = {}
        for i = 1,#m1 do
            local m3i = {}
            mtx[i] = m3i
            for j = 1,#m1[1] do
                m3i[j] = m1[i][j] + m2[i][j]
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.sub( m1, m2 )
        local mtx = {}
        for i = 1,#m1 do
            local m3i = {}
            mtx[i] = m3i
            for j = 1,#m1[1] do
                m3i[j] = m1[i][j] - m2[i][j]
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.mul( m1, m2 )
        local mtx = {}
        for i = 1,#m1 do
            mtx[i] = {}
            for j = 1,#m2[1] do
                local num = m1[i][1] * m2[1][j]
                for n = 2,#m1[1] do
                    num = num + m1[i][n] * m2[n][j]
                end
                mtx[i][j] = num
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.div( m1, m2 )
        local rank; m2,rank = HOUND.Matrix.invert( m2 )
        if not m2 then return m2, rank end -- singular
        return HOUND.Matrix.mul( m1, m2 )
    end

    function HOUND.Matrix.mulnum( m1, num )
        local mtx = {}
        for i = 1,#m1 do
            mtx[i] = {}
            for j = 1,#m1[1] do
                mtx[i][j] = m1[i][j] * num
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.divnum( m1, num )
        local mtx = {}
        for i = 1,#m1 do
            local mtxi = {}
            mtx[i] = mtxi
            for j = 1,#m1[1] do
                mtxi[j] = m1[i][j] / num
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.pow( m1, num )
        assert(num == math.floor(num), "exponent not an integer")
        if num == 0 then
            return HOUND.Matrix:new( #m1,"I" )
        end
        if num < 0 then
            local rank; m1,rank = HOUND.Matrix.invert( m1 )
          if not m1 then return m1, rank end -- singular
            num = -num
        end
        local mtx = HOUND.Matrix.copy( m1 )
        for i = 2,num	do
            mtx = HOUND.Matrix.mul( mtx,m1 )
        end
        return mtx
    end

    local function number_norm2(x)
      return x * x
    end

    function HOUND.Matrix.det( m1 )
        assert(#m1 == #m1[1], "matrix not square")

        local size = #m1

        if size == 1 then
            return m1[1][1]
        end

        if size == 2 then
            return m1[1][1]*m1[2][2] - m1[2][1]*m1[1][2]
        end

        if size == 3 then
            return ( m1[1][1]*m1[2][2]*m1[3][3] + m1[1][2]*m1[2][3]*m1[3][1] + m1[1][3]*m1[2][1]*m1[3][2]
                - m1[1][3]*m1[2][2]*m1[3][1] - m1[1][1]*m1[2][3]*m1[3][2] - m1[1][2]*m1[2][1]*m1[3][3] )
        end

        local e = m1[1][1]
        local zero  = type(e) == "table" and e.zero or 0
        local norm2 = type(e) == "table" and e.norm2 or number_norm2

        local mtx = HOUND.Matrix.copy( m1 )
        local det = 1
        for j = 1,#mtx[1] do
            local rows = #mtx
            local subdet,xrow
            for i = 1,rows do
                local e = mtx[i][j]
                if not subdet then
                    if e ~= zero then
                        subdet,xrow = e,i
                    end
                elseif e ~= zero and math.abs(norm2(e)-1) < math.abs(norm2(subdet)-1) then
                    subdet,xrow = e,i
                end
            end
            if subdet then
                if xrow ~= rows then
                    mtx[rows],mtx[xrow] = mtx[xrow],mtx[rows]
                    det = -det
                end
                for i = 1,rows-1 do
                    if mtx[i][j] ~= zero then
                        local factor = mtx[i][j]/subdet
                        for n = j+1,#mtx[1] do
                            mtx[i][n] = mtx[i][n] - factor * mtx[rows][n]
                        end
                    end
                end
                if math.fmod( rows,2 ) == 0 then
                    det = -det
                end
                det = det * subdet
                table.remove( mtx )
            else
                return det * 0
            end
        end
        return det
    end

    local pivotOk = function( mtx,i,j,norm2 )
        local iMin
        local normMin = math.huge
        for _i = i,#mtx do
            local e = mtx[_i][j]
            local norm = math.abs(norm2(e))
            if norm > 0 and norm < normMin then
                iMin = _i
                normMin = norm
                end
            end
        if iMin then
            if iMin ~= i then
                mtx[i],mtx[iMin] = mtx[iMin],mtx[i]
            end
            return true
            end
        return false
    end

    local function copy(x)
        return type(x) == "table" and x.copy(x) or x
    end

    function HOUND.Matrix.dogauss( mtx )
        local e = mtx[1][1]
        local zero = type(e) == "table" and e.zero or 0
        local one  = type(e) == "table" and e.one  or 1
        local norm2 = type(e) == "table" and e.norm2 or number_norm2

        local rows,columns = #mtx,#mtx[1]
        for j = 1,rows do
            if pivotOk( mtx,j,j,norm2 ) then
                for i = j+1,rows do
                    if mtx[i][j] ~= zero then
                        local factor = mtx[i][j]/mtx[j][j]
                        mtx[i][j] = copy(zero)
                        for _j = j+1,columns do
                            mtx[i][_j] = mtx[i][_j] - factor * mtx[j][_j]
                        end
                    end
                end
            else
                return false,j-1
            end
        end
        for j = rows,1,-1 do
            local div = mtx[j][j]
            for _j = j+1,columns do
                mtx[j][_j] = mtx[j][_j] / div
            end
            for i = j-1,1,-1 do
                if mtx[i][j] ~= zero then
                    local factor = mtx[i][j]
                    for _j = j+1,columns do
                        mtx[i][_j] = mtx[i][_j] - factor * mtx[j][_j]
                    end
                    mtx[i][j] = copy(zero)
                end
            end
            mtx[j][j] = copy(one)
        end
        return true
    end

    function HOUND.Matrix.invert( m1 )
        assert(#m1 == #m1[1], "matrix not square")
        local mtx = HOUND.Matrix.copy( m1 )
        local ident = setmetatable( {},HOUND.Matrix )
        local e = m1[1][1]
        local zero = type(e) == "table" and e.zero or 0
        local one  = type(e) == "table" and e.one  or 1
        for i = 1,#m1 do
            local identi = {}
            ident[i] = identi
            for j = 1,#m1 do
                identi[j] = copy((i == j) and one or zero)
            end
        end
        mtx = HOUND.Matrix.concath( mtx,ident )
        local done,rank = HOUND.Matrix.dogauss( mtx )
        if done then
            return HOUND.Matrix.subm( mtx, 1,(#mtx[1]/2)+1,#mtx,#mtx[1] )
        else
            return nil,rank
        end
    end

    local function get_abs_avg( m1, m2 )
        local dist = 0
        local e = m1[1][1]
        local abs = type(e) == "table" and e.abs or math.abs
        for i=1,#m1 do
            for j=1,#m1[1] do
                dist = dist + abs(m1[i][j]-m2[i][j])
            end
        end
        return dist/(#m1*2)
    end
    function HOUND.Matrix.sqrt( m1, iters )
        assert(#m1 == #m1[1], "matrix not square")
        local iters = iters or math.huge
        local y = HOUND.Matrix.copy( m1 )
        local z = HOUND.Matrix(#y, 'I')
        local dist = math.huge
        for n=1,iters do
            local lasty,lastz = y,z
            y, z = HOUND.Matrix.divnum((HOUND.Matrix.add(y,HOUND.Matrix.invert(z))),2),
                    HOUND.Matrix.divnum((HOUND.Matrix.add(z,HOUND.Matrix.invert(y))),2)
            local dist1 = get_abs_avg(y,lasty)
            if iters == math.huge then
                if dist1 >= dist then
                    return lasty,lastz,get_abs_avg(HOUND.Matrix.mul(lasty,lasty),m1)
                end
            end
            dist = dist1
        end
        return y,z,get_abs_avg(HOUND.Matrix.mul(y,y),m1)
    end

    function HOUND.Matrix.root( m1, root, iters )
        assert(#m1 == #m1[1], "matrix not square")
        local iters = iters or math.huge
        local mx = HOUND.Matrix.copy( m1 )
        local my = HOUND.Matrix.mul(mx:invert(),mx:pow(root-1))
        local dist = math.huge
        for n=1,iters do
            local lastx,lasty = mx,my
            mx,my = mx:mulnum(root-1):add(my:invert()):divnum(root),
                my:mulnum(root-1):add(mx:invert()):divnum(root)
                    :mul(my:invert():pow(root-2)):mul(my:mulnum(root-1)
                    :add(mx:invert())):divnum(root)
            local dist1 = get_abs_avg(mx,lastx)
            if iters == math.huge then
                if dist1 >= dist then
                    return lastx,lasty,get_abs_avg(HOUND.Matrix.pow(lastx,root),m1)
                end
            end
            dist = dist1
        end
        return mx,my,get_abs_avg(HOUND.Matrix.pow(mx,root),m1)
    end

    function HOUND.Matrix.normf(mtx)
        local mtype = HOUND.Matrix.type(mtx)
        local result = 0
        for i = 1,#mtx do
        for j = 1,#mtx[1] do
            local e = mtx[i][j]
            if mtype ~= "number" then e = e:abs() end
            result = result + e^2
        end
        end
        local sqrt = (type(result) == "number") and math.sqrt or result.sqrt
        return sqrt(result)
    end

    function HOUND.Matrix.normmax(mtx)
        local abs = (HOUND.Matrix.type(mtx) == "number") and math.abs or mtx[1][1].abs
        local result = 0
        for i = 1,#mtx do
        for j = 1,#mtx[1] do
            local e = abs(mtx[i][j])
            if e > result then result = e end
        end
        end
        return result
    end

    local numround = function( num,mult )
        return math.floor( num * mult + 0.5 ) / mult
    end
    local tround = function( t,mult )
        for i,v in ipairs(t) do
            t[i] = math.floor( v * mult + 0.5 ) / mult
        end
        return t
    end
    function HOUND.Matrix.round( mtx, idp )
        local mult = 10^( idp or 0 )
        local fround = HOUND.Matrix.type( mtx ) == "number" and numround or tround
        for i = 1,#mtx do
            for j = 1,#mtx[1] do
                mtx[i][j] = fround(mtx[i][j],mult)
            end
        end
        return mtx
    end

    local numfill = function( _,start,stop,idp )
        return l_math.random( start,stop ) / idp
    end
    local tfill = function( t,start,stop,idp )
        for i in ipairs(t) do
            t[i] = l_math.random( start,stop ) / idp
        end
        return t
    end
    function HOUND.Matrix.random( mtx,start,stop,idp )
        local start,stop,idp = start or -10,stop or 10,idp or 1
        local ffill = HOUND.Matrix.type( mtx ) == "number" and numfill or tfill
        for i = 1,#mtx do
            for j = 1,#mtx[1] do
                mtx[i][j] = ffill( mtx[i][j], start, stop, idp )
            end
        end
        return mtx
    end

    function HOUND.Matrix.type( mtx )
        local e = mtx[1][1]
        if type(e) == "table" then
            if e.type then
                return e:type()
            end
            return "tensor"
        end
        return "number"
    end

    local num_copy = function( num )
        return num
    end
    local t_copy = function( t )
        local newt = setmetatable( {}, getmetatable( t ) )
        for i,v in ipairs( t ) do
            newt[i] = v
        end
        return newt
    end

    function HOUND.Matrix.copy( m1 )
        local docopy = HOUND.Matrix.type( m1 ) == "number" and num_copy or t_copy
        local mtx = {}
        for i = 1,#m1[1] do
            mtx[i] = {}
            for j = 1,#m1 do
                mtx[i][j] = docopy( m1[i][j] )
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.transpose( m1 )
        local docopy = HOUND.Matrix.type( m1 ) == "number" and num_copy or t_copy
        local mtx = {}
        for i = 1,#m1[1] do
            mtx[i] = {}
            for j = 1,#m1 do
                mtx[i][j] = docopy( m1[j][i] )
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.subm( m1,i1,j1,i2,j2 )
        local docopy = HOUND.Matrix.type( m1 ) == "number" and num_copy or t_copy
        local mtx = {}
        for i = i1,i2 do
            local _i = i-i1+1
            mtx[_i] = {}
            for j = j1,j2 do
                local _j = j-j1+1
                mtx[_i][_j] = docopy( m1[i][j] )
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.concath( m1,m2 )
        assert(#m1 == #m2, "matrix size mismatch")
        local docopy = HOUND.Matrix.type( m1 ) == "number" and num_copy or t_copy
        local mtx = {}
        local offset = #m1[1]
        for i = 1,#m1 do
            mtx[i] = {}
            for j = 1,offset do
                mtx[i][j] = docopy( m1[i][j] )
            end
            for j = 1,#m2[1] do
                mtx[i][j+offset] = docopy( m2[i][j] )
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.concatv( m1,m2 )
        assert(#m1[1] == #m2[1], "matrix size mismatch")
        local docopy = HOUND.Matrix.type( m1 ) == "number" and num_copy or t_copy
        local mtx = {}
        for i = 1,#m1 do
            mtx[i] = {}
            for j = 1,#m1[1] do
                mtx[i][j] = docopy( m1[i][j] )
            end
        end
        local offset = #mtx
        for i = 1,#m2 do
            local _i = i + offset
            mtx[_i] = {}
            for j = 1,#m2[1] do
                mtx[_i][j] = docopy( m2[i][j] )
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.rotl( m1 )
        local mtx = HOUND.Matrix:new( #m1[1],#m1 )
        local docopy = HOUND.Matrix.type( m1 ) == "number" and num_copy or t_copy
        for i = 1,#m1 do
            for j = 1,#m1[1] do
                mtx[#m1[1]-j+1][i] = docopy( m1[i][j] )
            end
        end
        return mtx
    end

    function HOUND.Matrix.rotr( m1 )
        local mtx = HOUND.Matrix:new( #m1[1],#m1 )
        local docopy = HOUND.Matrix.type( m1 ) == "number" and num_copy or t_copy
        for i = 1,#m1 do
            for j = 1,#m1[1] do
                mtx[j][#m1-i+1] = docopy( m1[i][j] )
            end
        end
        return mtx
    end

    local function tensor_tostring( t,fstr )
        if not fstr then return "["..table.concat(t,",").."]" end
        local tval = {}
        for i,v in ipairs( t ) do
            tval[i] = string.format( fstr,v )
        end
        return "["..table.concat(tval,",").."]"
    end
    local function number_tostring( e,fstr )
        return fstr and string.format( fstr,e ) or e
    end

    function HOUND.Matrix.tostring( mtx, formatstr )
        local ts = {}
        local mtype = HOUND.Matrix.type( mtx )
        local e = mtx[1][1]
        local tostring = mtype == "tensor" and tensor_tostring or
              type(e) == "table" and e.tostring or number_tostring
        for i = 1,#mtx do
            local tstr = {}
            for j = 1,#mtx[1] do
                tstr[j] = tostring(mtx[i][j],formatstr)
            end
            ts[i] = table.concat(tstr, "\t")
        end
        return table.concat(ts, "\n")
    end

    function HOUND.Matrix.latex( mtx, align )
        local align = align or "c"
        local str = "$\\left( \\begin{array}{"..string.rep( align, #mtx[1] ).."}\n"
        local getstr = HOUND.Matrix.type( mtx ) == "tensor" and tensor_tostring or number_tostring
        for i = 1,#mtx do
            str = str.."\t"..getstr(mtx[i][1])
            for j = 2,#mtx[1] do
                str = str.." & "..getstr(mtx[i][j])
            end
            if i == #mtx then
                str = str.."\n"
            else
                str = str.." \\\\\n"
            end
        end
        return str.."\\end{array} \\right)$"
    end

    function HOUND.Matrix.rows( mtx )
        return #mtx
    end

    function HOUND.Matrix.columns( mtx )
        return #mtx[1]
    end

    function HOUND.Matrix.size( mtx )
        if HOUND.Matrix.type( mtx ) == "tensor" then
            return #mtx,#mtx[1],#mtx[1][1]
        end
        return #mtx,#mtx[1]
    end

    function HOUND.Matrix.getelement( mtx,i,j )
        if mtx[i] and mtx[i][j] then
            return mtx[i][j]
        end
    end

    function HOUND.Matrix.setelement( mtx,i,j,value )
        if HOUND.Matrix.getelement( mtx,i,j ) then
            mtx[i][j] = value
            return 1
        end
    end

    function HOUND.Matrix.ipairs( mtx )
        local i,j,rows,columns = 1,0,#mtx,#mtx[1]
        local function iter()
            j = j + 1
            if j > columns then -- return first element from next row
                i,j = i + 1,1
            end
            if i <= rows then
                return i,j
            end
        end
        return iter
    end

    function HOUND.Matrix.scalar( m1, m2 )
        return m1[1][1]*m2[1][1] + m1[2][1]*m2[2][1] +  m1[3][1]*m2[3][1]
    end

    function HOUND.Matrix.cross( m1, m2 )
        local mtx = {}
        mtx[1] = { m1[2][1]*m2[3][1] - m1[3][1]*m2[2][1] }
        mtx[2] = { m1[3][1]*m2[1][1] - m1[1][1]*m2[3][1] }
        mtx[3] = { m1[1][1]*m2[2][1] - m1[2][1]*m2[1][1] }
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.len( m1 )
        return math.sqrt( m1[1][1]^2 + m1[2][1]^2 + m1[3][1]^2 )
    end

    function HOUND.Matrix.replace( m1, func, ... )
        local mtx = {}
        for i = 1,#m1 do
            local m1i = m1[i]
            local mtxi = {}
            for j = 1,#m1i do
                mtxi[j] = func( m1i[j], ... )
            end
            mtx[i] = mtxi
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    function HOUND.Matrix.elementstostrings( mtx )
        local e = mtx[1][1]
        local tostring = type(e) == "table" and e.tostring or tostring
        return HOUND.Matrix.replace(mtx, tostring)
    end

    function HOUND.Matrix.solve( m1 )
        assert( HOUND.Matrix.type( m1 ) == "symbol", "matrix not of type 'symbol'" )
        local mtx = {}
        for i = 1,#m1 do
            mtx[i] = {}
            for j = 1,#m1[1] do
                mtx[i][j] = tonumber( loadstring( "return "..m1[i][j][1] )() )
            end
        end
        return setmetatable( mtx, HOUND.Matrix )
    end

    HOUND.Matrix.__add = function( ... )
        return HOUND.Matrix.add( ... )
    end

    HOUND.Matrix.__sub = function( ... )
        return HOUND.Matrix.sub( ... )
    end

    HOUND.Matrix.__mul = function( m1,m2 )
        if getmetatable( m1 ) ~= HOUND.Matrix then
            return HOUND.Matrix.mulnum( m2,m1 )
        elseif getmetatable( m2 ) ~= HOUND.Matrix then
            return HOUND.Matrix.mulnum( m1,m2 )
        end
        return HOUND.Matrix.mul( m1,m2 )
    end

    HOUND.Matrix.__div = function( m1,m2 )
        if getmetatable( m1 ) ~= HOUND.Matrix then
            return HOUND.Matrix.mulnum( HOUND.Matrix.invert(m2),m1 )
        elseif getmetatable( m2 ) ~= HOUND.Matrix then
            return HOUND.Matrix.divnum( m1,m2 )
        end
        return HOUND.Matrix.div( m1,m2 )
    end

    HOUND.Matrix.__unm = function( mtx )
        return HOUND.Matrix.mulnum( mtx,-1 )
    end

        local option = {
            ["*"] = function( m1 ) return HOUND.Matrix.conjugate( m1 ) end,
            ["T"] = function( m1 ) return HOUND.Matrix.transpose( m1 ) end,
        }
    HOUND.Matrix.__pow = function( m1, opt )
        return option[opt] and option[opt]( m1 ) or HOUND.Matrix.pow( m1,opt )
    end

    HOUND.Matrix.__eq = function( m1, m2 )
        if HOUND.Matrix.type( m1 ) ~= HOUND.Matrix.type( m2 ) then
            return false
        end
        if #m1 ~= #m2 or #m1[1] ~= #m2[1] then
            return false
        end
        for i = 1,#m1 do
            for j = 1,#m1[1] do
                if m1[i][j] ~= m2[i][j] then
                    return false
                end
            end
        end
        return true
    end

end--- Hound databases

do
    HOUND.DB = {}

    HOUND.DB.PHONETICS =  {
        ['A'] = "Alpha",
        ['B'] = "Bravo",
        ['C'] = "Charlie",
        ['D'] = "Delta",
        ['E'] = "Echo",
        ['F'] = "Foxtrot",
        ['G'] = "Golf",
        ['H'] = "Hotel",
        ['I'] = "India",
        ['J'] = "Juliette",
        ['K'] = "Kilo",
        ['L'] = "Lima",
        ['M'] = "Mike",
        ['N'] = "November",
        ['O'] = "Oscar",
        ['P'] = "Papa",
        ['Q'] = "Quebec",
        ['R'] = "Romeo",
        ['S'] = "Sierra",
        ['T'] = "Tango",
        ['U'] = "Uniform",
        ['V'] = "Victor",
        ['W'] = "Whiskey",
        ['X'] = "X ray",
        ['Y'] = "Yankee",
        ['Z'] = "Zulu",
        ['1'] = "One",
        ['2'] = "Two",
        ['3'] = "Three",
        ['4'] = "Four",
        ['5'] = "Five",
        ['6'] = "Six",
        ['7'] = "Seven",
        ['8'] = "Eight",
        ['9'] = "Niner",
        ['0'] = "Zero",
        [' '] = ",",
        ['.'] = "Decimal"
    }

    HOUND.DB.useDMM =  {
        ['F-16C_blk50'] = true,
        ['F-16C_50'] = true,
        ['M-2000C'] = true,
        ['A-10C'] = true,
        ['A-10C_2'] = true,
        ['AH-64D_BLK_II'] = true,
        ['F-15ESE'] = true,
        ['OH58D'] = true,
        ['OH-58D'] = true
    }

    HOUND.DB.useMGRS = {
        ['A-10C'] = true,
        ['A-10C_2'] = true,
        ['AH-64D_BLK_II'] = true,
        ['OH58D'] = true,
        ['OH-58D'] = true
    }

    HOUND.DB.Bands = {
        ["A"] = {1.199170,8.793912},
        ["B"] = {0.599585,0.599585},
        ["C"] = {0.299792,0.299792},
        ["D"] = {0.149896,0.149896},
        ["E"] = {0.099931,0.049965},
        ["F"] = {0.074948,0.024983},
        ["G"] = {0.049965,0.024983},
        ["H"] = {0.037474,0.012491},
        ["I"] = {0.029979,0.007495},
        ["J"] = {0.014990,0.014990},
        ["K"] = {0.007495,0.007495},
        ["L"] = {0.004997,0.002498},
        ["M"] = {0.002998,0.001999},
    }

    HOUND.DB.RadarType = {
        ['NONE'] = 0x00,
        ['EWR'] = 0x01,
        ['RANGEFINDER'] = 0x02,
        ['ANTISHIP'] = 0x04,
        ['SEARCH'] = 0x08,
        ['TRACK'] = 0x10,
        ['NAVAL'] = 0x20
    }

    HOUND.DB.CALLSIGNS = {
        NATO = {
            "ABLOW", "ACTON", "AGRAM", "AMINO", "AWOKE", "BARB", "BART", "BAZOO",
            "BOGUE", "BOOT", "BRAY", "CAMAY", "CAPON", "CASEY", "CHIME", "CHISUM",
            "COBRA", "COSMO", "CRISP", "DAGDA", "DALLY", "DEVON", "DIVE", "DOZER",
            "DUPLE", "EXOR", "EXUDE", "EXULT", "FLOSS", "FLOUT", "FLUKY", "FURR",
            "GENUS", "GOBO", "GOLLY", "GOOFY", "GROUP", "HAKE", "HARMO",
            "HERMA", "HEXAD", "HOLE", "HURDS", "HYMN", "IOTA", "JOSS", "KELT", "LARVA",
            "LUMPY", "MAFIA", "MINE", "MORTY", "MURKY", "NEVIN", "NEWLY", "NORTH",
            "OLIVE", "ORKIN", "PARRY", "PATIO", "PATSY", "PATTY", "PERMA", "PITTS",
            "POKER", "POOK", "PRIME", "PYTHON", "RAGU", "REMUS", "RINGY", "RITZ",
            "RIVET", "ROSE", "RULE", "RUNNY", "SAME", "SAVOY", "SCENT",
            "SCROW", "SEAT", "SLAG", "SLOG", "SNOOP", "SPRY", "STINT", "STOB", "TAKE",
            "TALLY", "TAPE", "TOLL", "TONUS", "TOPCAT", "TORA", "TOTTY", "TOXIC",
            "TRIAL", "TRYST", "VALVO", "VEIN", "VELA", "VETCH", "VINE", "VULCAN",
            "WATT", "WORTH", "ZEPEL", "ZIPPY"
        },
        GENERIC = {
            "VACUUM", "HOOVER", "KIRBY","ROOMBA","DYSON","SHERLOCK","WATSON","GADGET",
            "HORATIO","CAINE","CHRISTIE","BENSON","GIBBS","COLOMBO","HOLT","DIAZ",
            "SCULLY","MULDER","MARVIN","MARS","MORNINGSTAR","STEELE","CASTEL","BECKETT",
            "INDIANA","JONES","LARA","CROFT","VENTURA","SCOOBY","SHAGGY"
        }
    }

    HOUND.DB.HumanUnits = {
        byName = {
            [coalition.side.NEUTRAL] = {},
            [coalition.side.RED] = {},
            [coalition.side.BLUE] = {}
        },
        byGid = {
            [coalition.side.NEUTRAL] = {},
            [coalition.side.RED] = {},
            [coalition.side.BLUE] = {}
        }
    }
end
do
    HOUND.DB.Radars = {
        ['1L13 EWR'] = {
            ['Name'] = "Box Spring",
            ['Assigned'] = { "EWR" },
            ['Role'] = { HOUND.DB.RadarType.EWR },
            ['Band'] = {
                [true] = { 1.362693, 0.302821 },
                [false] = { 1.362693, 0.302821 },
            },
            ['Primary'] = false
        },
        ['55G6 EWR'] = {
            ['Name'] = "Tall Rack",
            ['Assigned'] = { "EWR" },
            ['Role'] = { HOUND.DB.RadarType.EWR },
            ['Band'] = {
                [true] = { 0.999308, 8.993774 },
                [false] = { 0.999308, 8.993774 }
            },
            ['Primary'] = false
        },
        ['FPS-117'] = {
            ['Name'] = "Seek Igloo",
            ['Assigned'] = { "EWR" },
            ['Role'] = { HOUND.DB.RadarType.EWR },
            ['Band'] = {
                [true] = { 0.214137, 0.032605 },
                [false] = { 0.214137, 0.032605 },
            },
            ['Primary'] = false
        },
        ['FPS-117 Dome'] = {
            ['Name'] = "Seek Igloo",
            ['Assigned'] = { "EWR" },
            ['Role'] = { HOUND.DB.RadarType.EWR },
            ['Band'] = {
                [true] = { 0.214137, 0.032605 },
                [false] = { 0.214137, 0.032605 },
            },
            ['Primary'] = false
        },
        ['p-19 s-125 sr'] = {
            ['Name'] = "Flat Face",
            ['Assigned'] = { "SA-2", "SA-3" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = { 0.342620, 0.018576 },
                [false] = { 0.342620, 0.018576 }
            },
            ['Primary'] = false
        },
        ['SNR_75V'] = {
            ['Name'] = "Fan-song",
            ['Assigned'] = { "SA-2" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.058898, 0.002159 },
                [false] = { 0.058898, 0.000940 }
            },
            ['Primary'] = true
        },
        ['RD_75'] = {
            ['Name'] = "Amazonka",
            ['Assigned'] = { "SA-2" },
            ['Role'] = { HOUND.DB.RadarType.RANGEFINDER },
            ['Band'] = {
                [true] = HOUND.DB.Bands.G,
                [false] = HOUND.DB.Bands.G
            },
            ['Primary'] = false
        },
        ['snr s-125 tr'] = {
            ['Name'] = "Low Blow",
            ['Assigned'] = { "SA-3" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.031893, 0.001417 },
                [false] = { 0.031893, 0.001417 }
            },
            ['Primary'] = true
        },
        ['Kub 1S91 str'] = {
            ['Name'] = "Straight Flush",
            ['Assigned'] = { "SA-6" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = HOUND.DB.Bands.I,
                [false] = HOUND.DB.Bands.C
            },
            ['Primary'] = true
        },
        ['Osa 9A33 ln'] = {
            ['Name'] = "Osa",
            ['Assigned'] = { "SA-8" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.020256, 0.000856 },
                [false] = HOUND.DB.Bands.C
            },
            ['Primary'] = true
        },
        ['S-300PS 40B6MD sr'] = {
            ['Name'] = "Clam Shell",
            ['Assigned'] = { "SA-10" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = { 0.090846, 0.012531 },
                [false] = { 0.090846, 0.012531 }
            },
            ['Primary'] = false
        },
        ['S-300PS 64H6E sr'] = {
            ['Name'] = "Big Bird",
            ['Assigned'] = { "SA-10" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = { 0.090846, 0.012531 },
                [false] = { 0.090846, 0.012531 }
            },
            ['Primary'] = false
        },
        ['RLS_19J6'] = {
            ['Name'] = "Tin Shield",
            ['Assigned'] = { "SA-5" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = { 0.093685, 0.011505 },
                [false] = { 0.093685, 0.011505 }
            },
            ['Primary'] = false
        },
        ['S-300PS 40B6MD sr_19J6'] = {
            ['Name'] = "Tin Shield",
            ['Assigned'] = { "SA-10" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = { 0.093685, 0.011505 },
                [false] = { 0.093685, 0.011505 }
            },
            ['Primary'] = false
        },
        ['S-300PS 40B6M tr'] = {
            ['Name'] = "Tomb Stone",
            ['Assigned'] = { "SA-10" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.014990, 0.022484 },
                [false] = { 0.014990, 0.022484 }
            },
            ['Primary'] = true
        },
        ['S-300PS 5H63C 30H6_tr'] = {
            ['Name'] = "Flap Lid",
            ['Assigned'] = { "SA-10" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.014990, 0.022484 },
                [false] = { 0.014990, 0.022484 }
            },
            ['Primary'] = true
        },
        ['SA-11 Buk SR 9S18M1'] = {
            ['Name'] = "Snow Drift",
            ['Assigned'] = { "SA-11" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = { 0.033310, 0.016655 },
                [false] = HOUND.DB.Bands.F
            },
            ['Primary'] = true
        },
        ['SA-11 Buk LN 9A310M1'] = {
            ['Name'] = "Fire Dome",
            ['Assigned'] = { "SA-11" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.033310, 0.016655 },
                [false] = { 0.029979, 0.019986 }
            },
            ['Primary'] = false
        },
        ['Tor 9A331'] = {
            ['Name'] = "Tor",
            ['Assigned'] = { "SA-15" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = {0.037474,0.037474}, -- G+H
                [false] = HOUND.DB.Bands.F
            },
            ['Primary'] = true
        },
        ['Strela-1 9P31'] = {
            ['Name'] = "SA-9",
            ['Assigned'] = { "Strela" },
            ['Role'] = { HOUND.DB.RadarType.RANGEFINDER },
            ['Band'] = {
                [true] = HOUND.DB.Bands.K,
                [false] = HOUND.DB.Bands.K
            },
            ['Primary'] = false
        },
        ['Strela-10M3'] = {
            ['Name'] = "SA-13",
            ['Assigned'] = { "Strela" },
            ['Role'] = { HOUND.DB.RadarType.RANGEFINDER },
            ['Band'] = {
                [true] = HOUND.DB.Bands.J,
                [false] = HOUND.DB.Bands.J
            },
            ['Primary'] = false
        },
        ['Patriot str'] = {
            ['Name'] = "Patriot",
            ['Assigned'] = { "Patriot" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.055008, 0.011910 },
                [false] = { 0.055008, 0.011910 }
            },
            ['Primary'] = true
        },
        ['Hawk sr'] = {
            ['Name'] = "Hawk SR",
            ['Assigned'] = { "Hawk" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = HOUND.DB.Bands.C,
                [false] = HOUND.DB.Bands.C
            },
            ['Primary'] = false
        },
        ['Hawk tr'] = {
            ['Name'] = "Hawk TR",
            ['Assigned'] = { "Hawk" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['Hawk cwar'] = {
            ['Name'] = "Hawk CWAR",
            ['Assigned'] = { "Hawk" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = HOUND.DB.Bands.J,
                [false] = HOUND.DB.Bands.J
            },
            ['Primary'] = false
        },
        ['RPC_5N62V'] = {
            ['Name'] = "Square Pair",
            ['Assigned'] = { "SA-5" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = {0.044087,0.002755},
                [false] = {0.044087,0.002755}
            },
            ['Primary'] = true
        },
        ['Roland ADS'] = {
            ['Name'] = "Roland TR",
            ['Assigned'] = { "Roland" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = HOUND.DB.Bands.D
            },
            ['Primary'] = true
        },
        ['Roland Radar'] = {
            ['Name'] = "Roland SR",
            ['Assigned'] = { "Roland" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = HOUND.DB.Bands.D,
                [false] = HOUND.DB.Bands.D
            },
            ['Primary'] = false
        },
        ['Gepard'] = {
            ['Name'] = "Gepard",
            ['Assigned'] = { "Gepard" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = HOUND.DB.Bands.J,
                [false] = HOUND.DB.Bands.E
            },
            ['Primary'] = true
        },
        ['rapier_fsa_blindfire_radar'] = {
            ['Name'] = "Rapier",
            ['Assigned'] = { "Rapier" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = HOUND.DB.Bands.F,
                [false] = HOUND.DB.Bands.F
            },
            ['Primary'] = true
        },
        ['rapier_fsa_launcher'] = {
            ['Name'] = "Rapier",
            ['Assigned'] = { "Rapier" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.074948, 0.224844 },
                [false] = { 0.074948, 0.224844 }
            },
            ['Primary'] = false
        },
        ['NASAMS_Radar_MPQ64F1'] = {
            ['Name'] = "Sentinel",
            ['Assigned'] = { "NASAMS" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['HQ-7_STR_SP'] = {
            ['Name'] = "HQ-7",
            ['Assigned'] = { "HQ-7" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = {0.516884,0.082701},
                [false] = HOUND.DB.Bands.E
            },
            ['Primary'] = false
        },
        ['HQ-7_LN_SP'] = {
            ['Name'] = "HQ-7",
            ['Assigned'] = { "HQ-7" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = HOUND.DB.Bands.J,
                [false] = HOUND.DB.Bands.E
            },
            ['Primary'] = true
        },
        ['HQ-7_LN_P'] = {
            ['Name'] = "HQ-7",
            ['Assigned'] = { "HQ-7" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = HOUND.DB.Bands.E,
                [false] = HOUND.DB.Bands.E
            },
            ['Primary'] = true
        },
        ['2S6 Tunguska'] = {
            ['Name'] = "Tunguska",
            ['Assigned'] = { "Tunguska" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = HOUND.DB.Bands.J,
                [false] = HOUND.DB.Bands.E
            },
            ['Primary'] = true
        },
        ['ZSU-23-4 Shilka'] = {
            ['Name'] = "Shilka",
            ['Assigned'] = { "AAA" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.019217, 0.001316 },
                [false] = { 0.019217, 0.001316 }
            },
            ['Primary'] = true
        },
        ['HEMTT_C-RAM_Phalanx'] = {
            ['Name'] = "Phalanx C-RAM",
            ['Assigned'] = { "AAA" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.016655, 0.008328 },
                [false] = { 0.016655, 0.008328 }
            },
            ['Primary'] = true
        },
        ['Dog Ear radar'] = {
            ['Name'] = "Dog Ear",
            ['Assigned'] = { "AAA" },
            ['Role'] = { HOUND.DB.RadarType.SEARCH },
            ['Band'] = {
                [true] = { 0.049965, 0.049965 },
                [false] = { 0.049965, 0.049965 }
            },
            ['Primary'] = true
        },
        ['SON_9'] = {
            ['Name'] = "Fire Can",
            ['Assigned'] = { "AAA" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.103377, 0.007658 },
                [false] = { 0.103377, 0.007658 }
            },
            ['Primary'] = true
        },
        ['Silkworm_SR'] = {
            ['Name'] = "Silkworm",
            ['Assigned'] = { "Silkworm" },
            ['Role'] = { HOUND.DB.RadarType.ANTISHIP },
            ['Band'] = {
                [true] = HOUND.DB.Bands.K,
                [false] = HOUND.DB.Bands.K
            },
            ['Primary'] = true
        },
        ['FuSe-65'] = {
            ['Name'] = "Würzburg",
            ['Assigned'] = { "AAA" },
            ['Role'] = { HOUND.DB.RadarType.TRACK },
            ['Band'] = {
                [true] = { 0.535344, 0.000000 },
                [false] = { 0.535344, 0.000000 }
            },
            ['Primary'] = false
        },
        ['FuMG-401'] = {
            ['Name'] = "EWR",
            ['Assigned'] = { "EWR" },
            ['Role'] = { HOUND.DB.RadarType.EWR },
            ['Band'] = {
                [true] = { 2.306096, 0.192175 },
                [false] = { 2.306096, 0.192175 }
            },
            ['Primary'] = false
        },
        ['Flakscheinwerfer_37'] = {
            ['Name'] = "AAA Searchlight",
            ['Assigned'] = { "AAA" },
            ['Role'] = { HOUND.DB.RadarType.NONE },
            ['Band'] = {
                [true] = HOUND.DB.Bands.L,
                [false] = HOUND.DB.Bands.L
            },
            ['Primary'] = false
        },
        ['Type_052B'] = {
            ['Name'] = "Luyang-1 (DD)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = {0.033310,0.016655},
                [false] = HOUND.DB.Bands.F
            },
            ['Primary'] = true
        },
        ['Type_052C'] = {
            ['Name'] = "Luyang-2 (DD)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['Type_054A'] = {
            ['Name'] = "Jiangkai (FF)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = {0.033310,0.016655},
                [false] = HOUND.DB.Bands.F
            },
            ['Primary'] = true
        },

        ['Type_093'] = {
            ['Name'] = "Shang Submarine",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = HOUND.DB.Bands.E,
                [false] = HOUND.DB.Bands.E
            },
            ['Primary'] = true
        },
        ['USS_Arleigh_Burke_IIa'] = {
            ['Name'] = "Arleigh Burke (DD)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.318251, 0.034446 }
            },
            ['Primary'] = true
        },
        ['CV_1143_5'] = {
            ['Name'] = "Kuznetsov (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['KUZNECOW'] = {
            ['Name'] = "Kuznetsov (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['Forrestal'] = {
            ['Name'] = "Forrestal (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.516884, 0.082701 }
            },
            ['Primary'] = true
        },
        ['VINSON'] = {
            ['Name'] = "Nimitz (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.318251, 0.034446 },
                [false] = { 0.318251, 0.034446 }
            },
            ['Primary'] = true
        },
        ['CVN_71'] = {
            ['Name'] = "Nimitz (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.318251, 0.034446 }
            },
            ['Primary'] = true
        },
        ['CVN_72'] = {
            ['Name'] = "Nimitz (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.318251, 0.034446 }
            },
            ['Primary'] = true
        },
        ['CVN_73'] = {
            ['Name'] = "Nimitz (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.318251, 0.034446 }
            },
            ['Primary'] = true
        },
        ['Stennis'] = {
            ['Name'] = "Nimitz (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.318251, 0.034446 }
            },
            ['Primary'] = true
        },
        ['CVN_75'] = {
            ['Name'] = "Nimitz (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.318251, 0.034446 }
            },
            ['Primary'] = true
        },
        ['La_Combattante_II'] = {
            ['Name'] = "La Combattante (FC)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = {0.024983,0.012491},
                [false] = {0.049965,0.007687}
            },
            ['Primary'] = true
        },
        ['ALBATROS'] = {
            ['Name'] = "Grisha (FC)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = HOUND.DB.Bands.H,
                [false] = HOUND.DB.Bands.H
            },
            ['Primary'] = true
        },
        ['MOLNIYA'] = {
            ['Name'] = "Molniya (FC)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['MOSCOW'] = {
            ['Name'] = "Moskva (CG)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['NEUSTRASH'] = {
            ['Name'] = "Neustrashimy (DD)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['PERRY'] = {
            ['Name'] = "Oliver H. Perry (FF)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.028552, 0.000696 },
                [false] = { 0.028552, 0.000696 }
            },
            ['Primary'] = true
        },
        ['PIOTR'] = {
            ['Name'] = "Kirov (CG)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.024983, 0.012491 }
            },
            ['Primary'] = true
        },
        ['REZKY'] = {
            ['Name'] = "Krivak (FF)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = HOUND.DB.Bands.H,
                [false] = HOUND.DB.Bands.H
            },
            ['Primary'] = true
        },
        ['LHA_Tarawa'] = {
            ['Name'] = "Tarawa (LHA)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.516884, 0.082701 }
            },
            ['Primary'] = true
        },
        ['TICONDEROG'] = {
            ['Name'] = "Ticonderoga (CG)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.024983, 0.012491 },
                [false] = { 0.318251, 0.034446 }
            },
            ['Primary'] = true
        },
        ['hms_invincible'] = {
            ['Name'] = "Invincible (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.516884, 0.082701 }
            },
            ['Primary'] = true
        },
        ['leander-gun-achilles'] = {
            ['Name'] = "Leander (FF)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.516884, 0.082701 }
            },
            ['Primary'] = true
        },
        ['leander-gun-andromeda'] = {
            ['Name'] = "Leander (FF)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.516884, 0.082701 }
            },
            ['Primary'] = true
        },
        ['leander-gun-ariadne'] = {
            ['Name'] = "Leander (FF)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.516884, 0.082701 }
            },
            ['Primary'] = true
        },
        ['leander-gun-condell'] = {
            ['Name'] = "Condell (FF)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.516884, 0.082701 }
            },
            ['Primary'] = true
        },
        ['leander-gun-lynch'] = {
            ['Name'] = "Condell (FF)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.516884, 0.082701 }
            },
            ['Primary'] = true
        },
        ['ara_vdm'] = {
            ['Name'] = "Veinticinco de Mayo (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = { 0.516884, 0.082701 },
                [false] = { 0.136269, 0.013627 }
            },
            ['Primary'] = true
        },
        ['santafe'] = {
        ['Name'] = "Balao Class (SS)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = {0.136269,0.013627},
                [false] = {0.136269,0.013627}
        },
        ['Primary'] = true
        },
        ['Essex'] = {
            ['Name'] = "Essex (CV)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                    [true] = {0.516884,0.082701},
                    [false] = {0.136269,0.013627}
            },
            ['Primary'] = true
        },
        ['BDK-775'] = {
            ['Name'] = "Ropucha (LS)",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = HOUND.DB.Bands.E,
                [false] = HOUND.DB.Bands.E
            },
            ['Primary'] = true
        },
        ['Type_071'] = {
            ['Name'] = "Yuzhao transport",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = HOUND.DB.Bands.E,
                [false] = HOUND.DB.Bands.E
            },
            ['Primary'] = true
        },
        ['atconveyor'] = {
            ['Name'] = "SS Atlantic Conveyor",
            ['Assigned'] = { "Naval" },
            ['Role'] = { HOUND.DB.RadarType.NAVAL },
            ['Band'] = {
                [true] = HOUND.DB.Bands.D,
                [false] = HOUND.DB.Bands.D
            },
            ['Primary'] = true
        },
    }

    HOUND.DB.Radars['CHAP_IRISTSLM_STR'] = {
        ['Name'] = "IRIS-T",
        ['Assigned'] = { "SHORAD" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
                [true] = {0.049965,0.024983},
                [false] = {0.049965,0.024983}
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CHAP_PantsirS1'] = {
        ['Name'] = "Pantsir",
        ['Assigned'] = {"SHORAD"},
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
                [true] = {0.016655,0.020819},
                [false] = {0.074948,0.074948}
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CHAP_TorM2'] = {
        ['Name'] = "Tor",
        ['Assigned'] = { "SA-15" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
                [true] = {0.011103,0.005552},
                [false] = {0.074948,0.074948}
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CHAP_Project22160'] = {
        ['Name'] = "Project 22160 (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
                [true] = {0.024983,0.012491},
                [false] = {0.024983,0.012491}
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CHAP_Project22160_TorM2KM'] = {
        ['Name'] = "Project 22160 (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
                [true] = {0.011103,0.005552},
                [false] = {0.024983,0.012491}
        },
        ['Primary'] = true
    }

    HOUND.DB.Platform = {
        [Object.Category.STATIC] = {
            ['Comms tower M']   = { antenna = { size = 107, factor = 1 }, ins_error = 0 },
            ['.Command Center'] = { antenna = { size = 62, factor = 1 }, ins_error = 0 },
            ['Cow']             = { antenna = { size = 1000, factor = 10 }, ins_error = 0 },
            ['TV tower']        = { antenna = { size = 235, factor = 1 }, ins_error = 0 },
        },
        [Object.Category.UNIT] = {
            ['Patriot AMG'] = { antenna = { size = 15, factor = 1 }, ins_error = 0 },
            ['SPK-11'] = { antenna = { size = 15, factor = 1 }, ins_error = 0 },
            ['CH-47D'] = { antenna = { size = 12, factor = 1 }, ins_error = 0 },
            ['CH-53E'] = { antenna = { size = 10, factor = 1 }, ins_error = 0 },
            ['MIL-26'] = { antenna = { size = 20, factor = 1 }, ins_error = 50 },
            ['SH-60B'] = { antenna = { size = 8, factor = 1 }, ins_error = 0 },
            ['UH-60A'] = { antenna = { size = 8, factor = 1 }, ins_error = 0 },
            ['Mi-8MT'] = { antenna = { size = 8, factor = 1 }, ins_error = 0 },
            ['UH-1H'] = { antenna = { size = 4, factor = 1 }, ins_error = 50 },
            ['KA-27'] = { antenna = { size = 4, factor = 1 }, ins_error = 50 },
            ['C-130'] = { antenna = { size = 35, factor = 1 }, ins_error = 0 },
            ['C-17A'] = { antenna = { size = 40, factor = 1 }, ins_error = 0 }, -- stand-in for RC-135, tuned antenna size to match
            ['S-3B'] = { antenna = { size = 18, factor = 0.8 }, ins_error = 0 },
            ['E-3A'] = { antenna = { size = 9, factor = 0.5 }, ins_error = 0 },
            ['E-2C'] = { antenna = { size = 7, factor = 0.5 }, ins_error = 0 },
            ['Tu-95MS'] = { antenna = { size = 50, factor = 1 }, ins_error = 50 },
            ['Tu-142'] = { antenna = { size = 50, factor = 1 }, ins_error = 0 },
            ['IL-76MD'] = { antenna = { size = 48, factor = 0.8 }, ins_error = 50 },
            ['H-6J'] = { antenna = { size = 3.5, factor = 1 }, require = { Payload = {'PHANTASM'} }, ins_error = 100 },
            ['Su-24M'] = { antenna = { size = 3.5, factor = 1 }, require = { Payload = {'PHANTASM'} }, ins_error = 50 },
            ['Su-24MR'] = { antenna = { size = 4.5, factor = 1 }, require = { Payload = {'TANGAZH'} }, ins_error = 50 },
            ['Su-25TM'] = { antenna = { size = 3.5, factor = 1 }, require = { Payload = {'PHANTASM'} }, ins_error = 50 },
            ['An-30M'] = { antenna = { size = 25, factor = 1 }, ins_error = 50 },
            ['A-50'] = { antenna = { size = 9, factor = 0.5 }, ins_error = 0 },
            ['An-26B'] = { antenna = { size = 26, factor = 1 }, ins_error = 100 },
            ['C-47'] = { antenna = { size = 12, factor = 1 }, ins_error = 100 },
            ['Su-25T'] = { antenna = { size = 3.5, factor = 1 }, require = { Payload = {'PHANTASM'} }, ins_error = 50 },
            ['AJS37'] = { antenna = { size = 4.5, factor = 1 }, require = { Payload = {'U22/A Jammer Pod','U22 Jammer'} }, ins_error = 50 },
            ['F-16C_50'] = { antenna = { size = 1.45, factor = 1 }, require = { Payload = {'f-16c_hts_pod'} }, ins_error = 0 },
            ['JF-17'] = { antenna = { size = 3.25, factor = 1 }, require = { Payload = {'KG-600'} }, ins_error = 0 },
            ['Mirage-F1EE'] = { antenna = { size = 3.7, factor = 1 }, require = { Payload = {'TMV_018_Syrel_POD'} }, ins_error = 50 },  -- does not reflect features in actual released product
            ['Mirage-F1M-CE'] = { antenna = { size = 3.7, factor = 1 }, require = { Payload = {'TMV_018_Syrel_POD'} }, ins_error = 0 }, -- does not reflect features in actual released product
            ['Mirage-F1M-EE'] = { antenna = { size = 3.7, factor = 1 }, require = { Payload = {'TMV_018_Syrel_POD'} }, ins_error = 0 }, -- does not reflect features in actual released product
            ['Mirage-F1CR'] = { antenna = { size = 4, factor = 1 }, require = { Payload = {'ASTAC_POD'} }, ins_error = 0 },             -- AI only (FAF)
            ['Mirage-F1EQ'] = { antenna = { size = 3.7, factor = 1 }, require = { Payload = {'TMV_018_Syrel_POD'} }, ins_error = 50 },  -- AI only (Iraq)
            ['Mirage-F1EDA'] = { antenna = { size = 3.7, factor = 1 }, require = { Payload = {'TMV_018_Syrel_POD'} }, ins_error = 50 }, -- AI only (Qatar)
        }
    }
end
do
    HOUND.DB.Platform[Object.Category.UNIT]['UH-60L'] = { antenna = { size = 8, factor = 1 }, ins_error = 0 }      -- community UH-69L
    HOUND.DB.Platform[Object.Category.UNIT]['Hercules'] = { antenna = { size = 35, factor = 1 }, ins_error = 0 }   -- Anubis' C-130J
    HOUND.DB.Platform[Object.Category.UNIT]['EC130'] = { antenna = { size = 35, factor = 1 }, ins_error = 0 }      -- Secret Squirrel EC-130
    HOUND.DB.Platform[Object.Category.UNIT]['RC135RJ'] = { antenna = { size = 40, factor = 1 }, ins_error = 0 }    -- Secret Squirrel RC-135
    HOUND.DB.Platform[Object.Category.UNIT]['P3C_Orion'] = { antenna = { size = 25, factor = 1 }, ins_error = 0 }  -- MAM P-3C_Orion
    HOUND.DB.Platform[Object.Category.UNIT]['CLP_P8'] = { antenna = { size = 35, factor = 1 }, ins_error = 0 }     -- CLP P-8A posidon
    HOUND.DB.Platform[Object.Category.UNIT]['CLP_TU214R'] = { antenna = { size = 40, factor = 1 }, ins_error = 0 } -- CLP TU-214R
    HOUND.DB.Platform[Object.Category.UNIT]['EA_6B'] = { antenna = { size = 9, factor = 1 }, ins_error = 0 }       --VSN EA-6B
    HOUND.DB.Platform[Object.Category.UNIT]['EA-18G'] = { antenna = { size = 14, factor = 1 }, ins_error = 0 }     --CJS EF-18G
    HOUND.DB.Platform[Object.Category.UNIT]['Shavit'] = { antenna = { size = 30, factor = 1 }, ins_error = 0 }     --IDF_Mods Shavit

    HOUND.DB.Radars['S-300PS 64H6E TRAILER sr'] = {
        ['Name'] = "Big Bird",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.C,
            [false] = HOUND.DB.Bands.C
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300PS SA-10B 40B6MD MAST sr'] = {
        ['Name'] = "Clam Shell",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.I,
            [false] = HOUND.DB.Bands.I
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300PS 40B6M MAST tr'] = {
        ['Name'] = "Flap Lid",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['S-300PS 30H6 TRAILER tr'] = {
        ['Name'] = "Flap Lid",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['S-300PS 30N6 TRAILER tr'] = {
        ['Name'] = "Flap Lid",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['S-300PMU1 40B6MD sr'] = {
        ['Name'] = "Clam Shell",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.I,
            [false] = HOUND.DB.Bands.I
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300PMU1 64N6E sr'] = {
        ['Name'] = "Big Bird",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.C,
            [false] = HOUND.DB.Bands.C
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300PMU1 30N6E tr'] = {
        ['Name'] = "Flap Lid",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['S-300PMU1 40B6M tr'] = {
        ['Name'] = "Grave Stone",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['S-300V 9S15 sr'] = {
        ['Name'] = 'Bill Board',
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300V 9S19 sr'] = {
        ['Name'] = 'High Screen',
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.C,
            [false] = HOUND.DB.Bands.C
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300V 9S32 tr'] = {
        ['Name'] = 'Grill Pan',
        ['Assigned'] = { "SA-12" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['S-300PMU2 92H6E tr'] = {
        ['Name'] = 'Grave Stone',
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.I,
            [false] = HOUND.DB.Bands.I
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['S-300PMU2 64H6E2 sr'] = {
        ['Name'] = "Big Bird",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.C,
            [false] = HOUND.DB.Bands.C
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300VM 9S15M2 sr'] = {
        ['Name'] = 'Bill Board M',
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300VM 9S19M2 sr'] = {
        ['Name'] = 'High Screen M',
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.C,
            [false] = HOUND.DB.Bands.C
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['S-300VM 9S32ME tr'] = {
        ['Name'] = 'Grill Pan M',
        ['Assigned'] = { "SA-12" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.K,
            [false] = HOUND.DB.Bands.K
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['SA-17 Buk M1-2 LN 9A310M1-2'] = {
        ['Name'] = "Fire Dome M",
        ['Assigned'] = { "SA-11" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.H,
            [false] = HOUND.DB.Bands.H
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['34Ya6E Gazetchik E decoy'] = {
        ['Name'] = "Flap Lid",
        ['Assigned'] = { "SA-10" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['SAMPT_MRI_ARABEL'] = {
        ['Name'] = "SAMP/T",
        ['Assigned'] = { "SAMP/T" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.I,
            [false] = HOUND.DB.Bands.I
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['SAMPT_MRI_GF300'] = {
        ['Name'] = "SAMP/T",
        ['Assigned'] = { "SAMP/T" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.K,
            [false] = HOUND.DB.Bands.K
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Fire Can radar'] = {
        ['Name'] = "Fire Can",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['EWR 55G6U NEBO-U'] = {
        ['Name'] = "Tall Rack",
        ['Assigned'] = { "EWR" },
        ['Role'] = { HOUND.DB.RadarType.EWR },
        ['Band'] = {
            [true] = HOUND.DB.Bands.A,
            [false] = HOUND.DB.Bands.A
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['EWR P-37 BAR LOCK'] = {
        ['Name'] = "Bar lock",
        ['Assigned'] = { "EWR", "SA-5" },
        ['Role'] = { HOUND.DB.RadarType.EWR, HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['EWR 1L119 Nebo-SVU'] = {
        ['Name'] = "Box Spring",
        ['Assigned'] = { "EWR" },
        ['Role'] = { HOUND.DB.RadarType.EWR },
        ['Band'] = {
            [true] = HOUND.DB.Bands.A,
            [false] = HOUND.DB.Bands.A
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['EWR Generic radar tower'] = {
        ['Name'] = "Civilian Radar",
        ['Assigned'] = { "EWR" },
        ['Role'] = { HOUND.DB.RadarType.EWR },
        ['Band'] = {
            [true] = HOUND.DB.Bands.C,
            [false] = HOUND.DB.Bands.C
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['PantsirS1'] = {
        ['Name'] = "Pantsir",
        ['Assigned'] = { "SA-22" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['PantsirS2'] = {
        ['Name'] = "Pantsir",
        ['Assigned'] = { "SA-22" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Admiral_Kasatonov'] = {
        ['Name'] = "Gorshkov (FF)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = HOUND.DB.Bands.F,
            [false] = HOUND.DB.Bands.F
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Karakurt_AShM'] = {
        ['Name'] = "Karakurt (FS)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Karakurt_LACM'] = {
        ['Name'] = "Karakurt (FS)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['MonolitB'] = {
        ['Name'] = "Monolit B",
        ['Assigned'] = { "Bastion" },
        ['Role'] = { HOUND.DB.RadarType.ANTISHIP },
        ['Band'] = {
            [true] = HOUND.DB.Bands.I,
            [false] = HOUND.DB.Bands.I
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['TorM2'] = {
        ['Name'] = "Tor",
        ['Assigned'] = { "SA-15" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['TorM2K'] = {
        ['Name'] = "Tor",
        ['Assigned'] = { "SA-15" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['TorM2M'] = {
        ['Name'] = "Tor",
        ['Assigned'] = { "SA-15" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }

    HOUND.DB.Radars['CH_BukM3_9A317M'] = {
        ['Name'] = "Fire Dome",
        ['Assigned'] = { "SA-11" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = false

    }
    HOUND.DB.Radars['CH_BukM3_9A317MA'] = {
        ['Name'] = "Fire Dome",
        ['Assigned'] = { "SA-11" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['CH_BukM3_9S18M13'] = {
        ['Name'] = "Snow Drift",
        ['Assigned'] = { "SA-11", "SA-17", "SA-27" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.516884, 0.082701 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_BukM3_9S36M'] = {
        ['Name'] = "Buk M3",
        ['Assigned'] = { "SA-27" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['CH_S350_50N6'] = {
        ['Name'] = "S-350 STR",
        ['Assigned'] = { "SA-25" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true

    }
    HOUND.DB.Radars['CH_S350_96L6'] = {
        ['Name'] = "Cheese Board",
        ['Assigned'] = { "SA-10", "SA-23", "SA-25" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.516884, 0.082701 }
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['CH_Gremyashchiy_AShM'] = {
        ['Name'] = "Gremyashchiy Corvette",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Gremyashchiy_LACM'] = {
        ['Name'] = "Gremyashchiy Corvette",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Grigorovich_AShM'] = {
        ['Name'] = "Krivak 5 (FF)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Grigorovich_LACM'] = {
        ['Name'] = "Krivak 5 (FF)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Project22160'] = {
        ['Name'] = "Project 22160 (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true

    }
    HOUND.DB.Radars['CH_Steregushchiy'] = {
        ['Name'] = "Steregushchiy (FC)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true

    }
    HOUND.DB.Radars['Admiral_Gorshkov'] = {
        ['Name'] = "Admiral Gorshkov (FF)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }

    HOUND.DB.Radars['CH_Arleigh_Burke_IIA'] = {
        ['Name'] = "Arleigh Burke (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Arleigh_Burke_III'] = {
        ['Name'] = "Arleigh Burke (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },

        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Constellation'] = {
        ['Name'] = "[CH] Constellation Frigate",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Ticonderoga'] = {
        ['Name'] = "Ticonderoga (CG)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Ticonderoga_CMP'] = {
        ['Name'] = "Ticonderoga (CG)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Arleigh_Burke_Flight_III_AShM'] = {
        ['Name'] = "Arleigh Burke (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Arleigh_Burke_Flight_III_LACM'] = {
        ['Name'] = "Arleigh Burke (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Arleigh_Burke_Flight_III_SAM'] = {
        ['Name'] = "Arleigh Burke (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Ticonderoga_CMP_AShM'] = {
        ['Name'] = "Ticonderoga (CG)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Ticonderoga_CMP_LACM'] = {
        ['Name'] = "Ticonderoga (CG)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Ticonderoga_CMP_SAM'] = {
        ['Name'] = "Ticonderoga (CG)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['MIM104_ANMPQ65'] = {
        ['Name'] = "Patriot",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['MIM104_ANMPQ65A'] = {
        ['Name'] = "Patriot",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['MIM104_LTAMDS'] = {
        ['Name'] = "Patriot LTAMDS",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['MIM104_ANMPQ65A_HEMTT'] = {
        ['Name'] = "Patriot",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['MIM104_ANMPQ65_HEMTT'] = {
        ['Name'] = "Patriot",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }

    HOUND.DB.Radars['MIM104_LTAMDS_HEMTT'] = {
        ['Name'] = "Patriot LTAMDS",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }

    HOUND.DB.Radars['CH_NASAMS3_SR'] = {
        ['Name'] = "Sentinel",
        ['Assigned'] = { "NASAMS" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Centurion_C_RAM'] = {
        ['Name'] = "Centurion C-RAM",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }

    HOUND.DB.Radars['CH_THAAD_ANTPY2'] = {
        ['Name'] = "THAAD STR",
        ['Assigned'] = { "THAAD" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }

    HOUND.DB.Radars['Type45'] = {
        ['Name'] = "Type 45 (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Type26'] = {
        ['Name'] = "Type 26 (FF)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_SkySabreGiraffe'] = {
        ['Name'] = "Giraffe",
        ['Assigned'] = { "Sky Sabre" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['HSwMS_Visby'] = {
        ['Name'] = "Visby (FS)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['LvKv9040'] = {
        ['Name'] = "LvKv9040",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.RANGEFINDER },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['LvS-103_PM103'] = {
        ['Name'] = "Patriot",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['LvS-103_PM103_HX'] = {
        ['Name'] = "Patriot",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['RBS-90'] = {
        ['Name'] = "RBS-90",
        ['Assigned'] = { "SHORAD" },
        ['Role'] = { HOUND.DB.RadarType.RANGEFINDER },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['BV410_RBS90'] = {
        ['Name'] = "RBS-90",
        ['Assigned'] = { "SHORAD" },
        ['Role'] = { HOUND.DB.RadarType.RANGEFINDER },
        ['Band'] = {
            [true] = HOUND.DB.Bands.J,
            [false] = HOUND.DB.Bands.J
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['UndE23'] = {
        ['Name'] = "UndE23",
        ['Assigned'] = { "SHORAD" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.G,
            [false] = HOUND.DB.Bands.G
        },
        ['Primary'] = true
    }

    HOUND.DB.Radars['Strb90'] = {
        ['Name'] = "Strb 90 FAC",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = HOUND.DB.Bands.E,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Type022'] = {
        ['Name'] = "Type 022 FAC",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Type054B'] = {
        ['Name'] = "Type 054B Frigate",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Type056A'] = {
        ['Name'] = "Type 056A Corvette",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Type055'] = {
        ['Name'] = "Type 055 (CG)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['Type052D'] = {
        ['Name'] = "Type 052D (DD)",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['PGL_625'] = {
        ['Name'] = "PGL-625",
        ['Assigned'] = { "SHORAD" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['HQ17A'] = {
        ['Name'] = "HQ-17",
        ['Assigned'] = { "HQ-17" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_HQ22_SR'] = {
        ['Name'] = "HQ-22 SR",
        ['Assigned'] = { "HQ-22" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.516884, 0.082701 }
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['CH_HQ22_STR'] = {
        ['Name'] = "HQ-22 STR",
        ['Assigned'] = { "HQ-22" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_PGZ09'] = {
        ['Name'] = "PGZ-09",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_PGZ95'] = {
        ['Name'] = "PGZ-95",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_LD3000'] = {
        ['Name'] = "LD-3000 C-RAM",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_LD3000_stationary'] = {
        ['Name'] = "LD-3000 C-RAM",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_MIM104_ANMPQ53_KAT1'] = {
        ['Name'] = "Patriot",
        ['Assigned'] = { "Patriot" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_F124'] = {
        ['Name'] = "F124 Frigate",
        ['Assigned'] = { "Naval" },
        ['Role'] = { HOUND.DB.RadarType.NAVAL },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_BoxerSkyranger'] = {
        ['Name'] = "Boxer",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_FlaRakRad'] = {
        ['Name'] = "FlaRakRad",
        ['Assigned'] = { "SHORAD" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.024983, 0.012491 },
            [false] = HOUND.DB.Bands.D
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_SkynexHX'] = {
        ['Name'] = "Skynex",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.136269, 0.013627 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_Skyshield_FCU'] = {
        ['Name'] = "Skyshield",
        ['Assigned'] = { "AAA" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['CH_TRML4D'] = {
        ['Name'] = "IRIS-T",
        ['Assigned'] = { "SHORAD" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = { 0.516884, 0.082701 },
            [false] = { 0.136269, 0.013627 }
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['ELM2048_MMR'] = {
        ['Name'] = "Elta MMR",
        ['Assigned'] = { "Sling" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.F,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['ELM2084_MMR_AD_SC'] = {
        ['Name'] = "Elta MMR",
        ['Assigned'] = { "Sling" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.F,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['ELM2084_MMR_AD_RT'] = {
        ['Name'] = "Elta MMR",
        ['Assigned'] = { "Sling" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH },
        ['Band'] = {
            [true] = HOUND.DB.Bands.F,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = false
    }
    HOUND.DB.Radars['ELM2084_MMR_WLR'] = {
        ['Name'] = "Elta MMR",
        ['Assigned'] = { "Sling" },
        ['Role'] = { HOUND.DB.RadarType.SEARCH, HOUND.DB.RadarType.TRACK },
        ['Band'] = {
            [true] = HOUND.DB.Bands.F,
            [false] = HOUND.DB.Bands.E
        },
        ['Primary'] = true
    }
    HOUND.DB.Radars['EWR P-14 Tall King'] = {
        ['Name'] = "Tall King",
        ['Assigned'] = { "EWR" },
        ['Role'] = { HOUND.DB.RadarType.EWR },
        ['Band'] = {
            [true] = HOUND.DB.Bands.A,
            [false] = HOUND.DB.Bands.A
        },
        ['Primary'] = false
    }
end
do
    local l_mist = HOUND.Mist
    local l_math = math

    function HOUND.DB.getRadarData(typeName)
        if not HOUND.DB.Radars[typeName] then return end
        local data = l_mist.utils.deepCopy(HOUND.DB.Radars[typeName])
        data.isEWR = HOUND.setContainsValue(data.Role,HOUND.DB.RadarType.EWR)
        data.Freqency = HOUND.DB.getEmitterFrequencies(data.Band)
        return data
    end

    function HOUND.DB.isValidPlatform(candidate,PayloadAdded)
        if (not HOUND.Utils.Dcs.isUnit(candidate) and not HOUND.Utils.Dcs.isStaticObject(candidate)) or not candidate:isExist()
             then return false
        end

        local isValid = false
        local mainCategory = Object.getCategory(candidate)
        local UnitType = candidate:getTypeName()
        if HOUND.setContains(HOUND.DB.Platform,mainCategory) then
            if HOUND.setContains(HOUND.DB.Platform[mainCategory],UnitType) then
                if HOUND.DB.Platform[mainCategory][UnitType]['require'] then
                    local platformData = HOUND.DB.Platform[mainCategory][UnitType]
                    if HOUND.setContains(platformData['require'],'Payload') then
                        HOUND.Logger.debug(tostring(PayloadAdded).. " | " .. type(PayloadAdded))
                        local required = platformData['require']['Payload']
                        if type(PayloadAdded) == 'string' then
                            HOUND.Logger.debug("HOUND.DB.isValidPlatform: checking payload " .. PayloadAdded .. " for platform " .. UnitType)
                            isValid = HOUND.setContainsValue(required,PayloadAdded)
                            HOUND.Logger.debug("HOUND.DB.isValidPlatform: isValid = " .. tostring(isValid))
                        else
                            isValid = HOUND.Utils.hasPayload(candidate,required)
                        end
                    end
                    if HOUND.setContains(platformData['require'],'TASK') then
                        local required = platformData['require']['TASK']
                        isValid = not HOUND.Utils.hasTask(candidate,required)
                    end
                else
                    isValid = true
                end
            end
        end
        return isValid
    end

    function HOUND.DB.getPlatformData(DcsObject)
        if not HOUND.Utils.Dcs.isUnit(DcsObject) and not HOUND.Utils.Dcs.isStaticObject(DcsObject) then return end

        local platformData={
            pos = l_mist.utils.deepCopy(DcsObject:getPosition().p),
            isStatic = false,
            isAerial = false,
        }

        local mainCategory, PlatformUnitCategory = DcsObject:getCategory()
        local typeName = DcsObject:getTypeName()
        local DbInfo = HOUND.DB.Platform[mainCategory][typeName]

        local errorDist = DbInfo.ins_error or 0
        platformData.posErr = HOUND.Utils.Vector.getRandomVec2(errorDist)
        platformData.posErr.y = 0
        platformData.ApertureSize = (DbInfo.antenna.size * DbInfo.antenna.factor) or 0

        local VerticalOffset = DbInfo.antenna.size
        local objHitBox = DcsObject:getDesc()["box"]
        if objHitBox then
            VerticalOffset = objHitBox["max"]["y"]
        end
        if mainCategory == Object.Category.STATIC then
            platformData.isStatic = true
            platformData.pos.y = platformData.pos.y + VerticalOffset/2
        else
            if PlatformUnitCategory == Unit.Category.HELICOPTER or PlatformUnitCategory == Unit.Category.AIRPLANE then
                platformData.isAerial = true
            end
            if PlatformUnitCategory == Unit.Category.GROUND_UNIT then
                platformData.pos.y = platformData.pos.y + VerticalOffset
            end
        end
        if not platformData.isAerial then
            platformData.pos.y = platformData.pos.y + VerticalOffset
        end
        return platformData
    end

    function HOUND.DB.getDefraction(wavelength,antenna_size)
        if wavelength == nil or antenna_size == nil or antenna_size == 0 then return l_math.rad(30) end
        return wavelength/antenna_size
    end

    function HOUND.DB.getApertureSize(DcsObject)
        if not HOUND.Utils.Dcs.isUnit(DcsObject) and not HOUND.Utils.Dcs.isStaticObject(DcsObject) then return 0 end
        local mainCategory = Object.getCategory(DcsObject)
        local typeName = DcsObject:getTypeName()
        if HOUND.setContains(HOUND.DB.Platform,mainCategory) then
            if HOUND.setContains(HOUND.DB.Platform[mainCategory],typeName) then
                return HOUND.DB.Platform[mainCategory][typeName].antenna.size * HOUND.DB.Platform[mainCategory][typeName].antenna.factor * HOUND.ANTENNA_FACTOR
            end
        end
        return 0
    end

    function HOUND.DB.getEmitterBand(DcsUnit)
        if not HOUND.Utils.Dcs.isUnit(DcsUnit) then return HOUND.DB.Bands.C end
        local typeName = DcsUnit:getTypeName()
        local _,isTracking = DcsUnit:getRadar()
        if HOUND.setContains(HOUND.DB.Radars,typeName) then
            return HOUND.DB.Radars[typeName].Band[HOUND.Utils.Dcs.isUnit(isTracking)]
        end
        return HOUND.DB.Bands.C
    end

    function HOUND.DB.getEmitterFrequencies(bands,factor)
        local freqFactor = factor or l_math.random()
        return {
            [true] = bands[true][1] + bands[true][2] * freqFactor,
            [false] = bands[false][1] + bands[false][2] * freqFactor
        }
    end

    function HOUND.DB.getSensorPrecision(platform,emitterFreq)
        local wavelength = emitterFreq
        if HOUND.Utils.Dcs.isUnit(emitterFreq) then
            local _,track = emitterFreq:getRadar()
            wavelength = HOUND.DB.getEmitterFrequencies(HOUND.DB.getEmitterBand(emitterFreq))[HOUND.Utils.Dcs.isUnit(track)]
        end

        return HOUND.DB.getDefraction(wavelength,HOUND.DB.getApertureSize(platform)) or l_math.rad(20.0) -- precision
    end

    function HOUND.DB.updateHumanDb(coalitionId)
        local coalitions = coalition.side
        if type(coalitionId == "number") and (coalitionId >= 0 and coalitionId <= 2) then
            coalitions = { coalitionId }
        end
        for _,coa in pairs(coalitions) do
            local activeCoaPlayers = HOUND.Utils.Dcs.getPlayers(coa)
            for unitName,player in pairs(activeCoaPlayers) do
                if not HOUND.DB.HumanUnits.byName[coa][unitName] then
                    HOUND.DB.HumanUnits.byName[coa][unitName] = HOUND.Mist.utils.deepCopy(player)
                else
                    for k,v in pairs(player) do
                        HOUND.DB.HumanUnits.byName[coa][unitName][k] = player[k]
                    end
                end
                local gid = player.groupId
                if type(HOUND.DB.HumanUnits.byGid[coa][gid]) ~= "table" then
                    HOUND.DB.HumanUnits.byGid[coa][gid] = {}
                end
                HOUND.DB.HumanUnits.byGid[coa][gid][unitName] = HOUND.DB.HumanUnits.byName[coa][unitName]
            end
        end
    end

    function HOUND.DB.cleanHumanDb(coalitionId)
        local coalitions = coalition.side
        if type(coalitionId == "number") and (coalitionId >= 0 and coalitionId <= 2) then
            coalitions = { coalitionId }
        end
        for _,coa in pairs(coalitions) do
            for unitName,player in pairs(HOUND.DB.HumanUnits.byName[coa]) do
                if HOUND.Utils.absTimeDelta(player.lastSeen) > 300 then
                    local gid = player.groupId
                    HOUND.DB.HumanUnits.byName[coa][unitName] = nil
                    HOUND.DB.HumanUnits.byGid[coa][gid][unitName] = nil
                    if length(HOUND.DB.HumanUnits.byGid[coa][gid]) == 0 then
                        HOUND.DB.HumanUnits.byGid[coa][gid] = nil
                    end
                end
            end
        end
    end

    function HOUND.DB.generateMistDbEntry(DcsUnit)
        if not HOUND.Utils.Dcs.isUnit(DcsUnit) then return {} end
        local grp = DcsUnit:getGroup()
        local unitCallsign = DcsUnit:getCallsign()
        local parsedCallsign = {unitCallsign:match("([%a]+)(%d+)%-(%d+)")}
        if #parsedCallsign ~= 3 then
            parsedCallsign = {unitCallsign:match("([%a]+)(%d)(%d)")}
        end
        local unitData = {
            type = DcsUnit:getTypeName(),
            unitId = DcsUnit:getID(),
            unitName = DcsUnit:getName(),
            lastSeen = timer:getAbsTime(),
            groupId = grp:getID(),
            groupName = grp:getName(),
            callsign = {
                [1] = parsedCallsign[1],
                [2] = tonumber(parsedCallsign[2]),
                [3] = tonumber(parsedCallsign[3]),
                name = unitCallsign
            }
        }
        return unitData
    end
end--- HOUND.Config
do

    HOUND.Config = {
        configMaps = {}
    }

    HOUND.Config.__index = HOUND.Config

    function HOUND.Config.get(HoundInstanceId)
        HoundInstanceId = HoundInstanceId or HOUND.Length(HOUND.Config.configMaps)+1

        if HOUND.Config.configMaps[HoundInstanceId] then
            return HOUND.Config.configMaps[HoundInstanceId]
        end

        local instance = {}
        instance.intervals = {
            scan = 10,
            process = 30,
            menus = 60,
            markers = 120,

        }
        instance.preferences = {
            useMarkers = true,
            markerType = HOUND.MARKER.CIRCLE,
            markSites = true,
            hardcore = false,
            detectDeadRadars = true,
            NatoBrevity = false,
            platformPosErr = false,
            useNatoCallsigns = false,
            AtisUpdateInterval = 300,
            AlertOnLaunch = false,
            AlertOnLaunchCooldown = 30

        }
        instance.coalitionId = nil
        instance.id = HoundInstanceId
        instance.callsigns = {}
        instance.callsignOverride = {}
        instance.radioMenu = {
            root = nil,
            parent = nil
        }
        instance.onScreenDebug = false

        instance.getId = function (self)
            return self.id
        end

        instance.getCoalition = function(self)
            return self.coalitionId
        end

        instance.setCoalition = function(self,coalitionId)
            if self.coalitionId ~= nil then
                env.info("[Hound] - coalition already set for Instance Id " .. self.id)
                return false
            end
            if HOUND.setContainsValue(coalition.side,coalitionId) then
                self.coalitionId = coalitionId
                return true
            end
            return false
        end

        instance.setInterval = function (self,intervalName,setVal)
            if HOUND.setContains(self.intervals,intervalName) and type(setVal) == "number" then
                self.intervals[intervalName] = setVal
                return true
            end
            return false
        end

        instance.getMarkerType = function (self)
            return self.preferences.markerType
        end

        instance.setMarkerType = function (self,markerType)
            if HOUND.setContainsValue(HOUND.MARKER,markerType) then
                self.preferences.markerType = markerType
                return true
            end
            return false
        end

        instance.getUseMarkers = function (self)
            return self.preferences.useMarkers
        end

        instance.setUseMarkers = function(self,value)
            if type(value) == "boolean" then
                self.preferences.useMarkers = value
                return true
            end
            return false
        end
        instance.getMarkSites = function (self)
            return self.preferences.markSites
        end
        instance.setMarkSites = function(self,value)
            if type(value) == "boolean" then
                self.preferences.markSites = value
                return true
            end
            return false
        end

        instance.getBDA = function(self)
            return self.preferences.detectDeadRadars
        end

        instance.setBDA = function(self,value)
            if type(value) == "boolean" then
                self.preferences.detectDeadRadars = value
                return true
            end
            return false
        end

        instance.getNATO = function(self)
            return self.preferences.NatoBrevity
        end

        instance.setNATO = function(self,value)
            if type(value) == "boolean" then
                self.preferences.NatoBrevity = value
                return true
            end
            return false
        end

        instance.getUseNATOCallsigns = function(self)
            return self.preferences.useNatoCallsigns
        end

        instance.setUseNATOCallsigns = function(self,value)
            if type(value) == "boolean" then
                self.preferences.useNatoCallsigns = value
                return true
            end
            return false
        end

        instance.getAtisUpdateInterval = function(self)
            return self.preferences.AtisUpdateInterval
        end

        instance.setAtisUpdateInterval = function(self,value)
            if type(value) == "number" then
                self.preferences.AtisUpdateInterval = value
                return true
            end
            return false
        end

        instance.getPosErr = function(self)
            return self.preferences.platformPosErr
        end

        instance.setPosErr = function(self,value)
            if type(value) == "boolean" then
                self.preferences.platformPosErr = value
                return true
            end
            return false
        end

        instance.getHardcore = function(self)
            return self.preferences.hardcore
        end

        instance.setHardcore = function(self,value)
            if type(value) == "boolean" then
                self.preferences.hardcore = value
                return true
            end
            return false
        end

        instance.getOnScreenDebug = function(self)
            return self.onScreenDebug
        end

        instance.setOnScreenDebug = function(self,value)
            if type(value) == "boolean" then
                self.onScreenDebug = value
                return true
            end
            return false
        end

        instance.getCallsignOverride = function(self)
            return self.callsignOverride
        end

        instance.setCallsignOverride = function(self,value)
            if type(value) == "table" then
                self.callsignOverride = value
                return true
            end
            return false
        end

        instance.setAlertOnLaunch = function(self,value)
            if type(value) == "boolean" then
                self.preferences.AlertOnLaunch = value
                return true
            end
            return false
        end

        instance.getAlertOnLaunch = function(self)
            return self.preferences.AlertOnLaunch
        end

        instance.getAlertOnLaunchCooldown = function(self)
            return self.preferences.AlertOnLaunchCooldown
        end

        instance.setAlertOnLaunchCooldown = function(self,value)
            if type(value) == "number" then
                self.preferences.AlertOnLaunchCooldown = value
                return true
            end
            return false
        end

        instance.getRadioMenu = function (self)
            if not self.radioMenu.root then
                self.radioMenu.root = missionCommands.addSubMenuForCoalition(
                    self:getCoalition(), 'ELINT',self:getRadioMenuParent())
            end
            return self.radioMenu.root
        end

        instance.removeRadioMenu = function (self)
            if self.radioMenu.root ~= nil then
                missionCommands.removeItemForCoalition(self:getCoalition(),self.radioMenu.root)
                self.radioMenu.root = nil
                return true
            end
            return false
        end

        instance.getRadioMenuParent = function(self)
            return self.radioMenu.parent
        end

        instance.setRadioMenuParent = function (self,parent)
            if type(parent) == "table" or (parent == nil and self.radioMenu.parent) then
                self:removeRadioMenu()
                self.radioMenu.parent = parent
                return true
            end
            return false
        end

        HOUND.Config.configMaps[HoundInstanceId] = instance

        return HOUND.Config.configMaps[HoundInstanceId]
    end
end
do
    local l_mist = HOUND.Mist
    local l_math = math
    local l_grpc = GRPC
    local PI_2 = 2*l_math.pi

    HOUND.Utils = {
        Mapping = {},
        Dcs     = {},
        Geo     = {},
        Marker  = {},
        Text    = {},
        Elint   = {},
        Vector  = {},
        Zone    = {},
        Sort    = {},
        Filter  = {},
        ReportId = nil,
        _HoundId = 0
    }
    HOUND.Utils.__index = HOUND.Utils

    function HOUND.Utils.getHoundId()
        HOUND.Utils._HoundId = HOUND.Utils._HoundId + 1
        return HOUND.Utils._HoundId
    end

    function HOUND.Utils.getMarkId()
        return HOUND.Utils.Marker.getId()
    end

    function HOUND.Utils.setInitialMarkId(startId)
        return HOUND.Utils.Marker.setInitialId(startId)
    end

    function HOUND.Utils.absTimeDelta(t0, t1)
        if t1 == nil then t1 = timer.getAbsTime() end
        return t1 - t0
    end

    function HOUND.Utils.angleDeltaRad(rad1,rad2)
        if not rad1 or not rad2 then return end
        return l_math.pi - l_math.abs(l_math.pi - l_math.abs(rad1-rad2) % PI_2)
    end

    function HOUND.Utils.normalizeAngle(rad)
        return rad - (PI_2) * l_math.floor((rad + l_math.pi) / (PI_2))
    end

    function HOUND.Utils.AzimuthAverage(azimuths)
        if not azimuths or HOUND.Length(azimuths) == 0 then return nil end

        local sumSin = 0
        local sumCos = 0
        for i=1, HOUND.Length(azimuths) do
            sumSin = sumSin + l_math.sin(azimuths[i])
            sumCos = sumCos + l_math.cos(azimuths[i])
        end
        return (l_math.atan2(sumSin,sumCos) + PI_2) % PI_2
    end

    function HOUND.Utils.getMagVar(DCSpoint)
        if not HOUND.Utils.Dcs.isPoint(DCSpoint) then return 0 end
        return l_mist.getNorthCorrection(DCSpoint)

    end
    function HOUND.Utils.PointClusterTilt(points,MagNorth,refPos)
        if not points or type(points) ~= "table" then return end
        if not refPos then
            refPos = l_mist.getAvgPoint(points)
        end
        local magVar = 0
        if MagNorth then
            magVar = HOUND.Utils.getMagVar(refPos)
        end
        local biasVector = nil
        for _,point in pairs(points) do
            local V = {
                y = 0
            }
            V.x = point.x - refPos.x
            V.z = point.z - refPos.z
            if V.x < 0 then
                V.x = -V.x
                V.z = -V.z
            end
            if biasVector == nil then biasVector = V else biasVector = l_mist.vec.add(biasVector,V) end
        end
        return (l_math.atan2(biasVector.z,biasVector.x) + magVar) % PI_2
    end

    function HOUND.Utils.RandomAngle()
        return l_math.random() * 2 * l_math.pi
    end

    function HOUND.Utils.getRoundedElevationFt(elev,resolution)
        if not resolution then
            resolution = 50
        end
        return HOUND.Utils.roundToNearest(l_mist.utils.metersToFeet(elev),resolution)
    end

    function HOUND.Utils.roundToNearest(input,nearest)
        return l_mist.utils.round(input/nearest) * nearest
    end

    function HOUND.Utils.getNormalAngularError(variance)
        local stddev = variance /2
        local Magnitude = l_math.sqrt(-2 * l_math.log(l_math.random())) * stddev
        local Theta = 2* math.pi * l_math.random()

        local epsilon = {
            az = Magnitude * l_math.cos(Theta),
            el = Magnitude * l_math.sin(Theta)
        }
        return epsilon
    end

    function HOUND.Utils.getControllerResponse()
        local response = {
            " ",
            "Good Luck!",
            "Happy Hunting!",
            "Please send my regards.",
            "Come back with E T A, T O T, and B D A.",
            " "
        }
        return response[l_math.max(1,l_math.min(l_math.ceil(timer.getAbsTime() % HOUND.Length(response)),HOUND.Length(response)))]
    end

    function HOUND.Utils.getCoalitionString(coalitionID)
        local coalitionStr = "RED"
        if coalitionID == coalition.side.BLUE then
            coalitionStr = "BLUE"
        elseif coalitionID == coalition.side.NEUTRAL then
            coalitionStr = "NEUTRAL"
        end
        return coalitionStr
    end

    function HOUND.Utils.getHemispheres(lat,lon,fullText)
        local hemi = {
            NS = "North",
            EW = "East"
        }
        if lat < 0 then hemi.NS = "South" end
        if lon < 0 then hemi.EW = "West" end
        if fullText == nil or fullText == false then
            hemi.NS = string.sub(hemi.NS, 1, 1)
            hemi.EW = string.sub(hemi.EW, 1, 1)
        end
        return hemi
    end

    function HOUND.Utils.getReportId(ReportId)
        local returnId
        if ReportId ~= nil then
            returnId =  string.byte(ReportId)
        else
            returnId = HOUND.Utils.ReportId
        end
        if returnId == nil or returnId == string.byte('Z') then
            returnId = string.byte('A')
        else
            returnId = returnId + 1
        end
        if not ReportId then
            HOUND.Utils.ReportId = returnId
        end

        return HOUND.DB.PHONETICS[string.char(returnId)],string.char(returnId)
    end

    function HOUND.Utils.DecToDMS(cood)
        local deg = l_math.floor(cood)
        if cood < 0 then
            deg = l_math.ceil(cood)
        end
        local minutes = l_math.floor(l_math.abs(cood - deg) * 60)
        local sec = l_math.floor((l_math.abs(cood-deg) * 3600) % 60)
        local dec = l_math.abs(cood-deg) * 60

        return {
            d = deg,
            m = minutes,
            s = sec,
            mDec = l_mist.utils.round(dec ,3),
            sDec = l_mist.utils.round((l_mist.utils.round(dec,3)*1000)-(minutes*1000))
        }
    end

    function HOUND.Utils.getBR(src,dst)
        if not src or not dst then return end
        local BR = {}
        local dir = l_mist.utils.getDir(l_mist.vec.sub(dst,src),src) -- pass src to get magvar included
        BR.brg = l_mist.utils.round(l_mist.utils.toDegree( dir ))
        BR.brStr = string.format("%03d",BR.brg)
        BR.rng = l_mist.utils.round(l_mist.utils.metersToNM(l_mist.utils.get2DDist(dst,src)))
        return BR
    end

    function HOUND.Utils.getFormationCallsign(player,override,flightMember)
        local callsign = ""
        local DcsUnit = Unit.getByName(player.unitName)
        if type(player) ~= "table" then return callsign end
        if type(flightMember) == "table" and override == nil then
            override,flightMember = flightMember,override
        end
        local formationCallsign = string.gsub(player.callsign.name,"[%d%s]","")

        callsign =  formationCallsign .. " " .. player.callsign[2]
        if flightMember then
            callsign = callsign .. " " .. player.callsign[3]
        end

        if type(override) == "table" then
            if HOUND.setContains(override,formationCallsign) then
                local override = override[formationCallsign]
                if override == "*" then
                    override = DcsUnit:getGroup():getName() or formationCallsign
                end
                callsign = callsign:gsub(formationCallsign,override)
                return string.upper(callsign:match( "^%s*(.-)%s*$" ))
            end
        end

        if not DcsUnit then return string.upper(callsign:match( "^%s*(.-)%s*$" )) end

        local playerName = DcsUnit:getPlayerName()
        playerName = playerName:match("%a+%s%d+[?%p%s*]%d*")
        if playerName then
            callsign = playerName
            local base = string.match(callsign,"%a+")
            local num = tonumber(string.match(callsign,"%d+"))
            local memberNum = string.gsub(callsign,"%a+%s%d+[%p%s*]","")
            if memberNum:len() > 0 then
                memberNum = tonumber(memberNum:match("%d+"))
            else
                memberNum = nil
            end

            callsign = base
            if type(num) == "number" and type(memberNum) == "number" then
                callsign = callsign .. " " .. num
            end

            if flightMember then
                if type(memberNum) == "number" then
                    callsign = callsign .. " " .. memberNum
                end
                if type(num) == "number" and type(memberNum) == "nil" then
                    callsign = callsign .. " " .. num
                end
            end
            return string.upper(callsign:match( "^%s*(.-)%s*$" ))
        end
        return string.upper(callsign:match( "^%s*(.-)%s*$" ))
    end

    function HOUND.Utils.getHoundCallsign(namePool)
        local SelectedPool = HOUND.DB.CALLSIGNS[namePool] or HOUND.DB.CALLSIGNS.GENERIC
        return SelectedPool[l_math.random(1, HOUND.Length(SelectedPool))]
    end

    function HOUND.Utils.useDMM(DcsUnit)
        if not DcsUnit then return false end
        local typeName = nil
        if type(DcsUnit) == "string" then
            typeName = DcsUnit
        end
        if HOUND.Utils.Dcs.isUnit(DcsUnit) then
            typeName = DcsUnit:getTypeName()
        end
        return HOUND.setContains(HOUND.DB.useDMM,typeName)
    end

    function HOUND.Utils.useMGRS(DcsUnit)
        if not DcsUnit then return false end
        local typeName = nil
        if type(DcsUnit) == "string" then
            typeName = DcsUnit
        end
        if HOUND.Utils.Dcs.isUnit(DcsUnit) then
            typeName = DcsUnit:getTypeName()
        end
        return HOUND.setContains(HOUND.DB.useMGRS,typeName)
    end
    function HOUND.Utils.hasPayload(DcsUnit,payloadName)
        return true
    end

    function HOUND.Utils.hasTask(DcsUnit,taskName)
        return true
    end

    HOUND.Utils.Mapping.CURVES = {
        RETAIL = 0,
        WINDOWS = 1,
        HERRA9 = 2,
        HERRA45 = 3,
        EXPONENTIAL = 4,
        MIXED = 5,
        POWER = 6
    }

    function HOUND.Utils.Mapping.clamp(input, out_min, out_max)
        return l_math.max(out_min,l_math.min(input,out_max))
    end

    function HOUND.Utils.Mapping.linear(input, in_min, in_max, out_min, out_max,clamp)
        local mapValue = (input - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
        if clamp then
            if out_min < out_max then
                return l_math.max(out_min,l_math.min(out_max,mapValue))
            else
                return l_math.max(out_max,l_math.min(out_min,mapValue))
            end
        end
        return mapValue
    end

    function HOUND.Utils.Mapping.nonLinear(value,in_min,in_max,out_min,out_max,sensitivity,curve_type)

        if type(sensitivity) ~= "number" then
            sensitivity = 9
        end
        sensitivity=l_math.min(0,l_math.max(9,sensitivity))
        local relativePos = HOUND.Utils.Mapping.linear(value,in_min,in_max,0,1)
        local mappedIn = relativePos*(sensitivity/9)+(relativePos^5)*(9-sensitivity)/9
        if type(curve_type) == "number" then
            if curve_type == 1 then
                mappedIn = relativePos^(3-(sensitivity/4.5))
            elseif curve_type == 2 then
                mappedIn = relativePos^(sensitivity/9)*((1-l_math.cos(relativePos*l_math.pi))/2)^((9-sensitivity)/9)
            elseif curve_type == 3 then
                mappedIn = relativePos^(sensitivity/9)*((1-l_math.cos(relativePos*l_math.pi))/2)^((9-sensitivity)/4.5)
            elseif curve_type == 4 then
                mappedIn = (l_math.exp((10-sensitivity)*relativePos)-1)/(l_math.exp(10-sensitivity)-1)
            elseif curve_type == 5 then
                mappedIn = relativePos^(1+((5-sensitivity)/9))
            elseif curve_type == 6 then
                mappedIn = relativePos*relativePos^((9-sensitivity)/9)
            end
        end

        if type(out_min) == "number" and type(out_max) == "number" then
            return HOUND.Utils.Mapping.linear(mappedIn,0,1,out_min,out_max)
        end
        return mappedIn
    end

    function HOUND.Utils.Dcs.isPoint(point)
        if type(point) ~= "table" then return false end
        return (type(point.x) == "number") and (type(point.z) == "number")
    end

    function HOUND.Utils.Dcs.isUnit(obj)
        if type(obj) ~= "table" then return false end
        return getmetatable(obj) == Unit
    end

    function HOUND.Utils.Dcs.isGroup(obj)
        if type(obj) ~= "table" then return false end
        return getmetatable(obj) == Group
    end

    function HOUND.Utils.Dcs.isStaticObject(obj)
        if type(obj) ~= "table" then return false end
        return getmetatable(obj) == StaticObject
    end

    function HOUND.Utils.Dcs.isHuman(obj)
        if not HOUND.Utils.Dcs.isUnit(obj) then return false end
        return obj:getPlayerName() ~= nil
    end

    function HOUND.Utils.Dcs.getPlayers(coalitionId)
        if type(coalitionId) ~= "number" or (coalitionId > 2 or coalitionId < 0) then return {} end
        local players = coalition.getPlayers(coalitionId)
        local humanUnits = {}
        for i = 1, #players do
            local playerUnit = players[i]
            local _,catEx = playerUnit:getCategory()
            if HOUND.setContainsValue({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER},catEx) then
                local unit_data = HOUND.DB.generateMistDbEntry(playerUnit)
                humanUnits[unit_data.unitName] = unit_data
            end
        end
        return humanUnits
    end

    function HOUND.Utils.Dcs.getPlayersInGroup(DcsGroup)
        if type(DcsGroup) == "string" then
            DcsGroup = Group.getByName(DcsGroup)
        end
        if not HOUND.Utils.Dcs.isGroup(DcsGroup) then return {} end
        local coa = DcsGroup:getCoalition()
        local gid = DcsGroup:getID()
        if type(HOUND.DB.HumanUnits.byGid[coa][gid]) ~= "table" then return {} end
        local humanUnits = {}
        for unitName,unitData in pairs(HOUND.DB.HumanUnits.byGid[coa][gid]) do
            humanUnits[unitName] = unitData
        end
        return humanUnits
    end

    function HOUND.Utils.Dcs.isRadarTracking(DcsUnit)
        if not HOUND.Utils.Dcs.isUnit(DcsUnit) then return false end
        local _,isTracking = DcsUnit:getRadar()
        return HOUND.Utils.Dcs.isUnit(isTracking)
    end

    function HOUND.Utils.Dcs.getSamMaxRange(DcsUnit)
        local maxRng = 0
        if DcsUnit ~= nil then
            local units = DcsUnit:getGroup():getUnits()
            for _, unit in ipairs(units) do
                local weapons = unit:getAmmo()
                if weapons ~= nil then
                    for _, ammo in ipairs(weapons) do
                        if ammo.desc.category == Weapon.Category.MISSILE and ammo.desc.missileCategory == Weapon.MissileCategory.SAM then
                            maxRng = l_math.max(l_math.max(ammo.desc.rangeMaxAltMax,ammo.desc.rangeMaxAltMin),maxRng)
                        end
                    end
                end
            end
        end
        return maxRng
    end

    function HOUND.Utils.Dcs.getRadarDetectionRange(DcsUnit)
        local detectionRange = 0
        local unit_sensors = DcsUnit:getSensors()
        if not unit_sensors then return detectionRange end
        if not HOUND.setContains(unit_sensors,Unit.SensorType.RADAR) then return detectionRange end
        for _,radar in pairs(unit_sensors[Unit.SensorType.RADAR]) do
            if HOUND.setContains(radar,"detectionDistanceAir") then
                for _,aspects in pairs(radar["detectionDistanceAir"]) do
                    for _,range in pairs(aspects) do
                        detectionRange = l_math.max(detectionRange,range)
                    end
                end
            end
        end
        return detectionRange
    end

    function HOUND.Utils.Dcs.getRadarUnitsInGroup(DcsGroup)
        local radarUnits = {}
        if HOUND.Utils.Dcs.isGroup(DcsGroup) and DcsGroup:isExist() and DcsGroup:getSize() > 0 then
            for _,unit in ipairs(DcsGroup:getUnits()) do
                if unit:hasSensors(Unit.SensorType.RADAR) and HOUND.setContains(HOUND.DB.Radars,unit:getTypeName()) then
                    table.insert(radarUnits,unit)
                end
            end
        end
        return radarUnits
    end

    function HOUND.Utils.Dcs.getGroupNames(prefix)
        local groups = {}
        if type(prefix) ~= "string" then
            prefix = nil
        end
        for _,coalitionName in pairs(coalition.side) do
            for _,group in pairs(coalition.getGroups(coalitionName)) do
                local groupName = group:getName()
                if prefix == nil or (prefix ~= "" and string.find(groupName, prefix, 1, true) == 1) then
                    groups[groupName] = group:getID()
                end
            end
        end
        return groups
    end

    function HOUND.Utils.Dcs.getUnitNames(prefix)
        local units = {}
        if type(prefix) ~= "string" then
            prefix = nil
        end
        for _,coalitionName in pairs(coalition.side) do
            for _,group in pairs(coalition.getGroups(coalitionName)) do
                for _,unit in pairs(group:getUnits()) do
                    local unitName = unit:getName()
                    if prefix == nil or (prefix ~= "" and string.find(unitName, prefix, 1, true) == 1) then
                        units[unitName] = HOUND.DB.generateMistDbEntry(unit)
                    end
                end
            end
        end
        return units
    end

    function HOUND.Utils.Dcs.getStaticObjectNames(prefix)
        local staticObjs = {}
        if type(prefix) ~= "string" then
            prefix = nil
        end
        for _,coalitionName in pairs(coalition.side) do
            for _,staticObj in pairs(coalition.getStaticObjects(coalitionName)) do
                local name = staticObj:getName()
                if prefix == nil or (prefix ~= "" and string.find(name, prefix, 1, true) == 1) then
                    staticObjs[name] = name
                end

            end
        end
        return staticObjs
    end

    function HOUND.Utils.Dcs.getGroupPoints(groupIdent)
        local gpId = groupIdent
        if type(groupIdent) == 'string' and not tonumber(groupIdent) then
            for grpName,grpId in pairs(HOUND.Utils.Dcs.getGroupNames(groupIdent)) do
                if grpName == groupIdent then
                    gpId = grpId
                end
            end
            if gpId == groupIdent then
                log:error("Group not found: $1", groupIdent)
            end
        end

        for coa_name, coa_data in pairs(env.mission.coalition) do
            if  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_cat_name, obj_cat_data in pairs(cntry_data) do
                            if obj_cat_name == "helicopter" or obj_cat_name == "ship" or obj_cat_name == "plane" or obj_cat_name == "vehicle" then	-- only these types have points
                                if ((type(obj_cat_data) == 'table') and obj_cat_data.group and (type(obj_cat_data.group) == 'table') and (#obj_cat_data.group > 0)) then	--there's a group!
                                    for group_num, group_data in pairs(obj_cat_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
                                                    if not point.point then
                                                        points[point_num] = { x = point.x, y = point.y }
                                                    else
                                                        points[point_num] = point.point	--it's possible that the ME could move to the point = Vec2 notation.
                                                    end
                                                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_cat_data.group) do
                                end --if ((type(obj_cat_data) == 'table') and obj_cat_data.group and (type(obj_cat_data.group) == 'table') and (#obj_cat_data.group > 0)) then
                            end --if obj_cat_name == "helicopter" or obj_cat_name == "ship" or obj_cat_name == "plane" or obj_cat_name == "vehicle" or obj_cat_name == "static" then
                        end --for obj_cat_name, obj_cat_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
    end

    function HOUND.Utils.Geo.checkLOS(pos0,pos1)
        if not HOUND.Utils.Dcs.isPoint(pos0) or not HOUND.Utils.Dcs.isPoint(pos1) then return false end
        local dist = l_mist.utils.get2DDist(pos0,pos1)
        local radarHorizon = HOUND.Utils.Geo.EarthLOS(pos0.y,pos1.y)
        return (dist <= radarHorizon*1.025 and land.isVisible(pos0,pos1))
    end

    function HOUND.Utils.Geo.EarthLOS(h0,h1)
        if not h0 then return 0 end
        local Re = 6367444 -- Radius of earth in M (avarage radius of WGS84)
        local d0 = l_math.sqrt(h0^2+2*Re*h0)
        local d1 = 0
        if h1 then d1 = l_math.sqrt(h1^2+2*Re*h1) end
        return d0+d1
    end

    function HOUND.Utils.Geo.getProjectedIP(p0,az,el)
        if not HOUND.Utils.Dcs.isPoint(p0) or type(az) ~= "number" or type(el) ~= "number" then return end
        local maxSlant = HOUND.Utils.Geo.EarthLOS(p0.y)*1.1

        local unitVector = HOUND.Utils.Vector.getUnitVector(az,el)
        return land.getIP(p0, unitVector , maxSlant )
    end

    function HOUND.Utils.Geo.setPointHeight(point,offset)
        if HOUND.Utils.Dcs.isPoint(point) and type(point.y) ~= "number" then
            offset = offset or 0
            point.y = land.getHeight({x=point.x,y=point.z}) + offset
        end
        return point
    end

    function HOUND.Utils.Geo.setHeight(point,offset)
        if type(point) == "table" then
            offset = offset or 0
            if HOUND.Utils.Dcs.isPoint(point) then
                return HOUND.Utils.Geo.setPointHeight(point,offset)
            end
            for _,pt in pairs(point) do
                pt = HOUND.Utils.Geo.setPointHeight(pt,offset)
            end
        end
        return point
    end

    function HOUND.Utils.Geo.get2DDistance(src, dst)
        if HOUND.Utils.Dcs.isPoint(src) and HOUND.Utils.Dcs.isPoint(dst) then
            return l_mist.utils.get2DDist(src,dst)
        end

    end

    function HOUND.Utils.Geo.get3DDistance(src, dst)
        if HOUND.Utils.Dcs.isPoint(src) and HOUND.Utils.Dcs.isPoint(dst) then
            return l_mist.utils.get3DDist(src,dst)
        end

    end

    HOUND.Utils.Marker._MarkId = 4999
    HOUND.Utils.Marker.Type = {
        NONE = 0,
        POINT = 1,
        TEXT =  2,
        CIRCLE = 3,
        FREEFORM = 4
    }

    function HOUND.Utils.Marker.getId()
            HOUND.Utils.Marker._MarkId = HOUND.Utils.Marker._MarkId + 1
        return HOUND.Utils.Marker._MarkId
    end

    function HOUND.Utils.Marker.setInitialId(startId)
        if type(startId) ~= "number" then
            HOUND.Logger.error("Failed to set Initial marker Id. Value provided was not a number")
            return false
        end
        if HOUND.Utils.Marker._MarkID ~= 0 then
            HOUND.Logger.error("Initial MarkId not updated because markers have already been drawn")
            return false
        end
        HOUND.Utils.Marker._MarkId = startId
        return true
    end

    function HOUND.Utils.Marker.create(args)
        local instance = {}
        instance.id = -1
        instance.type = HOUND.Utils.Marker.Type.NONE

        instance.setPos = function(self,pos)
            if self.type == HOUND.Utils.Marker.Type.FREEFORM then return end
            if HOUND.Utils.Dcs.isPoint(pos) then
                trigger.action.setMarkupPositionStart(self.id,pos)
            end
        end

        instance.setText = function(self,text)
            if type(text) == "string" and self.id > 0 then
                if self.type == HOUND.Utils.Marker.Type.TEXT then
                    text = HOUND.MARKER_TEXT_POINTER .. text
                end
                trigger.action.setMarkupText(self.id,text)
            end
        end

        instance.setRadius = function(self,radius)
            if type(radius) == "number" and self.type == HOUND.Utils.Marker.Type.CIRCLE and self.id > 0 then
                trigger.action.setMarkupRadius(self.id,radius)
            end
        end

        instance.setFillColor = function(self,color)
            if self.id > 0 and self.type ~= HOUND.Utils.Marker.Type.FREEFORM and type(color) == "table" then
                trigger.action.setMarkupColorFill(self.id,color)
            end
        end

        instance.setLineColor = function(self,color)
            if self.id > 0 and self.type ~= HOUND.Utils.Marker.Type.FREEFORM and type(color) == "table" then
                trigger.action.setMarkupColor(self.id,color)
            end
        end

        instance.setLineType = function(self,lineType)
            if self.id > 0 and type(lineType) == "number" and self.type ~= HOUND.Utils.Marker.Type.FREEFORM then
                trigger.action.setMarkupTypeLine(self.id,lineType)
            end
        end

        instance.isDrawn = function(self)
            return (self.id > 0)
        end

        instance.remove = function(self)
            if self.id > 0 then
                local GC = (self.id % 5 == 0)
                trigger.action.removeMark(self.id)
                self.id = -1
                self.type = HOUND.Utils.Marker.Type.NONE
                if GC then
                    collectgarbage("collect")
                end
            end
        end

        instance._new = function(self,args)
            if type(args) ~= "table" then return false end
            local coalition = args.coalition
            local pos = args.pos
            local text = args.text
            local lineColor = args.lineColor or {0,0,0,0.75}
            local fillColor = args.fillColor or {0,0,0,0}
            local lineType = args.lineType or 2
            local fontSize = args.fontSize or 16
            if self.id < 1 then
                self.id = HOUND.Utils.Marker.getId()
            end
            if HOUND.Utils.Dcs.isPoint(pos) then
                if args.useLegacyMarker then
                    self.type = HOUND.Utils.Marker.Type.POINT
                    trigger.action.markToCoalition(self.id, text, pos, coalition,true)
                    return true
                end
                self.type = HOUND.Utils.Marker.Type.TEXT
                trigger.action.textToAll(coalition,self.id, pos,lineColor,fillColor,fontSize,true,HOUND.MARKER_TEXT_POINTER .. text)
                return true
            end

            if HOUND.Length(pos) == 2 and HOUND.Utils.Dcs.isPoint(pos.p) and type(pos.r) == "number" then
                self.type = HOUND.Utils.Marker.Type.CIRCLE
                trigger.action.circleToAll(coalition,self.id, pos.p,pos.r,lineColor,fillColor,lineType,true)
                return true
            end

            if HOUND.Length(pos) == 4 then
                self.type = HOUND.Utils.Marker.Type.FREEFORM
                trigger.action.markupToAll(6,coalition,self.id,
                    pos[1], pos[2], pos[3], pos[4],
                    lineColor,fillColor,lineType,true)
            end

            if HOUND.Length(pos) == 8 then
                self.type = HOUND.Utils.Marker.Type.FREEFORM
                trigger.action.markupToAll(7,coalition,self.id,
                    pos[1], pos[2], pos[3], pos[4],
                    pos[5], pos[6], pos[7], pos[8],
                    lineColor,fillColor,lineType,true)
            end

            if HOUND.Length(pos) == 16 then
                self.type = HOUND.Utils.Marker.Type.FREEFORM
                trigger.action.markupToAll(7,coalition,self.id,
                    pos[1], pos[2], pos[3], pos[4],
                    pos[5], pos[6], pos[7], pos[8],
                    pos[9], pos[10], pos[11], pos[12],
                    pos[13], pos[14], pos[15], pos[16],
                    lineColor,fillColor,lineType,true)
            end
        end

        instance._replace = function(self,args)
            self:remove()
            return self:_new(args)
        end

        instance.update = function(self,args)
            if type(args.coalition) ~= "number" then return false end
            if self.id < 1 then
                return self:_new(args)
            end

            if (self.type ==  HOUND.Utils.Marker.Type.POINT or self.type == HOUND.Utils.Marker.Type.FREEFORM) then
                return self:_replace(args)
            end
            if args.pos then
                local pos = args.pos
                if HOUND.Utils.Dcs.isPoint(pos) then
                    self:setPos(pos)
                end
                if HOUND.Length(pos) == 2 and type(pos.r) == "number" and HOUND.Utils.Dcs.isPoint(pos.p) then
                    self:setPos(pos.p)
                    self:setRadius(pos.r)
                end
                if type(pos) == "table" and HOUND.Length(pos) > 2 and HOUND.Utils.Dcs.isPoint(pos[1]) then
                    return self:_replace(args)
                end
            end
            if args.text and type(args.text) == "string" then
                self:setText(args.text)
            end
            if type(args.fillColor) == "table" then
                self:setFillColor(args.fillColor)
            end
            if type(args.lineColor) == "table" then
                self:setLineColor(args.lineColor)
            end
            if type(args.lineType) == "number" then
                self:setLineType(args.lineType)
            end
        end
        if type(args) == "table" then
            instance.update(instance,args)
        end
        return instance
    end

    function HOUND.Utils.Text.getLL(lat,lon,minDec)
        local hemi = HOUND.Utils.getHemispheres(lat,lon)
        lat = HOUND.Utils.DecToDMS(lat)
        lon = HOUND.Utils.DecToDMS(lon)
        if minDec == true then
            return hemi.NS .. string.format("%02d",l_math.abs(lat.d)) .. "°" .. string.format("%.3f",lat.mDec) .. "'" ..  " " ..  hemi.EW  .. string.format("%03d",l_math.abs(lon.d)) .. "°" .. string.format("%.3f",lon.mDec) .. "'"
        end
        return hemi.NS .. string.format("%02d",l_math.abs(lat.d)) .. "°" .. string.format("%02d",lat.m) .. "'".. string.format("%02d",l_math.floor(lat.s)).."\"" ..  " " ..  hemi.EW  .. string.format("%03d",l_math.abs(lon.d)) .. "°" .. string.format("%02d",lon.m) .. "'".. string.format("%02d",l_math.floor(lon.s)) .."\""
    end

    function HOUND.Utils.Text.getTime(timestamp)
        if timestamp == nil then timestamp = timer.getAbsTime() end
        local DHMS = l_mist.time.getDHMS(timestamp)
        return string.format("%02d",DHMS.h)  .. string.format("%02d",DHMS.m)
    end

    function HOUND.Utils.Elint.generateAngularError(variance)
        local vec2 = HOUND.Utils.Vector.getRandomVec2(variance)
        local epsilon = {
            az = vec2.x,
            el = vec2.z
        }
        return epsilon
    end

    function HOUND.Utils.Elint.getAzimuth(src, dst, sensorPrecision)
        if not HOUND.Utils.Dcs.isPoint(src) or not HOUND.Utils.Dcs.isPoint(dst) then return end
        local AngularErr = HOUND.Utils.Elint.generateAngularError(sensorPrecision)

        local vec = l_mist.vec.sub(dst, src)
        local az = l_math.atan2(vec.z,vec.x) + AngularErr.az
        if az < 0 then
            az = az + PI_2
        end
        if az > PI_2 then
            az = az - PI_2
        end

        local el = (l_math.atan(vec.y/l_math.sqrt(vec.x^2 + vec.z^2)) + AngularErr.el)

        return az,el,vec
    end

    function HOUND.Utils.Elint.getSignalStrength(src, dst, maxDetection)
        if not HOUND.Utils.Dcs.isPoint(src) or not HOUND.Utils.Dcs.isPoint(dst) or not (type(maxDetection) == "number" and maxDetection > 0) then return 0 end
        local dist = l_mist.utils.get3DDist(src,dst)
        local rng = (dist/maxDetection)
        return 1/(rng*rng)
    end

    function HOUND.Utils.Elint.getActiveRadars(instanceCoalition)
        local Radars = {}
        if instanceCoalition == nil then return Radars end

        for _,coalitionName in pairs(coalition.side) do
            if coalitionName ~= instanceCoalition then
                for _,CategoryId in pairs({Group.Category.GROUND,Group.Category.SHIP}) do
                    for _,group in pairs(coalition.getGroups(coalitionName, CategoryId)) do
                        for _,unit in pairs(group:getUnits()) do
                            if (unit:isExist() and unit:isActive() and unit:getRadar()) then
                                table.insert(Radars, unit:getName()) -- insert the name
                            end
                        end
                    end
                end
            end
        end
        return Radars
    end

    function HOUND.Utils.Elint.getActiveRadarsInGroup(GroupName)
        local Radars = {}
        if GroupName == nil then return Radars end
        local group = Group.getByName(GroupName)
        if not HOUND.Utils.Dcs.isGroup(group) then return Radars end
        for _,unit in pairs(group:getUnits()) do
            if (unit:isExist() and unit:isActive() and unit:getRadar()) then
                table.insert(Radars, unit:getName()) -- insert the name
            end
        end
        return Radars
    end

    function HOUND.Utils.Elint.getRwrContacts(platform)
        if not HOUND.Utils.Dcs.isUnit(platform) and not platform:hasSensors(Unit.SensorType.RWR) then return {} end
        local radars = {}
        local platformCoalition = platform:getCoalition()
        local contacts = platform:getController():getDetectedTargets(Controller.Detection.RWR)
        for _,unit in contacts do
            if unit:getCoalition() ~= platformCoalition and unit:getRadar() then
                table.insert(radars,unit:getName())
            end
        end
        return radars
    end

    function HOUND.Utils.Vector.getUnitVector(Theta,Phi)
        if not Theta then
            return {x=0,y=0,z=0}
        end
        Phi = Phi or 0
        local unitVector = {
                x = l_math.cos(Phi)*l_math.cos(Theta),
                z = l_math.cos(Phi)*l_math.sin(Theta),
                y = l_math.sin(Phi)
            }
        return unitVector
    end

    function HOUND.Utils.Vector.getRandomVec2(variance)
        if type(variance) ~= 'number' or variance == 0 then return {x=0,y=0,z=0} end
        local stddev = variance / 2
        local Magnitude = l_math.sqrt(-2 * l_math.log(l_math.random())) * stddev
        local Theta = PI_2 * l_math.random()
        local epsilon = HOUND.Utils.Vector.getUnitVector(Theta)
        for axis,value in pairs(epsilon) do
            epsilon[axis] = value * Magnitude
        end
        return epsilon
    end

    function HOUND.Utils.Vector.getRandomVec3(variance)
        if type(variance) ~= 'number' or variance == 0 then return {x=0,y=0,z=0} end
        local stddev = variance /2
        local Magnitude = l_math.sqrt(-2 * l_math.log(l_math.random())) * stddev
        local Theta = PI_2 * l_math.random()
        local Phi = PI_2 * l_math.random()

        local epsilon = HOUND.Utils.Vector.getUnitVector(Theta,Phi)
        for axis,value in pairs(epsilon) do
            epsilon[axis] = value * Magnitude
        end
        return epsilon
    end

    function HOUND.Utils.Zone.listDrawnZones()
        local zoneNames = {}
        local base = _G.env.mission
        if not base or not base.drawings or not base.drawings.layers then return zoneNames end
        for _,drawLayer in pairs(base.drawings.layers) do
            if type(drawLayer["objects"]) == "table" then
                for _,drawObject in pairs(drawLayer["objects"]) do
                    if drawObject["primitiveType"] == "Polygon" and (HOUND.setContainsValue({"free","rect","oval"},drawObject["polygonMode"])) then
                        table.insert(zoneNames,drawObject["name"])
                    end
                end
            end
        end
        return zoneNames
    end

    function HOUND.Utils.Zone.getDrawnZone(zoneName)
        if type(zoneName) ~= "string" then return nil end
        if not _G.env.mission.drawings or not _G.env.mission.drawings.layers then return nil end
        for _,drawLayer in pairs(_G.env.mission.drawings.layers) do
            if type(drawLayer["objects"]) == "table" then
                for _,drawObject in pairs(drawLayer["objects"]) do
                    if drawObject["name"] == zoneName and drawObject["primitiveType"] == "Polygon" then
                        local points = {}
                        local theta = nil
                        if drawObject["polygonMode"] == "free" and HOUND.Length(drawObject["points"]) >2 then
                            points = l_mist.utils.deepCopy(drawObject["points"])
                            table.remove(points)
                        end
                        if drawObject["polygonMode"] == "rect" then
                            theta = l_math.rad(drawObject["angle"])
                            local w,h = drawObject["width"],drawObject["height"]

                            table.insert(points,{x=h/2,y=w/2})
                            table.insert(points,{x=-h/2,y=w/2})
                            table.insert(points,{x=-h/2,y=-w/2})
                            table.insert(points,{x=h/2,y=-w/2})
                        end
                        if drawObject["polygonMode"] == "oval" then
                            theta = l_math.rad(drawObject["angle"])
                            local r1,r2 = drawObject["r1"],drawObject["r2"]
                            local numPoints = 16
                            local angleStep = PI_2/numPoints

                            for i = 1, numPoints do
                                local pointAngle = PI_2 - (i * angleStep)
                                local x = r1 * l_math.cos(pointAngle)
                                local y = r2 * l_math.sin(pointAngle)
                                table.insert(points,{x=x,y=y})
                            end
                        end
                        if theta then
                            for _,point in pairs(points) do
                                local x = point.x
                                local y = point.y
                                point.x = x * l_math.cos(theta) - y * l_math.sin(theta)
                                point.y = x * l_math.sin(theta) + y * l_math.cos(theta)
                            end
                        end
                        if HOUND.Length(points) < 3 then return nil end
                        local objectX,objecty = drawObject["mapX"],drawObject["mapY"]
                        for _,point in pairs(points) do
                            point.x = point.x + objectX
                            point.y = point.y + objecty
                        end
                        return points
                    end
                end
            end
        end
        return nil
    end

    function HOUND.Utils.Zone.getGroupRoute(GroupName)
        if type(GroupName) == "string" and HOUND.Utils.Dcs.isGroup(Group.getByName(GroupName)) then
            return HOUND.Utils.Dcs.getGroupPoints(Group.getByName(GroupName):getID())
        end
    end

    function HOUND.Utils.Sort.ContactsByRange(a,b)
        if a.isEWR ~= b.isEWR then
          return b.isEWR and not a.isEWR
        end
        if a.maxWeaponsRange ~= b.maxWeaponsRange then
            return a.maxWeaponsRange > b.maxWeaponsRange
        end
        if a.detectionRange ~= b.detectionRange then
            return a.detectionRange > b.detectionRange
        end
        if a.typeAssigned ~= b.typeAssigned then
            return table.concat(a.typeAssigned) < table.concat(b.typeAssigned)
        end
        if a.typeName ~= b.typeName then
            return a.typeName < b.typeName
        end
        if a.first_seen ~= b.first_seen then
            return a.first_seen > b.first_seen
        end
        if getmetatable(a) == HOUND.Contact.Site then
            return a.gid < b.gid
        end
        return a.uid < b.uid
    end

    function HOUND.Utils.Sort.ContactsById(a,b)
        if  a.uid ~= b.uid then
            return a.uid < b.uid
        end
        return a.maxWeaponsRange > b.maxWeaponsRange
    end

    function HOUND.Utils.Sort.ContactsByPrio(a,b)
        if a.isPrimary ~= b.isPrimary then
            return a.isPrimary and not b.isPrimary
        end
        if a.radarRoles ~= b.radarRoles then
            local aRoles,bRoles = 0,0
            for _,role in pairs(a.radarRoles) do
                aRoles = aRoles + role
            end
            for _,role in pairs(b.radarRoles) do
                bRoles = bRoles + role
            end
            return aRoles > bRoles
        end
        return a.uid < b.uid
    end

    function HOUND.Utils.Sort.sectorsByPriorityLowFirst(a,b)
        return a:getPriority() > b:getPriority()
    end

    function HOUND.Utils.Sort.sectorsByPriorityLowLast(a,b)
        return a:getPriority() < b:getPriority()
    end

    function HOUND.Utils.Filter.groupsByPrefix(prefix)
        if type(prefix) ~= "string" then return {} end
        local groups = {}
        for groupName, _ in pairs(HOUND.Utils.Dcs.getGroupNames(prefix)) do
            local dcsObject = Group.getByName(groupName)
            if HOUND.Utils.Dcs.isGroup(dcsObject) then
                groups[groupName] = dcsObject
            end
        end
        return groups
    end

    function HOUND.Utils.Filter.unitsByPrefix(prefix)
        if type(prefix) ~= "string" then return {} end
        local units = {}
        for unitName, _ in pairs(HOUND.Utils.Dcs.getUnitNames(prefix)) do
            local dcsUnit = Unit.getByName(unitName)
            if HOUND.Utils.Dcs.isUnit(dcsUnit) then
                units[unitName] = dcsUnit
            end
        end
        return units
    end

    function HOUND.Utils.Filter.staticObjectsByPrefix(prefix)
        if type(prefix) ~= "string" then return {} end
        local objects = {}
        for objectName, _ in pairs(HOUND.Utils.Dcs.getStaticObjectNames(prefix)) do
            local dcsObject = StaticObject.getByName(objectName)
            if HOUND.Utils.Dcs.isStaticObject(dcsObject) then
                objects[objectName] = dcsObject
            end
        end
        return objects
    end
end
do
    local l_mist = HOUND.Mist
    local l_math = math
    local l_grpc = GRPC
    local PI_2 = 2*l_math.pi

    HOUND.Utils.TTS = {}

    function HOUND.Utils.TTS.isAvailable()
        for _,engine in ipairs(HOUND.TTS_ENGINE) do
            if engine == "GRPC" and (l_grpc ~= nil and type(l_grpc.tts) == "function") then return true end
            if engine == "STTS" and STTS ~= nil then return true end
        end
        return false
    end

    function HOUND.Utils.TTS.getdefaultModulation(freq)
        if not freq then return "AM" end
        if tonumber(freq) ~= nil then
            freq = tonumber(freq)
            if freq < 90 or (freq > 1000000 and freq < (90 * 1000000)) then
                return "FM"
            else
                return "AM"
            end
        end
        if type(freq) == "string" then
            freq = string.split(freq,",")
        end
        if type(freq) == "table" then
            local retval = {}
            for _,frequency in ipairs(freq) do
                table.insert(retval,HOUND.Utils.TTS.getdefaultModulation(tonumber(frequency)))
            end
            return table.concat(retval,",")
        end
        return "AM"
    end

    function HOUND.Utils.TTS.Transmit(msg,coalitionID,args,transmitterPos)
        if not HOUND.Utils.TTS.isAvailable() then return end
        if msg == nil then return end
        if coalitionID == nil then return end

        if args.freq == nil then return end
        args.volume = args.volume or "1.0"
        args.name = args.name or "Hound"
        args.gender = args.gender or "female"
        if type(args.engine) ~= "string" or not HOUND.setContainsValue(HOUND.TTS_ENGINE,args.engine) then
            for _,engine in ipairs(HOUND.TTS_ENGINE) do
                if engine == "GRPC" and (l_grpc ~= nil and type(l_grpc.tts) == "function") then
                    args.engine = engine
                    break
                end
                if engine == "STTS" and STTS ~= nil then
                    args.engine = engine
                    break
                end
            end
        end
        if args.engine == "STTS" then
            return HOUND.Utils.TTS.TransmitSTTS(msg,coalitionID,args,transmitterPos)
        end
        if args.engine == "GRPC" then
            return HOUND.Utils.TTS.TransmitGRPC(msg,coalitionID,args,transmitterPos)
        end
    end

    function HOUND.Utils.TTS.TransmitSTTS(msg,coalitionID,args,transmitterPos)
        args.modulation = args.modulation or HOUND.Utils.TTS.getdefaultModulation(args.freq)
        args.culture = args.culture or "en-US"
        return STTS.TextToSpeech(msg,args.freq,args.modulation,args.volume,args.name,coalitionID,transmitterPos,args.speed,args.gender,args.culture,args.voice,args.googletts,args.azurecreds)
    end

    function HOUND.Utils.TTS.TransmitGRPC(msg,coalitionID,args,transmitterPos)
        local VOLUME = {"default","x-slow", "slow", "medium", "fast", "x-fast"}
        local ssml_msg = msg

        local grpc_ttsArgs = {
            srsClientName = args.name,
            coalition = HOUND.Utils.getCoalitionString(coalitionID):lower(),
        }
        if type(transmitterPos) == "table" then
            grpc_ttsArgs.position = {}
            grpc_ttsArgs.position.lat, grpc_ttsArgs.position.lon, grpc_ttsArgs.position.alt = coord.LOtoLL( transmitterPos )
        end
        if type(args.provider) == "table" then
            grpc_ttsArgs.provider = args.provider
        end

        local readSpeed = 1.0
        if args.speed ~= 0 then
            if args.speed > 10 then
                readSpeed = HOUND.Utils.Mapping.linear(args.speed,50,250,0.5,2.5,true)
            else
                if args.speed > 0 then
                    readSpeed = HOUND.Utils.Mapping.linear(args.speed,0,10,1.0,2.5,true)
                else
                    readSpeed = HOUND.Utils.Mapping.linear(args.speed,-10,0,0.5,1.0,true)
                end
            end
        end

        local ssml_prosody = ""
        if readSpeed ~= 1.0  then
            ssml_prosody = ssml_prosody .. " rate='"..readSpeed.."'"
        end

        if args.volume ~= 1.0 then
            local volume = ""

            if HOUND.setContainsValue(VOLUME,args.volume) then
                volume = args.volume
            end

            if type(args.volume)=="number" then
                if args.volume ~= 0 then
                    volume = (args.volume*100)-100 .. "%"
                    if args.volume > 1 then
                        volume = "+" .. volume
                    end
                else
                    volume = "slient"
                end
            end

            if string.len(volume) > 0 then
                ssml_prosody = ssml_prosody .. " volume='"..volume.."'"
            end
        end
        if string.len(ssml_prosody) > 0 then
            ssml_msg = table.concat({"<prosody",ssml_prosody,">",ssml_msg,"</prosody>"},"")
        end

        local ssml_voice = ""
        if args.voice then
            ssml_voice = ssml_voice.." name='"..args.voice.."'"
        else
            if args.gender then
                ssml_voice = ssml_voice.." gender='"..args.gender.."'"
            end
            if args.culture then
                ssml_voice = ssml_voice.." language='"..args.culture.."'"
            end
        end

        if string.len(ssml_voice) > 0 then
            ssml_msg = table.concat({"<voice",ssml_voice,">",ssml_msg,"</voice>"},"")
        end

        local freqs = string.split(args.freq,",")

        for _,freq in ipairs(freqs) do
            freq = math.ceil(freq * 1000000)
            l_grpc.tts(ssml_msg, freq, grpc_ttsArgs)
        end
        return HOUND.Utils.TTS.getReadTime(msg) / readSpeed -- read speed > 1.0 is fast
    end

    function HOUND.Utils.TTS.getTtsTime(timestamp)
        if timestamp == nil then timestamp = timer.getAbsTime() end
        local DHMS = l_mist.time.getDHMS(timestamp)
        local hours = DHMS.h
        local minutes = DHMS.m
        if hours == 0 then
            hours = HOUND.DB.PHONETICS["0"]
        else
            hours = string.format("%02d",hours)
        end

        if minutes == 0 then
            minutes = "hundred"
        else
            minutes = string.format("%02d",minutes)
        end

        return hours .. " " .. minutes .. " Local"
    end

    function HOUND.Utils.TTS.getVerbalConfidenceLevel(confidenceRadius)
        if confidenceRadius == 0.1 then return "Precise" end

        local score={
            "Very High", -- 500
            "High", -- 1000
            "Medium", -- 1500
            "Low", -- 2000
            "Low", -- 2500
            "Very Low", -- 3000
            "Very Low", -- 3500
            "Very Low", -- 4000
            "Very Low", -- 4500
            "Unactionable", -- 5000
        }
        return score[l_math.min(#score,l_math.max(1,l_math.floor(confidenceRadius/500)+1))]
    end

    function HOUND.Utils.TTS.getVerbalContactAge(timestamp,isSimple,NATO)
        local ageSeconds = HOUND.Utils.absTimeDelta(timestamp,timer.getAbsTime())

        if isSimple then
            if NATO then
                if ageSeconds < 16 then return "Active" end
                if ageSeconds < HOUND.CONTACT_TIMEOUT then return "Down" end
                return "Asleep"
            end
            if ageSeconds < 16 then return "Active" end
            if ageSeconds <= 90 then return "very recent" end
            if ageSeconds <= 180 then return "recent" end
            if ageSeconds <= 300 then return "relevant" end
            return "stale"
        end
        local DHMS = l_mist.time.getDHMS(ageSeconds)
        if ageSeconds < 60 then return tostring(l_math.floor(DHMS.s)) .. " seconds" end
        if ageSeconds < 7200 then return tostring(l_math.floor(DHMS.h)*60+l_math.floor(DHMS.m)) .. " minutes" end
        return tostring(l_math.floor(DHMS.h)) .. " hours, " .. tostring(l_math.floor(DHMS.m)) .. " minutes"
    end

    function HOUND.Utils.TTS.DecToDMS(cood,minDec,padDeg)
        local DMS = HOUND.Utils.DecToDMS(cood)
        local strTab = {
            l_math.abs(DMS.d) .. " degrees",
            string.format("%02d",DMS.m) .. " minutes",
            string.format("%02d",DMS.s) .. " seconds"
        }
        if padDeg == true then
            strTab[1] = string.format("%03d",l_math.abs(DMS.d)) .. " degrees"
        end
        if minDec == true then
            strTab[2] = string.format("%02d",DMS.m)
            strTab[3] = HOUND.Utils.TTS.toPhonetic( "." .. string.format("%03d",DMS.sDec)) .. " minutes"
        end
        return table.concat(strTab,", ")
    end

    function HOUND.Utils.TTS.getVerbalLL(lat,lon,minDec)
        minDec = minDec or false
        local hemi = HOUND.Utils.getHemispheres(lat,lon,true)
        return hemi.NS .. ", " .. HOUND.Utils.TTS.DecToDMS(lat,minDec)  ..  ", " .. hemi.EW .. ", " .. HOUND.Utils.TTS.DecToDMS(lon,minDec,true)
    end

    function HOUND.Utils.TTS.toPhonetic(str)
        local retval = ""
        str = string.upper(tostring(str))
        for i=1, string.len(str) do
            local char = HOUND.DB.PHONETICS[string.sub(str, i, i)] or ""
            retval = retval .. char .. " "
        end
        return retval:match( "^%s*(.-)%s*$" ) -- return and strip trailing whitespaces
    end

    function HOUND.Utils.TTS.getReadTime(length,speed,googleTTS)
        if length == nil then return nil end
        local maxRateRatio = 3 -- can be chaned to 5 if windows TTSrate is up to 5x not 4x

        speed = speed or 1.0
        googleTTS = googleTTS or false

        local speedFactor = 1.0
        if googleTTS then
            speedFactor = speed
        else
            if speed ~= 0 then
                speedFactor = l_math.abs(speed) * (maxRateRatio - 1) / 10 + 1
            end
            if speed < 0 then
                speedFactor = 1/speedFactor
            end
        end

        local wpm = l_math.ceil(100 * speedFactor)
        local cps = l_math.floor((wpm * 5)/60)

        if type(length) == "string" then
            length = string.len(length)
        end

        return l_math.ceil(length/cps)
    end

    function HOUND.Utils.TTS.simplfyDistance(distanceM)
        local distanceUnit = "meters"
        local distance = HOUND.Utils.roundToNearest(distanceM,50) or 0
        if distance >= 1000 then
            distance = string.format("%.1f",tostring(HOUND.Utils.roundToNearest(distanceM,100)/1000))
            distanceUnit = "kilometers"
        end
        return distance .. " " .. distanceUnit
    end
end    --- HOUND.Utils
do
    local l_mist = HOUND.Mist
    local l_math = math
    local PI_2 = 2*l_math.pi

    HOUND.Utils.Polygon ={}
    HOUND.Utils.Cluster = {}

    function HOUND.Utils.Polygon.threatOnSector(polygon,point, radius)
        if type(polygon) ~= "table" or HOUND.Length(polygon) < 3 or not HOUND.Utils.Dcs.isPoint(l_mist.utils.makeVec3(polygon[1])) then
            return
        end
        if not HOUND.Utils.Dcs.isPoint(point) then
            return
        end
        local inPolygon = l_mist.pointInPolygon(point,polygon)
        local intersectsPolygon = inPolygon

        if radius ~= nil and radius > 0 and l_mist.shape ~= nil then
            local circle={point=point,radius=radius}
            intersectsPolygon = l_mist.shape.insideShape(circle,polygon)
        end
        return inPolygon,intersectsPolygon
    end

    function HOUND.Utils.Polygon.filterPointsByPolygon(points,polygon)
        local filteredPoints = {}
        if type(points) ~= "table" or type(polygon) ~= "table" then return filteredPoints end

        for _,point in pairs(points) do
            if l_mist.pointInPolygon(point,polygon) then
                table.insert(filteredPoints,point)
            end
        end
        return filteredPoints
    end

    function HOUND.Utils.Polygon.clipPolygons(subjectPolygon, clipPolygon)
        local function inside (p, cp1, cp2)
            return (cp2.x-cp1.x)*(p.z-cp1.z) > (cp2.z-cp1.z)*(p.x-cp1.x)
        end

        local function intersection (cp1, cp2, s, e)
            local dcx, dcz = cp1.x-cp2.x, cp1.z-cp2.z
            local dpx, dpz = s.x-e.x, s.z-e.z
            local n1 = cp1.x*cp2.z - cp1.z*cp2.x
            local n2 = s.x*e.z - s.z*e.x
            local n3 = 1 / (dcx*dpz - dcz*dpx)
            local x = (n1*dpx - n2*dcx) * n3
            local z = (n1*dpz - n2*dcz) * n3
            return {x=x, z=z}
        end

        if type(subjectPolygon) ~= "table" or type(clipPolygon) ~= "table" then return end

        local outputList = subjectPolygon
        local cp1 = clipPolygon[#clipPolygon]
        for _, cp2 in ipairs(clipPolygon) do  -- WP clipEdge is cp1,cp2 here
        local inputList = outputList
        outputList = {}
        local s = inputList[#inputList]
        for _, e in ipairs(inputList) do
            if inside(e, cp1, cp2) then
            if not inside(s, cp1, cp2) then
                outputList[#outputList+1] = intersection(cp1, cp2, s, e)
            end
            outputList[#outputList+1] = e
            elseif inside(s, cp1, cp2) then
            outputList[#outputList+1] = intersection(cp1, cp2, s, e)
            end
            s = e
        end
        cp1 = cp2
        end
        if HOUND.Length(outputList) > 0 then
            return outputList
        end
        return nil
    end

    function HOUND.Utils.Polygon.giftWrap(points)
        local function signedArea(p, q, r)
            local cross = (q.z - p.z) * (r.x - q.x)
                        - (q.x - p.x) * (r.z - q.z)
            return cross
        end
        local function isCCW(p, q, r) return signedArea(p, q, r) < 0 end

        local numPoints = #points
        if numPoints < 3 then
            return
        end

        local leftMostPointIndex = 1
        for i = 1, numPoints do
            if points[i].x < points[leftMostPointIndex].x then
                leftMostPointIndex = i
            end
        end

        local p = leftMostPointIndex
        local hull = {} -- The convex hull to be returned

        repeat
            local q = points[p + 1] and p + 1 or 1
            for i = 1, numPoints, 1 do
                if isCCW(points[p], points[i], points[q]) then q = i end
            end

            table.insert(hull, points[q]) -- Save q to the hull
            p = q  -- p is now q for the next iteration
        until (p == leftMostPointIndex)

        return hull
    end

    function HOUND.Utils.Polygon.circumcirclePoints(points)
        local function calcCircle(p1,p2,p3)
            local cx,cz, r
            if HOUND.Utils.Dcs.isPoint(p1) and not p2 and not p3 then
                return {x = p1.x, z = p1.z,r = 0}
            end
            if HOUND.Utils.Dcs.isPoint(p1) and HOUND.Utils.Dcs.isPoint(p2) and not p3 then
                cx = 0.5 * (p1.x + p2.x)
                cz = 0.5 * (p1.z + p2.z)
            else
                local a = p2.x - p1.x
                local b = p2.z - p1.z
                local c = p3.x - p1.x
                local d = p3.z - p1.z
                local e = a * (p2.x + p1.x) * 0.5 + b * (p2.z + p1.z) * 0.5
                local f = c * (p3.x + p1.x) * 0.5 + d * (p3.z + p1.z) * 0.5
                local det = a * d - b * c

                cx = (d * e - b * f) / det
                cz = (-c * e + a * f) / det
            end

            r = l_math.sqrt((p1.x - cx) * (p1.x - cx) + (p1.z - cz) * (p1.z - cz))
            return {x=cx,z=cz,r=r}
        end

        local function isInCircle(p,c)
            return ((c.x - p.x) * (c.x - p.x) + (c.z - p.z) * (c.z - p.z) <= c.r * c.r)
        end

        local function shuffle(a)
            for i = #a, 2, -1 do
                local j = l_math.random(i)
                a[i], a[j] = a[j], a[i]
            end
            return a
        end

        local function mec(pts,n,boundary,b)
            local circle
            if b == 3 then
                circle = calcCircle(boundary[1],boundary[2],boundary[3])
            elseif (n == 1) and (b == 0) then circle = calcCircle(pts[1])
            elseif (n == 0) and (b == 2) then circle = calcCircle(boundary[1], boundary[2])
            elseif (n == 1) and (b == 1) then circle = calcCircle(boundary[1], pts[1])
            else
                circle = mec(pts, n-1, boundary, #boundary)
                if ( not isInCircle(pts[n], circle)) then
                    boundary[b+1] = pts[n]
                    circle = mec(pts, n-1, boundary, #boundary)
                end
            end
            return circle
        end

        local clonedPoints = l_mist.utils.deepCopy(points)
        shuffle(clonedPoints)
        return mec(clonedPoints, #points, {}, 0)
    end

    function HOUND.Utils.Polygon.getArea(polygon)
        if not polygon or type(polygon) ~= "table" or HOUND.Length(polygon) < 2 then return 0 end
        local a,b = 0,0
        for i=1,HOUND.Length(polygon)-1 do
            a = a + polygon[i].x * polygon[i+1].z
            b = b + polygon[i].z * polygon[i+1].x
        end
        a = a + polygon[HOUND.Length(polygon)].x * polygon[1].z
        b = b + polygon[HOUND.Length(polygon)].z * polygon[1].x
        return l_math.abs((a-b)/2)
    end

    function HOUND.Utils.Polygon.clipOrHull(polyA,polyB)
        if HOUND.Utils.Polygon.getArea(polyA) < HOUND.Utils.Polygon.getArea(polyB) then
            polyA,polyB = polyB,polyA
        end
        local polygon = HOUND.Utils.Polygon.clipPolygons(polyA,polyB)
        if Polygon == nil then
            local points = l_mist.utils.deepCopy(polyA)
            for _,point in pairs(polyB) do
                table.insert(points,l_mist.utils.deepCopy(point))
            end
            polygon = HOUND.Utils.Polygon.giftWrap(points)
        end
        return polygon
    end

    function HOUND.Utils.Polygon.azMinMax(poly,refPos)
        if not HOUND.Utils.Dcs.isPoint(refPos) or type(poly) ~= "table" or HOUND.Length(poly) < 2 or l_mist.pointInPolygon(refPos,poly) then
            return
        end

        local points = l_mist.utils.deepCopy(poly)
        for _,pt in pairs(points) do
            pt.refAz = l_mist.utils.getDir(l_mist.vec.sub(pt,refPos))
        end

        table.sort(points,function (a,b) return (a.refAz+PI_2) < (b.refAz+PI_2) end)
        local leftMost = table.remove(points,1)
        local rightMost = table.remove(points)
        return HOUND.Utils.angleDeltaRad(leftMost.refAz,rightMost.refAz),(leftMost),(rightMost)
    end

    function HOUND.Utils.Cluster.gaussianKernel(value,bandwidth)
        return (1/(bandwidth*l_math.sqrt(2*l_math.pi))) * l_math.exp(-0.5*((value / bandwidth))^2)
    end

    function HOUND.Utils.Cluster.stdDev()
        local instance = {}
        instance.count = 0
        instance.mean = 0
        instance.M2 = 0
        instance.update = function(self,value)
            self.count = self.count + 1
            local delta = value - self.mean
            self.mean = self.mean + (delta / self.count)
            local delta2 = value - self.mean
            self.M2 = self.M2 + (delta * delta2)
        end
        instance.get = function (self)
            if self.count < 2 then return nil end
            return {
                mean = self.mean,
                variance = (self.M2/self.count),
                sampleVariance = (self.M2/(self.count-1))
            }
        end
        return instance
    end

    function HOUND.Utils.Cluster.weightedMean(origPoints,initPos,threashold,maxIttr)
        if type(origPoints) ~= "table" or not HOUND.Utils.Dcs.isPoint(origPoints[1]) then return end
        local points = HOUND.Utils.Geo.setHeight(l_mist.utils.deepCopy(origPoints))
        if HOUND.Length(points) == 1 then return l_mist.utils.deepCopy(points[1]) end

        local current_mean = initPos
        if type(current_mean) == "boolean" and current_mean then
            current_mean = points[l_math.random(HOUND.Length(points))]
        end
        if not HOUND.Utils.Dcs.isPoint(current_mean) then
            current_mean = l_mist.getAvgPoint(origPoints)
        end
        if not HOUND.Utils.Dcs.isPoint(current_mean) then return end
        threashold = threashold or 10
        maxIttr = maxIttr or 50
        local last_mean
        local ittr = 0
        local converged = false

        while not converged do
            last_mean = l_mist.utils.deepCopy(current_mean)
            local totalDist = 0
            local totalInvWeight = 0
            for _,point in pairs(points) do
                point.dist = l_mist.utils.get2DDist(last_mean,point)
                totalDist = totalDist + point.dist
            end
            for _,point in pairs(points) do
                point.w = 1/(point.dist/totalDist)
                totalInvWeight = totalInvWeight + point.w
            end

            for _,point in pairs(points) do
                local weight = point.w/totalInvWeight
                current_mean = l_mist.vec.add(current_mean,l_mist.vec.scalar_mult(l_mist.vec.sub(point,current_mean),weight))
            end
            ittr = ittr + 1
            converged = l_mist.utils.get2DDist(last_mean,current_mean) < threashold or ittr == maxIttr
        end
        HOUND.Utils.Geo.setHeight(current_mean)
        return l_mist.utils.deepCopy(current_mean)
    end

    function HOUND.Utils.Cluster.getDeltaSubsetPercent(Table,referencePos,NthPercentile,returnRelative)
        local t = l_mist.utils.deepCopy(Table)
        local len_t = HOUND.Length(t)
        t = HOUND.Utils.Geo.setHeight(t)
        if not referencePos then
            referencePos = l_mist.getAvgPoint(t)
        end
        for _,pt in ipairs(t) do
            pt.dist = l_mist.utils.get2DDist(referencePos,pt)
        end
        table.sort(t,function(a,b) return a.dist < b.dist end)

        local percentile = l_math.floor(len_t*NthPercentile)
        local NumToUse = l_math.max(l_math.min(2,len_t),percentile)
        local returnTable = {}
        for i = 1, NumToUse  do
            table.insert(returnTable,t[i])
        end
        if returnRelative then
            for i = 1,#returnTable do
                returnTable[i] = l_mist.vec.sub(returnTable[i],referencePos)
            end
        end

        return returnTable
    end

    function HOUND.Utils.Cluster.WeightedCentroid(PosList)
        local sumWeights = 0
        local estimate = {z=0,x=0}
        for _,pos in ipairs(PosList) do
            if HOUND.Utils.Dcs.isPoint(pos) and pos.score > 0 then
                local w = pos.score
                sumWeights = sumWeights + w
                estimate.z = estimate.z + (w * (pos.z - estimate.z)) / sumWeights
                estimate.x = estimate.x + (w * (pos.x - estimate.x)) / sumWeights
            end
        end
        estimate.y = land.getHeight({x=estimate.x, y=estimate.z}) or 0
        return estimate
    end

    function HOUND.Utils.Cluster.WLS_GDOP(measurements, initial_guess, max_iter, tol)
        local l_math = math
        local x, z = initial_guess.x, initial_guess.z
        max_iter = max_iter or 10
        tol = tol or 1

        local function computeJacobian(x, z, xi, zi)
            local dx = x - xi
            local dz = z - zi
            local r2 = dx*dx + dz*dz
            return {-dz/r2, dx/r2}
        end

        local H, y, W
        local Cov

        for iter = 1, max_iter do
            H, y, W = {}, {}, {}

            for i, m in ipairs(measurements) do

                local jac = computeJacobian(x, z, m.platformPos.x, m.platformPos.z)
                local pred_theta = l_math.atan2(z - m.platformPos.z, x - m.platformPos.x)
                local residual = (m.az - pred_theta + l_math.pi) % (2*l_math.pi) - l_math.pi

                H[#H+1] = jac
                y[#y+1] = {residual}
                local sigma2 = (m.platformPrecision or 1)^2 * (m.gdop or 1)
                W[#W+1] = {1/sigma2}
            end

            local Hmat = HOUND.Matrix(H)
            local ymat = HOUND.Matrix(y)
            local Wmat = HOUND.Matrix:new(#W, #W)
            for i=1,#W do Wmat[i][i] = W[i][1] end

            local Ht = HOUND.Matrix.transpose(Hmat)
            local HtW = HOUND.Matrix.mul(Ht, Wmat)
            local HtWH = HOUND.Matrix.mul(HtW, Hmat)
            local HtWy = HOUND.Matrix.mul(HtW, ymat)
            local HtWH_inv = HOUND.Matrix.invert(HtWH)
            if not HtWH_inv then break end
            Cov = HtWH_inv -- always keep the last HtWH_inv in the Cov matrix

            local delta = HOUND.Matrix.mul(HtWH_inv, HtWy)
            local dx, dz = delta[1][1], delta[2][1]

            x = x + dx
            z = z + dz
            if l_math.abs(dx) < tol and l_math.abs(dz) < tol then break end
        end

        local a = Cov[1][1]
        local b = Cov[1][2]
        local c = Cov[2][2]
        local trace = a + c
        local det = a * c - b * b
        local eig1 = trace/2 + l_math.sqrt((trace*trace)/4 - det)
        local eig2 = trace/2 - l_math.sqrt((trace*trace)/4 - det)

        local major = l_math.sqrt(l_math.max(eig1, eig2))
        local minor = l_math.sqrt(l_math.min(eig1, eig2))
        local theta = 0.5 * l_math.atan2(2*b, a - c)

        local uncertenty_data = {}
        uncertenty_data.major = HOUND.Mist.utils.round(major)
        uncertenty_data.minor = HOUND.Mist.utils.round(minor)
        uncertenty_data.theta = (theta + math.pi*2) % math.pi -- [0, pi]
        uncertenty_data.az = HOUND.Mist.utils.round(l_math.deg(uncertenty_data.theta))
        uncertenty_data.r  = (major + minor) / 2

        local solution = {x = x, z = z, y = land.getHeight({x=x, y=z}) or 0}

        return solution, uncertenty_data
    end
end    --- HOUND.EventHandler
do
    HOUND.EventHandler = {
        idx = 0,
        subscribers = {},
        _internalSubscribers = {},
        subscribeOn = {}
    }

    HOUND.EventHandler.__index = HOUND.EventHandler

    function HOUND.EventHandler.addEventHandler(handler)
        if type(handler) == "table" then
            HOUND.EventHandler.subscribers[handler] = handler
        end
    end

    function HOUND.EventHandler.removeEventHandler(handler)
        HOUND.EventHandler.subscribers[handler] = nil
        for eventType,_ in pairs(HOUND.EventHandler.subscribeOn) do
            HOUND.EventHandler.subscribeOn[eventType][handler] = nil
        end
    end

    function HOUND.EventHandler.addInternalEventHandler(handler)
        if type(handler) == "table" then
            HOUND.EventHandler._internalSubscribers[handler] = handler
        end
    end

    function HOUND.EventHandler.removeInternalEventHandler(handler)
        if HOUND.setContains(HOUND.EventHandler._internalSubscribers,handler) then
            HOUND.EventHandler._internalSubscribers[handler] = nil
        end
    end

    function HOUND.EventHandler.on(eventType,handler)
        if type(handler) == "function" then
            if not HOUND.EventHandler.subscribeOn[eventType] then
                HOUND.EventHandler.subscribeOn[eventType] = {}
            end
            HOUND.EventHandler.subscribeOn[eventType][handler] = handler
        end
    end

    function HOUND.EventHandler.onHoundEvent(event)
        for _, handler in pairs(HOUND.EventHandler._internalSubscribers) do
            if handler and getmetatable(handler) == HoundElint and handler:getId() == event.houndId then
                if handler.onHoundInternalEvent and type(handler.onHoundInternalEvent) == "function" then
                    handler:onHoundInternalEvent(event)
                end
                if handler.onHoundEvent and type(handler.onHoundEvent) == "function" then
                    handler:onHoundEvent(event)
                end
            end
        end
        for _, handler in pairs(HOUND.EventHandler.subscribers) do
            if handler.onHoundEvent and type(handler.onHoundEvent) == "function" then
                handler:onHoundEvent(event)
            end
        end
    end

    function HOUND.EventHandler.publishEvent(event)
        if not event.time then
            event.time = timer.getTime()
        end
        HOUND.EventHandler.onHoundEvent(event)
    end

    function HOUND.EventHandler.getIdx()
        HOUND.EventHandler.idx = HOUND.EventHandler.idx + 1
        return  HOUND.EventHandler.idx
    end
end
do
    HOUND.Contact.Base = {}
    HOUND.Contact.Base.__index = HOUND.Contact.Base

    local HoundUtils = HOUND.Utils

    function HOUND.Contact.Base:New(DcsObject,HoundCoalition)
        if not DcsObject or type(DcsObject) ~= "table" or not DcsObject.getName or not HoundCoalition then
            HOUND.Logger.warn("failed to create HOUND.Contact instance")
            return
        end
        local instance = {}
        setmetatable(instance, HOUND.Contact.Base)
        instance.DcsObject = DcsObject
        instance.DcsGroupName = nil
        instance.DcsObjectName = nil
        instance.typeAssigned = {"Unknown"}

        instance.pos = {
            p = nil,
            grid = nil,
            LL = {
                lat = nil,
                lon = nil,
            },
            be = {
                brg = nil,
                rng = nil
            }
        }
        instance.uncertenty_data = nil
        instance.last_seen = timer.getAbsTime()
        instance.first_seen = timer.getAbsTime()
        instance.maxWeaponsRange = 0
        instance.detectionRange = 0

        instance._platformCoalition = HoundCoalition
        instance.primarySector = "default"
        instance.threatSectors = {
            default = true
        }
        instance.state = nil
        instance.preBriefed = false
        instance.events = {}
        instance._markpoints = {
            pos = HoundUtils.Marker.create(),
            area = HoundUtils.Marker.create()
        }
        return instance
    end

    function HOUND.Contact.Base:destroy()
        HOUND.Logger.error("HOUND.Contact.Base:destroy() prototype envoked. please override")
    end

    function HOUND.Contact.Base:getDcsGroupName()
        return self.DcsGroupName
    end

    function HOUND.Contact.Base:getDcsName()
        return self.DcsObjectName
    end

    function HOUND.Contact.Base:getDcsObject()
        return self.DcsObject or self.DcsObjectName
    end
    function HOUND.Contact.Base:getLastSeen()
        return HoundUtils.absTimeDelta(self.last_seen)
    end

    function HOUND.Contact.Base:getObject()
        return self.DcsObject
    end
    function HOUND.Contact.Base:hasPos()
        return HoundUtils.Dcs.isPoint(self.pos.p)
    end

    function HOUND.Contact.Base:getMaxWeaponsRange()
        return self.maxWeaponsRange
    end

    function HOUND.Contact.Base:getRadarDetectionRange()
        return self.detectionRange
    end

    function HOUND.Contact.Base:getTypeAssigned()
        return table.concat(self.typeAssigned," or ")
    end

    function HOUND.Contact.Base:getDesignation(NATO)
        if not NATO then return self:getTypeAssigned() end
        local natoDesignation = string.gsub(self:getTypeAssigned(),"(SA)-",'')
        if natoDesignation == "Naval" then
            natoDesignation = self:getType()
        end
        return natoDesignation
    end

    function HOUND.Contact.Base:getNatoDesignation()
        return self:getDesignation(true)
    end

    function HOUND.Contact.Base:isActive()
        return self:getLastSeen()/16 < 1.0
    end

    function HOUND.Contact.Base:isRecent()
        return self:getLastSeen()/120 < 1.0
    end

    function HOUND.Contact.Base:isAccurate()
        return self.preBriefed
    end

    function HOUND.Contact.Base:getPreBriefed()
        return self.preBriefed
    end

    function HOUND.Contact.Base:setPreBriefed(state)
        if type(state) == "boolean" then
            self.preBriefed = state
            if not self.preBriefed then
                if type(self.detected_by) == "table" then
                    for i,v in ipairs(self.detected_by) do
                        if v == "External" then
                            table.remove(self.detected_by,i)
                            break
                        end
                    end
                end
            end
        end
    end

    function HOUND.Contact.Base:isTimedout()
        return self:getLastSeen() > HOUND.CONTACT_TIMEOUT
    end

    function HOUND.Contact.Base:getState()
        return self.state
    end

    function HOUND.Contact.Base:queueEvent(eventId)
        if eventId == HOUND.EVENTS.NO_CHANGE then return end
        local event = {
            id = eventId,
            initiator = self,
            time = timer.getTime()
        }
        table.insert(self.events,event)
    end

    function HOUND.Contact.Base:getEventQueue()
        return self.events
    end

    function HOUND.Contact.Base:getPrimarySector()
        return self.primarySector
    end

    function HOUND.Contact.Base:getSectors()
        return self.threatSectors
    end

    function HOUND.Contact.Base:isInSector(sectorName)
        return self.threatSectors[sectorName] or false
    end

    function HOUND.Contact.Base:updateDefaultSector()
        self.threatSectors[self.primarySector] = true
        if self.primarySector == "default" then return end
        for k,v in pairs(self.threatSectors) do
            if k ~= "default" and v == true then
                self.threatSectors["default"] = false
                return
            end
        end
        self.threatSectors["default"] = true
    end

    function HOUND.Contact.Base:updateSector(sectorName,inSector,threatsSector)
        if inSector == nil and threatsSector == nil then
            return
        end
        self.threatSectors[sectorName] = threatsSector or false

        if inSector and self.primarySector ~= sectorName then
            self.primarySector = sectorName
            self.threatSectors[sectorName] = true
        end
        self:updateDefaultSector()
    end

    function HOUND.Contact.Base:addSector(sectorName)
        self.threatSectors[sectorName] = true
        self:updateDefaultSector()
    end

    function HOUND.Contact.Base:removeSector(sectorName)
        if self.threatSectors[sectorName] then
            self.threatSectors[sectorName] = false
            self:updateDefaultSector()
        end
    end

    function HOUND.Contact.Base:isThreatsSector(sectorName)
        return self.threatSectors[sectorName] or false
    end

    function HOUND.Contact.Base:removeMarkers()
        for _,marker in pairs(self._markpoints) do
            marker:remove()
        end
    end
end--- HOUND.Contact.Estimator
do
    local l_math = math
    local TwoPI = 2 * l_math.pi
    local HalfPi = l_math.pi / 2
    local HoundUtils = HOUND.Utils
    local matrix = HOUND.Matrix

    HOUND.Contact.Estimator = {}
    HOUND.Contact.Estimator.__index = HOUND.Contact.Estimator

    HOUND.Contact.Estimator.Kalman = {}

    function HOUND.Contact.Estimator.accuracyScore(err)
        local score = 0
        if type(err) == "number" then
            score = HoundUtils.Mapping.linear(err, 0, 100000, 1, 0, true)
            score = HoundUtils.Cluster.gaussianKernel(score, 0.2)
        end
        if type(score) == "number" then
            return score
        else
            return 0
        end
    end

    function HOUND.Contact.Estimator.Kalman.posFilter()
        local Kalman = {}

        Kalman.P = {
            x = 0.5,
            z = 0.5
        }

        Kalman.estimated = {}

        Kalman.update = function(self, datapoint)
            if type(self.estimated.p) ~= "table" and HoundUtils.Dcs.isPoint(datapoint) then
                self.estimated.p = {
                    x = datapoint.x,
                    z = datapoint.z,
                    y = datapoint.y
                }
            end

            if type(datapoint.err.score) ~= "table" then
                return self.estimated.p
            end
            self.P.x = self.P.x + math.sqrt(datapoint.err.score.x)
            self.P.z = self.P.z + math.sqrt(datapoint.err.score.z)

            local Kx = self.P.x / (self.P.x + (datapoint.err.score.x))
            local Kz = self.P.z / (self.P.z + (datapoint.err.score.z))

            self.estimated.p.x = self.estimated.p.x + (Kx * (datapoint.x - self.estimated.p.x))
            self.estimated.p.z = self.estimated.p.z + (Kz * (datapoint.z - self.estimated.p.z))

            self.P.x = (1 - Kx) * self.P.x
            self.P.z = (1 - Kz) * self.P.z

            self.estimated.p = HoundUtils.Geo.setHeight(self.estimated.p)
            return self.estimated.p
        end

        Kalman.get = function(self)
            return self.estimated.p
        end

        return Kalman
    end

    function HOUND.Contact.Estimator.Kalman.AzFilter(noise)
        local Kalman = {}
        Kalman.P = 0.5
        Kalman.noise = noise

        Kalman.estimated = nil

        Kalman.update = function(self, newAz, predictedAz, processNoise)
            if not self.estimated then
                self.estimated = newAz
            end
            local predAz = self.estimated
            local noiseP = self.noise
            if type(predictedAz) == "number" then
                predAz = predictedAz
            end
            if type(processNoise) == "number" then
                noiseP = processNoise
            end

            self.P = self.P + l_math.sqrt(noiseP) -- add "process noise" in the form of standard diviation
            local K = self.P / (self.P + self.noise)
            local deltaAz = newAz - predAz
            self.estimated = ((self.estimated + K * (deltaAz)) + TwoPI) % TwoPI
            self.P = (1 - K) * self.P
        end

        Kalman.get = function(self)
            return self.estimated
        end

        return Kalman
    end

    function HOUND.Contact.Estimator.Kalman.AzElFilter()
        local Kalman = {}
        Kalman.K = {
            Az = 0,
            El = 0
        }
        Kalman.P = {
            Az = 1,
            El = 1
        }
        Kalman.estimated = {
            pos = nil,
            Az = nil,
            El = nil
        }

        Kalman.reset = function(self)
            self.P = {
                Az = 1,
                El = 1
            }
        end

        Kalman.update = function(self, datapoint)
            if not self.estimated.pos then
                self.estimated.Az = (1 / self.P.Az) * datapoint.az
                self.estimated.El = (1 / self.P.El) * datapoint.el
                self.estimated.pos = HoundUtils.Geo.getProjectedIP(datapoint.platformPos, self.estimated.Az,
                    self.estimated.El)
                return self.estimated
            end
            local prediction = self:predict(datapoint)

            local errEstimate = {
                Az = datapoint.platformPrecision,
                El = datapoint.platformPrecision
            }

            self.K.Az = self.P.Az / (self.P.Az + errEstimate.Az)
            self.K.El = self.P.El / (self.P.El + errEstimate.El)

            self.estimated.Az = self.estimated.Az + (self.K.Az * (datapoint.az - prediction.Az))
            self.estimated.El = self.estimated.El + (self.K.El * (datapoint.el - prediction.El))
            self.estimated.pos = HoundUtils.Geo.getProjectedIP(datapoint.platformPos, self.estimated.Az,
                self.estimated.El)

            self.P.Az = (1 - self.K.Az)
            self.P.El = (1 - self.K.El)

            return self.estimated
        end

        Kalman.predict = function(self, datapoint)
            local prediction = {}
            prediction.Az, prediction.El = HoundUtils.Elint.getAzimuth(datapoint.platformPos, self.estimated.pos, 0)
            return prediction
        end

        Kalman.getValue = function(self)
            return self.estimated
        end

        return Kalman
    end

    HOUND.Contact.Estimator.UPLKF = {}
    HOUND.Contact.Estimator.UPLKF.__index = HOUND.Contact.Estimator.UPLKF

    function HOUND.Contact.Estimator.UPLKF:create(p0, v0, timestamp, initialPosError, isMobile)
        if not HoundUtils.Dcs.isPoint(p0) then return nil end
        local instance = {}
        setmetatable(instance, HOUND.Contact.Estimator.UPLKF)
        instance.t0 = timestamp or timer.getAbsTime()
        instance.mobile = isMobile or false
        instance._maxNoise = 0
        v0 = v0 or { z = 0, x = 0 }

        instance.state = matrix({
            { p0.x },
            { p0.z },
            { v0.x },
            { v0.z }
        })
        local position_accuracy = initialPosError or 10000
        position_accuracy = l_math.min(position_accuracy, 10000)
        local velocity_accuracy = instance.mobile and 30 or 1
        instance.P = matrix({
            { l_math.pow(position_accuracy, 2), 0,                                0,                                0 },
            { 0,                                l_math.pow(position_accuracy, 2), 0,                                0 },
            { 0,                                0,                                l_math.pow(velocity_accuracy, 2), 0 },
            { 0,                                0,                                0,                                l_math.pow(velocity_accuracy, 2) }
        })

        if HOUND.DEBUG then
            instance.marker = HoundUtils.Marker.create()
            trigger.action.outText("new KF: x:" .. instance.state[2][1] .. "| y: " .. instance.state[1][1], 20)
        end
        return instance
    end

    function HOUND.Contact.Estimator.UPLKF:getEstimatedPos(state)
        local X_k = state or self.state
        local pos = { x = X_k[1][1], z = X_k[2][1] }
        if HoundUtils.Dcs.isPoint(pos) then
            pos = HoundUtils.Geo.setPointHeight(pos)
            return pos
        end
    end

    function HOUND.Contact.Estimator.UPLKF.normalizeAz(azimuth)
        local bearing = (HalfPi - azimuth + TwoPI) % TwoPI
        return (((bearing) + l_math.pi) % TwoPI) - l_math.pi
    end

    function HOUND.Contact.Estimator.UPLKF.bearingToAzimuth(bearing)
        return (HalfPi - bearing + TwoPI) % TwoPI
    end

    function HOUND.Contact.Estimator.UPLKF:updateMarker()
        local pos = self:getEstimatedPos()
        self.marker:update({
            useLegacyMarker = false
            ,
            pos = pos,
            text = "UB-PLKF",
            coalition = -1
        })
    end

    function HOUND.Contact.Estimator.UPLKF:getF(deltaT)
        local Ft = matrix(4, "I")
        Ft[1][3] = deltaT
        Ft[2][4] = deltaT
        return Ft
    end

    function HOUND.Contact.Estimator.UPLKF:getQ(deltaT, sigma)
        local dT = deltaT or 10
        local sigma_a = sigma or self._maxNoise
        sigma_a = sigma_a / 2

        return matrix({
            { 0.25 * l_math.pow(dT, 4) * sigma_a, 0,                                  0.5 * l_math.pow(dT, 3) * sigma_a, 0 },
            { 0,                                  0.25 * l_math.pow(dT, 4) * sigma_a, 0,                                 0.5 * l_math.pow(dT, 3) * sigma_a },
            { 0.5 * l_math.pow(dT, 3) * sigma_a,  0,                                  l_math.pow(dT, 2) * sigma_a,       0 },
            { 0,                                  0.5 * l_math.pow(dT, 3) * sigma_a,  0,                                 l_math.pow(dT, 2) * sigma_a },
        })
    end

    function HOUND.Contact.Estimator.UPLKF:predictStep(X, P, timestep, Q)
        local F = self:getF(timestep)
        local Q = Q or self:getQ(timestep)
        local x_hat = F * X + Q
        local P_hat = F * P * F:transpose() + Q

        x_hat[3][1] = HoundUtils.Mapping.clamp(x_hat[3][1], -30.0, 30.0)
        x_hat[4][1] = HoundUtils.Mapping.clamp(x_hat[4][1], -30.0, 30.0)
        return x_hat, P_hat
    end

    function HOUND.Contact.Estimator.UPLKF:predict(timestamp)
        timestamp = timestamp or timer.getAbsTime()
        local deltaT = timestamp - self.t0
        self.t0 = timestamp

        self.state, self.P = self:predictStep(self.state, self.P, deltaT)
    end

    function HOUND.Contact.Estimator.UPLKF:update(p0, z, timestamp, z_err)
        timestamp = timestamp or timer.getAbsTime()
        local deltaT = timestamp - self.t0
        self.t0 = timestamp
        local err = z_err or l_math.rad(HOUND.MAX_ANGULAR_RES_DEG)
        local Ri = err
        self._maxNoise = l_math.max(self._maxNoise, err)

        local Q = self:getQ(deltaT)
        local x_hat, P_k = self:predictStep(self.state, self.P, deltaT, Q)

        local estimatedPos = self:getEstimatedPos(x_hat)
        local d_k = HoundUtils.Geo.get2DDistance(p0, estimatedPos)

        local z_bearing = self.normalizeAz(z)
        local z_hat_bearing = self.normalizeAz(HoundUtils.Elint.getAzimuth(p0, estimatedPos))

        local cos_beta_k, sin_beta_k = l_math.cos(z_hat_bearing), l_math.sin(z_hat_bearing)
        local m_k = cos_beta_k * estimatedPos.x + sin_beta_k * estimatedPos.z - d_k

        local H_k = matrix({
            { sin_beta_k / m_k, -cos_beta_k / m_k, 0, 0 }
        })

        local z_k = matrix({ { z_bearing } }) / m_k

        local R_k = matrix({ { Ri } })
        local S_k = H_k * P_k * H_k:transpose() + R_k

        local K_k = P_k * H_k:transpose() * S_k:invert()

        local y_k = z_k - H_k * x_hat

        HOUND.Logger.debug("z_bearing: " .. z_bearing .. "\n z_hat_bearing: " .. z_hat_bearing)
        self.state = x_hat + (K_k * y_k)
        self.P = (matrix(4, "I") - K_k * H_k) * P_k

        self.state[3][1] = HoundUtils.Mapping.clamp(self.state[3][1], -30.0, 30.0)
        self.state[4][1] = HoundUtils.Mapping.clamp(self.state[4][1], -30.0, 30.0)

        if HOUND.DEBUG then
            self:updateMarker()
        end
    end

    setmetatable(HOUND.Contact.Estimator.UPLKF,
        { __call = function(...) return HOUND.Contact.Estimator.UPLKF.create(...) end })
end
do
    local l_math = math
    local HoundUtils = HOUND.Utils

    HOUND.Contact.Datapoint = {}
    HOUND.Contact.Datapoint.__index = HOUND.Contact.Datapoint
    HOUND.Contact.Datapoint.DataPointId = 0

    function HOUND.Contact.Datapoint.New(platform0, p0, az0, el0, s0, t0, angularResolution, isPlatformStatic)
        local elintDatapoint = {}
        setmetatable(elintDatapoint, HOUND.Contact.Datapoint)
        elintDatapoint.platformPos = p0
        elintDatapoint.az = az0
        elintDatapoint.el = el0
        elintDatapoint.signalStrength = tonumber(s0) or 0
        elintDatapoint.t = tonumber(t0)
        elintDatapoint.platformId = tonumber(platform0:getID())
        elintDatapoint.platformName = platform0:getName()
        elintDatapoint.platformStatic = isPlatformStatic or false
        elintDatapoint.platformPrecision = angularResolution or l_math.rad(HOUND.MAX_ANGULAR_RES_DEG)
        elintDatapoint.kalman = nil
        elintDatapoint.processed = false
        if elintDatapoint.platformStatic then
            elintDatapoint.kalman = HOUND.Contact.Estimator.Kalman.AzFilter(elintDatapoint.platformPrecision)
            elintDatapoint:update(elintDatapoint.az)
        end
        return elintDatapoint
    end

    function HOUND.Contact.Datapoint.isStatic(self)
        return self.platformStatic
    end

    function HOUND.Contact.Datapoint.getAge(self)
        return HoundUtils.absTimeDelta(self.t)
    end

    function HOUND.Contact.Datapoint.getPos(self)
        if self.kalman then
            return self.kalman:getValue().pos or nil
        end
        if not self.az and not self.el then return end
        self.pos = HoundUtils.Geo.getProjectedIP(self.platformPos, self.az, self.el)
        if not HountUtils.Dcs.isPoint(self.pos) then
            self.pos = HoundUtils.Geo.getProjectedIP(self.platformPos, self.az, (self.el - (self.platformPrecision/2)))
        end
        return self.pos
    end
    function HOUND.Contact.Datapoint.update(self,newAz,predictedAz,processNoise)
        if not self.platformPrecision and not self.platformStatic then return end
        self.kalman:update(newAz,nil,processNoise)
        self.az = self.kalman:get()
        return self.az
    end
end
do

    local l_math = math
    local l_mist = HOUND.Mist
    local PI_2 = l_math.pi*2
    local HoundUtils = HOUND.Utils

    HOUND.Contact.Emitter = {}
    HOUND.Contact.Emitter = HOUND.inheritsFrom(HOUND.Contact.Base)

    function HOUND.Contact.Emitter:New(DcsObject,HoundCoalition,ContactId)
        if not DcsObject or type(DcsObject) ~= "table" or not DcsObject.getName or not HoundCoalition then
            HOUND.Logger.warn("failed to create HOUND.Contact instance")
            return
        end
        local instance = self:superClass():New(DcsObject,HoundCoalition)
        setmetatable(instance, HOUND.Contact.Emitter)
        self.__index = self

        instance.uid = ContactId or tonumber(DcsObject:getID())
        instance.DcsTypeName = DcsObject:getTypeName()
        instance.DcsGroupName = Group.getName(DcsObject:getGroup())
        instance.DcsObjectName = DcsObject:getName()
        instance.DcsObjectAlive = true
        instance.typeName = DcsObject:getTypeName()
        instance.isEWR = false
        instance.typeAssigned = {"Unknown"}

        instance.isPrimary = false
        instance.radarRoles = {HOUND.DB.RadarType.SEARCH}

        local _,contactUnitCategory = DcsObject:getCategory()
        if contactUnitCategory and contactUnitCategory == Unit.Category.SHIP then
            instance.band = {
                [false] = HOUND.DB.Bands.E,
                [true] = HOUND.DB.Bands.E,
            }
            instance.typeAssigned = {"Naval"}
            instance.radarRoles = {HOUND.DB.RadarType.NAVAL}
        end

        local contactData = HOUND.DB.getRadarData(instance.DcsTypeName)
        if contactData  then
            instance.typeName =  contactData.Name
            instance.isEWR = contactData.isEWR
            instance.typeAssigned = contactData.Assigned
            instance.band = contactData.Band
            instance.isPrimary = contactData.isPrimary
            instance.radarRoles = contactData.Role
            instance.frequency = contactData.Freqency
        else
            instance.band = {
                [false] = HOUND.DB.Bands.C,
                [true] = HOUND.DB.Bands.C,
            }
            instance.frequency = HOUND.DB.getEmitterFrequencies(instance.band)
        end

        instance.uncertenty_data = nil
        instance.maxWeaponsRange = HoundUtils.Dcs.getSamMaxRange(DcsObject)
        instance.detectionRange = HoundUtils.Dcs.getRadarDetectionRange(DcsObject)
        instance._dataPoints = {}
        instance.detected_by = {}
        instance.state = HOUND.EVENTS.RADAR_NEW
        instance.preBriefed = false
        instance.unitAlive = true
        instance.Kalman = nil
        return instance
    end

    function HOUND.Contact.Emitter:destroy()
        self:removeMarkers()
        self.state=HOUND.EVENTS.RADAR_DESTROYED
        self:queueEvent(HOUND.EVENTS.RADAR_DESTROYED)
    end

    function HOUND.Contact.Emitter:getName()
        return self:getType() .. " " .. self:getId()
    end

    function HOUND.Contact.Emitter:getType()
        return self.typeName
    end

    function HOUND.Contact.Emitter:getId()
        return self.uid%100
    end

    function HOUND.Contact.Emitter:getTrackId()
        local trackType = 'E'
        if self:isAccurate() then
            trackType = 'I'
        end
        return string.format("%s-%d",trackType,self.uid)
    end

    function HOUND.Contact.Emitter:getPos()
        return self.pos.p
    end

    function HOUND.Contact.Emitter:getWavelenght(isTracking)
        isTracking = isTracking or false
        return self.frequency[isTracking]
    end

    function HOUND.Contact.Emitter:getElev()
        if not self:hasPos() then return 0 end
        local step = 50
        if self:isAccurate() then
            step = 1
        end
        return HoundUtils.getRoundedElevationFt(self.pos.elev,step)
    end

    function HOUND.Contact.Emitter:getLife()
        if self:isAlive() and (not HoundUtils.Dcs.isUnit(self.DcsObject)) then
            HOUND.Logger.error("something is wrong with the object for " .. self.DcsObjectName)
            self:setDead()
        end
        if self.DcsObject and type(self.DcsObject) == "table" and self.DcsObject:isExist() then
            return self.DcsObject:getLife(),(self.DcsObject:getLife()/self.DcsObject:getLife0())
        end
        return 0
    end

    function HOUND.Contact.Emitter:isAlive()
        return self.DcsObjectAlive
    end

    function HOUND.Contact.Emitter:setDead()
        self.DcsObjectAlive = false
        self:updateDeadDcsObject()
    end

    function HOUND.Contact.Emitter:updateDeadDcsObject()
        self.DcsObject = Unit.getByName(self.DcsObjectName) or StaticObject.getByName(self.DcsObjectName)
        if not self.DcsObject then
            self.DcsObject = self.DcsObjectName
        end
    end

    function HOUND.Contact.Emitter:CleanTimedout()
        if self:isTimedout() then
            self._dataPoints = {}
            self.state = HOUND.EVENTS.RADAR_ASLEEP
        end
        return self.state
    end

    function HOUND.Contact.Emitter:countPlatforms(skipStatic)
        local count = 0
        if HOUND.Length(self._dataPoints) == 0 then return count end
        for _,platformDataPoints in pairs(self._dataPoints) do
            if not platformDataPoints[1].staticPlatform or (not skipStatic and platformDataPoints[1].staticPlatform) then
                count = count + 1
            end
        end
        return count
    end

    function HOUND.Contact.Emitter:countDatapoints()
        local count = 0
        if HOUND.Length(self._dataPoints) == 0 then return count end
        for _,platformDataPoints in pairs(self._dataPoints) do
            count = count + HOUND.Length(platformDataPoints)
        end
        return count
    end

    function HOUND.Contact.Emitter:KalmanPredict(timestamp)
        timestamp = timestamp or timer.getAbsTime()
        if HOUND.ENABLE_KALMAN and self.Kalman then
            HOUND.Logger.debug(self:getName() .. " is KalmanPredict")
            self.Kalman:predict(timestamp)
        end

    end
    function HOUND.Contact.Emitter:AddPoint(datapoint)
        if HOUND.ENABLE_KALMAN and not self.Kalman and HoundUtils.Dcs.isPoint(self.pos.p) then
            if self.uncertenty_data.r < 5000 then
                self.Kalman = HOUND.Contact.Estimator.UPLKF(self.pos.p,{x=0,z=0},self.last_seen,self.uncertenty_data.r)
            end
        end
        self.last_seen = datapoint.t
        if HOUND.ENABLE_KALMAN and self.Kalman then
            HOUND.Logger.debug(self:getName() .. " is KalmanUpdate")
            self.Kalman:update(datapoint.platformPos,datapoint.az,datapoint.t,datapoint.platformPrecision)
        end

        if HOUND.Length(self._dataPoints[datapoint.platformId]) == 0 then
            self._dataPoints[datapoint.platformId] = {}
        end

        if datapoint.platformStatic then
            if HOUND.Length(self._dataPoints[datapoint.platformId]) == 0 then
                self._dataPoints[datapoint.platformId] = {datapoint}
                return
            end
            local predicted = {}
            if HoundUtils.Dcs.isPoint(self.pos.p) then
                predicted.az,predicted.el = HoundUtils.Elint.getAzimuth( datapoint.platformPos , self.pos.p, 0.0 )
                if type(self.uncertenty_data) == "table" and self.uncertenty_data.minor and self.uncertenty_data.major and self.uncertenty_data.az then
                    predicted.err = HoundUtils.Polygon.azMinMax(HOUND.Contact.Emitter.calculatePoly(self.uncertenty_data,8,self.pos.p),datapoint.platformPos)
                end
            end
            self._dataPoints[datapoint.platformId][1]:update(datapoint.az,predicted.az,predicted.err)
            return
        end

        if HOUND.Length(self._dataPoints[datapoint.platformId]) < 2 then
            table.insert(self._dataPoints[datapoint.platformId], 1, datapoint)
            return
        else
            local DeltaT = self._dataPoints[datapoint.platformId][2]:getAge() - datapoint:getAge()
            if  DeltaT >= HOUND.DATAPOINTS_INTERVAL then
                table.insert(self._dataPoints[datapoint.platformId], 1, datapoint)
            else
                local deallocate = self._dataPoints[datapoint.platformId][1]
                self._dataPoints[datapoint.platformId][1] = datapoint
                deallocate = nil
            end
        end

        for i=HOUND.Length(self._dataPoints[datapoint.platformId]),1,-1 do
            if self._dataPoints[datapoint.platformId][i]:getAge() > HOUND.CONTACT_TIMEOUT then
                local deallocate = table.remove(self._dataPoints[datapoint.platformId])
                deallocate = nil
            else
                i=1
            end
        end

        if self:countPlatforms(true) > 0 then
            local pointsPerPlatform = l_math.ceil(HOUND.DATAPOINTS_NUM/self:countPlatforms(true))
            while HOUND.Length(self._dataPoints[datapoint.platformId]) > pointsPerPlatform do
                local deallocate = table.remove(self._dataPoints[datapoint.platformId])
                deallocate = nil
            end
        end
    end

    function HOUND.Contact.Emitter.triangulatePoints(earlyPoint, latePoint)
        local p1 = earlyPoint.platformPos
        local p2 = latePoint.platformPos

        local m1 = l_math.tan(earlyPoint.az)
        local m2 = l_math.tan(latePoint.az)

        local b1 = -m1 * p1.x + p1.z
        local b2 = -m2 * p2.x + p2.z

        local Easting = (b2 - b1) / (m1 - m2)
        local Northing = m1 * Easting + b1

        local pos = {}

        pos.x = Easting
        pos.z = Northing
        pos.y = land.getHeight({x=pos.x,y=pos.z})

        local d1 = HoundUtils.Geo.get2DDistance(p1,pos)
        local d2 = HoundUtils.Geo.get2DDistance(p2,pos)
        local var1 = l_math.pow(((d1 / HOUND.REF_DIST) * earlyPoint.platformPrecision),2)
        local var2 = l_math.pow(((d2 / HOUND.REF_DIST) * latePoint.platformPrecision),2)
        local score = 1 / l_math.max((var1+var2),1e-10)
        local angleScore = l_math.sin(HoundUtils.angleDeltaRad(earlyPoint.az,latePoint.az))
        pos.score = score * angleScore
        return pos
    end

    function HOUND.Contact.Emitter.calculateEllipse(estimatedPositions,refPos,giftWrapped)
        local percentile = HOUND.ELLIPSE_PERCENTILE
        if giftWrapped then percentile = 1.0 end
        local RelativeToPos = HoundUtils.Cluster.getDeltaSubsetPercent(estimatedPositions,refPos,percentile,true)

        local min = {}
        min.x = 99999
        min.y = 99999

        local max = {}
        max.x = -99999
        max.y = -99999

        Theta = HoundUtils.PointClusterTilt(RelativeToPos)

        local sinTheta = l_math.sin(-Theta)
        local cosTheta = l_math.cos(-Theta)

        for k,pos in ipairs(RelativeToPos) do
            local newPos = {}
            newPos.x = pos.x*cosTheta - pos.z*sinTheta
            newPos.z = pos.x*sinTheta + pos.z*cosTheta
            newPos.y = pos.y

            min.x = l_math.min(min.x,newPos.x)
            max.x = l_math.max(max.x,newPos.x)
            min.y = l_math.min(min.y,newPos.z)
            max.y = l_math.max(max.y,newPos.z)

            RelativeToPos[k] = newPos
        end

        local a = l_mist.utils.round(l_math.abs(min.x)+l_math.abs(max.x))
        local b = l_mist.utils.round(l_math.abs(min.y)+l_math.abs(max.y))

        local uncertenty_data = {}
        uncertenty_data.major = l_math.max(a,b)
        uncertenty_data.minor = l_math.min(a,b)
        uncertenty_data.theta = (Theta + PI_2) % PI_2
        uncertenty_data.az = l_mist.utils.round(l_math.deg(uncertenty_data.theta))
        uncertenty_data.r  = (a+b)/4

        return uncertenty_data
    end

    function HOUND.Contact.Emitter:calculateExtrasPosData(pos)
        if HoundUtils.Dcs.isPoint(pos.p) then
            local bullsPos = coalition.getMainRefPoint(self._platformCoalition)
            pos.LL = {}
            pos.LL.lat, pos.LL.lon = coord.LOtoLL(pos.p)
            pos.elev = pos.p.y
            pos.grid  = coord.LLtoMGRS(pos.LL.lat, pos.LL.lon)
            pos.be = HoundUtils.getBR(bullsPos,pos.p)
        end
        return pos
    end

    function HOUND.Contact.Emitter:processIntersection(targetTable,point1,point2)
        if point1.platformPos.x == point2.platformPos.x and point1.platformPos.z == point2.platformPos.z then return end
        local err = (point1.platformPrecision + point2.platformPrecision)/2
        if HoundUtils.angleDeltaRad(point1.az,point2.az) < err then return end
        local intersection = self.triangulatePoints(point1,point2)
        if not HoundUtils.Dcs.isPoint(intersection) then return end
        table.insert(targetTable,intersection)
    end

    function HOUND.Contact.Emitter:processData()
        if self:getPreBriefed() then
            if type(self.DcsObject) == "table" and type(self.DcsObject.isExist) == "function" and self.DcsObject:isExist()
                then
                    local unitPos = self.DcsObject:getPosition()
                    if HoundUtils.Geo.get2DDistance(unitPos.p,self.pos.p) < 0.25 then return end
                    if self:isActive() then
                        HOUND.Logger.debug(self:getName().. " is active and moved.. not longer PB")
                        self:setPreBriefed(false)
                    end
                else
                    self.state = HOUND.EVENTS.NO_CHANGE
                    return
            end
        end

        if not self:isRecent() and self.state ~= HOUND.EVENTS.RADAR_NEW then
            return self.state
        end

        local newContact = (self.state == HOUND.EVENTS.RADAR_NEW)
        local mobileDataPoints = {}
        local staticDataPoints = {}
        local estimatePositions = {}
        local platforms = {}
        local staticPlatformsOnly = true
        local staticClipPolygon2D = nil
        local AllDataPoints = {}

        for _,platformDatapoints in pairs(self._dataPoints) do
            if HOUND.Length(platformDatapoints) > 0 then
                for _,datapoint in pairs(platformDatapoints) do
                    if datapoint:isStatic() then
                        table.insert(staticDataPoints,datapoint)
                    else
                        staticPlatformsOnly = false
                        table.insert(mobileDataPoints,datapoint)
                    end
                    platforms[datapoint.platformName] = 1
                end
            end
        end
        local numMobilepoints = HOUND.Length(mobileDataPoints)
        local numStaticPoints = HOUND.Length(staticDataPoints)
        table.sort(mobileDataPoints, function(a,b) return a.signalStrength < b.signalStrength end)
        table.sort(staticDataPoints, function(a,b) return a.signalStrength < b.signalStrength end)

        if numMobilepoints+numStaticPoints < 2 and HOUND.Length(estimatePositions) == 0 then return end
        if numStaticPoints > 1 then
            for i=1,numStaticPoints-1 do
                for j=i+1,numStaticPoints do
                    self:processIntersection(estimatePositions,staticDataPoints[i],staticDataPoints[j])
                end
            end
        end

        if numStaticPoints > 0  and numMobilepoints > 0 then
            for _,staticDataPoint in ipairs(staticDataPoints) do
                for _,mobileDataPoint in ipairs(mobileDataPoints) do
                    self:processIntersection(estimatePositions,staticDataPoint,mobileDataPoint)
                end
            end
         end

        if numMobilepoints > 1 then
            for i=1,numMobilepoints-1 do
                for j=i+1,numMobilepoints do
                    if mobileDataPoints[i].platformPos ~= mobileDataPoints[j].platformPos then
                        self:processIntersection(estimatePositions,mobileDataPoints[i],mobileDataPoints[j])
                    end
                end
                mobileDataPoints[i].processed = true
            end
        end

        if HOUND.Length(estimatePositions) > 2 or (HOUND.Length(estimatePositions) > 0 and staticPlatformsOnly) then

                if HOUND.ENABLE_BETTER_SCORE then
                    self.pos.p = HoundUtils.Cluster.WeightedCentroid(estimatePositions)
                else
                    self.pos.p = HoundUtils.Cluster.weightedMean(estimatePositions,self.pos.p)

                    if HOUND.Length(estimatePositions) > 10 then
                        self.pos.p = HoundUtils.Cluster.weightedMean(
                            HoundUtils.Cluster.getDeltaSubsetPercent(estimatePositions,self.pos.p,HOUND.ELLIPSE_PERCENTILE),
                            self.pos.p)
                    end
                end

                self.uncertenty_data = self.calculateEllipse(estimatePositions,self.pos.p)
                if type(staticClipPolygon2D) == "table" and ( staticPlatformsOnly) then
                    self.uncertenty_data = self.calculateEllipse(staticClipPolygon2D,self.pos.p,true)
                end

            self.uncertenty_data.az = l_mist.utils.round(l_math.deg((self.uncertenty_data.theta+l_mist.getNorthCorrection(self.pos.p)+PI_2)%PI_2))

            self:calculateExtrasPosData(self.pos)

            if self.state == HOUND.EVENTS.RADAR_ASLEEP then
                self.state = HOUND.EVENTS.SITE_ALIVE
            else
                self.state = HOUND.EVENTS.RADAR_UPDATED
            end

            local detected_by = {}

            for key,_ in pairs(platforms) do
                table.insert(detected_by,key)
            end
            local deallocate = self.detected_by
            self.detected_by = detected_by
            deallocate = nil
        end

        if newContact and HoundUtils.Dcs.isPoint(self.pos.p) ~= nil and self.isEWR == false then
            self.state = HOUND.EVENTS.RADAR_DETECTED
            self:calculateExtrasPosData(self.pos)
        end
        self:queueEvent(self.state)
        return self.state
    end

    function HOUND.Contact.Emitter.calculatePoly(uncertenty_data,numPoints,refPos)
        local polygonPoints = {}
        if type(uncertenty_data) ~= "table" or not uncertenty_data.major or not uncertenty_data.minor or not uncertenty_data.az then
            return polygonPoints
        end
        if type(numPoints) ~= "number" then
            numPoints = 8
        end
        if not HoundUtils.Dcs.isPoint(refPos) then
            refPos = {x=0,y=0,z=0}
        end
        local angleStep = PI_2/numPoints
        local theta = l_math.rad(uncertenty_data.az) - HoundUtils.getMagVar(refPos)
        local cos_theta,sin_theta = l_math.cos(theta),l_math.sin(theta)
        for i = 1, numPoints do
            local pointAngle = PI_2 - (i * angleStep)
            local point = {}
            point.x = uncertenty_data.major/2 * l_math.cos(pointAngle)
            point.z = uncertenty_data.minor/2 * l_math.sin(pointAngle)
            local x = point.x * cos_theta - point.z * sin_theta
            local z = point.x * sin_theta + point.z * cos_theta
            point.x = x + refPos.x
            point.z = z + refPos.z
            local mgrs = coord.LLtoMGRS(coord.LOtoLL( point ))
            if type(mgrs) == "table" and type(mgrs.Easting) == "number" and type(mgrs.Northing ) == "number" then
                table.insert(polygonPoints, point)
            end
        end
        HoundUtils.Geo.setHeight(polygonPoints)

        return polygonPoints
    end

    function HOUND.Contact.Emitter:drawAreaMarker(numPoints)
        if numPoints == nil then numPoints = 1 end
        if numPoints ~= 1 and numPoints ~= 4 and numPoints ~=8 and numPoints ~= 16 then
            HOUND.Logger.error("DCS limitation, only 1,4,8 or 16 points are allowed")
            numPoints = 1
            end

        local alpha = HoundUtils.Mapping.linear(l_math.floor(HoundUtils.absTimeDelta(self.last_seen)),0,HOUND.CONTACT_TIMEOUT,HOUND.MARKER_MAX_ALPHA,HOUND.MARKER_MIN_ALPHA,true)
        local fillColor = {0,0,0,alpha}
        local lineColor = {0,0,0,HOUND.MARKER_LINE_OPACITY}
        local lineType = 2
        if (HoundUtils.absTimeDelta(self.last_seen) < 30) then
            lineType = 1
        end
        if self._platformCoalition == coalition.side.BLUE then
            fillColor[1] = 1
            lineColor[1] = 1
        elseif self._platformCoalition == coalition.side.RED then
            fillColor[3] = 1
            lineColor[3] = 1
        end

        local markArgs = {
            fillColor = fillColor,
            lineColor = lineColor,
            coalition = self._platformCoalition,
            lineType = lineType
        }
        if numPoints == 1 then
            markArgs.pos = {
                p = self.pos.p,
                r = self.uncertenty_data.r
            }
        else
            markArgs.pos = HOUND.Contact.Emitter.calculatePoly(self.uncertenty_data,numPoints,self.pos.p)
        end
        return self._markpoints.area:update(markArgs)
    end

    function HOUND.Contact.Emitter:updateMarker(MarkerType)
        local MarkerType = MarkerType or HOUND.MARKER.POINT
        if not self:hasPos() or self.uncertenty_data == nil or not self:isRecent() then return end
        if self:isAccurate() and self._markpoints.pos:isDrawn() then return end
        local markerArgs = {
            text = self.typeName .. " " .. (self.uid%100),
            pos = self.pos.p,
            coalition = self._platformCoalition,
            useLegacyMarker = HOUND.USE_LEGACY_MARKERS
        }
        if not self:isAccurate() and HOUND.USE_LEGACY_MARKERS then
            markerArgs.text = markerArgs.text .. " (" .. self.uncertenty_data.major .. "/" .. self.uncertenty_data.minor .. "@" .. self.uncertenty_data.az .. ")"
        end
        if MarkerType >= HOUND.MARKER.POINT then
            self._markpoints.pos:update(markerArgs)
        end

        if  MarkerType < HOUND.MARKER.POINT or self:isAccurate() then
                self._markpoints.area:remove()
                if MarkerType < HOUND.MARKER.POINT then
                    self._markpoints.pos:remove()
                end
            return
        end

        if MarkerType == HOUND.MARKER.CIRCLE then
            self:drawAreaMarker()
        end

        if MarkerType == HOUND.MARKER.DIAMOND then
            self:drawAreaMarker(4)
        end

        if MarkerType == HOUND.MARKER.OCTAGON then
            self:drawAreaMarker(8)
        end

        if MarkerType == HOUND.MARKER.POLYGON then
            self:drawAreaMarker(16)
        end
    end

    function HOUND.Contact.Emitter:useUnitPos(unitPosMarker)
        if not self.DcsObject:isExist() then
            HOUND.Logger.info("PB failed - unit does not exist")
            return
        end
        self.state = HOUND.EVENTS.RADAR_DETECTED
        if type(self.pos.p) == "table" then
            self.state = HOUND.EVENTS.RADAR_UPDATED
        end
        local unitPos = self.DcsObject:getPosition()
        self:setPreBriefed(true)

        self.pos.p = l_mist.utils.deepCopy(unitPos.p)
        self:calculateExtrasPosData(self.pos)

        self.uncertenty_data = {}
        self.uncertenty_data.major = 0.1
        self.uncertenty_data.minor = 0.1
        self.uncertenty_data.az = 0
        self.uncertenty_data.r  = 0.1

        table.insert(self.detected_by,"External")
        self:updateMarker(unitPosMarker)
        return self.state
    end

    function HOUND.Contact.Emitter:export()
        local contact = {}
        contact.typeName = self.typeName
        contact.uid = self.uid % 100
        contact.DcsObjectName = self.DcsObject:getName()
        if self.pos.p ~= nil and self.uncertenty_data ~= nil then
            contact.pos = self.pos.p
            contact.LL = self.pos.LL

            contact.accuracy = HoundUtils.TTS.getVerbalConfidenceLevel( self.uncertenty_data.r )
            contact.uncertenty = {
                major = self.uncertenty_data.major,
                minor = self.uncertenty_data.minor,
                heading = self.uncertenty_data.az
            }
        end
        contact.maxWeaponsRange = self.maxWeaponsRange
        contact.last_seen = self.last_seen
        contact.detected_by = self.detected_by
        return l_mist.utils.deepCopy(contact)
    end
end
do
    local l_math = math
    local HoundUtils = HOUND.Utils

    function HOUND.Contact.Emitter:getTextData(utmZone,MGRSdigits)
        if self.pos.p == nil then return end
        local GridPos = ""
        if utmZone then
            GridPos = GridPos .. self.pos.grid.UTMZone .. " "
        end
        GridPos = GridPos .. self.pos.grid.MGRSDigraph
        local BE = self.pos.be.brStr .. "/" .. self.pos.be.rng
        if MGRSdigits == nil then
            return GridPos,BE
        end
        local E = l_math.floor(self.pos.grid.Easting/(10^l_math.min(5,l_math.max(1,5-MGRSdigits))))
        local N = l_math.floor(self.pos.grid.Northing/(10^l_math.min(5,l_math.max(1,5-MGRSdigits))))
        GridPos = GridPos .. " " .. E .. " " .. N

        return GridPos,BE
    end

    function HOUND.Contact.Emitter:getTtsData(utmZone,MGRSdigits)
        if self.pos.p == nil then return end
        local phoneticGridPos = ""
        if utmZone then
            phoneticGridPos =  phoneticGridPos .. HoundUtils.TTS.toPhonetic(self.pos.grid.UTMZone) .. " "
        end

        phoneticGridPos =  phoneticGridPos ..  HoundUtils.TTS.toPhonetic(self.pos.grid.MGRSDigraph)
        local phoneticBulls = HoundUtils.TTS.toPhonetic(self.pos.be.brStr)
                                .. "  " .. self.pos.be.rng
        if MGRSdigits==nil then
            return phoneticGridPos,phoneticBulls
        end
        local E = l_math.floor(self.pos.grid.Easting/(10^l_math.min(5,l_math.max(1,5-MGRSdigits))))
        local N = l_math.floor(self.pos.grid.Northing/(10^l_math.min(5,l_math.max(1,5-MGRSdigits))))
        phoneticGridPos = phoneticGridPos .. " " .. HoundUtils.TTS.toPhonetic(E) .. "   " .. HoundUtils.TTS.toPhonetic(N)

        return phoneticGridPos,phoneticBulls
    end

    function HOUND.Contact.Emitter:generateTtsBrief(NATO)
        if self.pos.p == nil or self.uncertenty_data == nil then return end
        local phoneticGridPos,phoneticBulls = self:getTtsData(false,1)
        local reportedName = self:getName()
        if NATO then
            reportedName = self:getDesignation(NATO)
        end
        local str = reportedName
        if self:isAccurate() then
            str = str .. ", reported"
        else
            str = str .. ", " .. HoundUtils.TTS.getVerbalContactAge(self.last_seen,true,NATO)
        end
        if NATO then
            str = str .. " bullseye " .. phoneticBulls
        else
            str = str .. " at " .. phoneticGridPos
        end
        if not self:isAccurate() then
            str = str .. ", accuracy " .. HoundUtils.TTS.getVerbalConfidenceLevel( self.uncertenty_data.r )
        end
        str = str .. "."
        return str
    end

    function HOUND.Contact.Emitter:generateTtsReport(useDMM,preferMGRS,refPos)
        if self.pos.p == nil then return end
        useDMM = useDMM or false
        preferMGRS = preferMGRS or false
        local MGRSPrecision = HOUND.MGRS_PRECISION
        if preferMGRS then
            MGRSPrecision = 5;
        end
        local BR = nil
        if refPos ~= nil and refPos.x ~= nil and refPos.z ~= nil then
            BR = HoundUtils.getBR(self.pos.p,refPos)
        end
        local phoneticGridPos,phoneticBulls = self:getTtsData(true,MGRSPrecision)
        local msg =  self:getName()
        if self:isAccurate()
            then
                msg = msg .. ", reported"
            else
               msg = msg .. ", " .. HoundUtils.TTS.getVerbalContactAge(self.last_seen,true)
        end
        if BR ~= nil
            then
                msg = msg .. " from you " .. HoundUtils.TTS.toPhonetic(BR.brStr) .. " for " .. BR.rng
            else
                msg = msg .." at bullseye " .. phoneticBulls
        end
        local LLstr = HoundUtils.TTS.getVerbalLL(self.pos.LL.lat,self.pos.LL.lon,useDMM)

        local primaryPos = LLstr
        if preferMGRS then
            primaryPos = phoneticGridPos
        end

        msg = msg .. ", accuracy " .. HoundUtils.TTS.getVerbalConfidenceLevel( self.uncertenty_data.r )
        msg = msg .. ", position " .. primaryPos
        msg = msg .. ", I say again " .. primaryPos
        if not preferMGRS then
            msg = msg .. ", MGRS " .. phoneticGridPos
        end
        msg = msg .. ", elevation  " .. self:getElev() .. " feet MSL"

        if HOUND.EXTENDED_INFO then
            if self:isAccurate()
                then
                    msg = msg .. ", Reported " .. HoundUtils.TTS.getVerbalContactAge(self.first_seen) .. " ago"
                else
                    msg = msg .. ", ellipse " ..  HoundUtils.TTS.simplfyDistance(self.uncertenty_data.major) .. " by " ..  HoundUtils.TTS.simplfyDistance(self.uncertenty_data.minor) .. ", aligned bearing " .. HoundUtils.TTS.toPhonetic(string.format("%03d",self.uncertenty_data.az))
                    msg = msg .. ", Tracked for " .. HoundUtils.TTS.getVerbalContactAge(self.first_seen) .. ", last seen " .. HoundUtils.TTS.getVerbalContactAge(self.last_seen) .. " ago"
                end
        end
        msg = msg .. ". " .. HoundUtils.getControllerResponse()
        return msg
    end

    function HOUND.Contact.Emitter:generateTextReport(useDMM,refPos)
        if self.pos.p == nil then return end
        useDMM = useDMM or false

        local GridPos,BePos = self:getTextData(true,HOUND.MGRS_PRECISION)
        local BR = nil
        if refPos ~= nil and refPos.x ~= nil and refPos.z ~= nil then
            BR = HoundUtils.getBR(self.pos.p,refPos)
        end
        local msg =  self:getName()
        if self:isAccurate()
            then
                msg = msg .." (Reported)\n"
            else
                msg = msg .." (" .. HoundUtils.TTS.getVerbalContactAge(self.last_seen,true).. ")\n"
        end
        msg = msg .. "Accuracy: " .. HoundUtils.TTS.getVerbalConfidenceLevel( self.uncertenty_data.r ) .. "\n"
        msg = msg .. "BE: " .. BePos .. "\n" -- .. " (grid ".. GridPos ..")\n"
        if BR ~= nil then
            msg = msg .. "BR: " .. BR.brStr .. " for " .. BR.rng
        end
        msg = msg .. "LL: " .. HoundUtils.Text.getLL(self.pos.LL.lat,self.pos.LL.lon,useDMM).."\n"
        msg = msg .. "MGRS: " .. GridPos .. "\n"
        msg = msg .. "Elev: " .. self:getElev() .. "ft"
        if HOUND.EXTENDED_INFO then
            if self:isAccurate() then
                msg = msg .. "\nReported " .. HoundUtils.TTS.getVerbalContactAge(self.first_seen) .. " ago. "
            else
                msg = msg .. "\nEllipse: " ..  self.uncertenty_data.major .. " by " ..  self.uncertenty_data.minor .. " aligned bearing " .. string.format("%03d",self.uncertenty_data.az) .. "\n"
                msg = msg .. "Tracked for: " .. HoundUtils.TTS.getVerbalContactAge(self.first_seen) .. " Last Contact: " ..  HoundUtils.TTS.getVerbalContactAge(self.last_seen) .. " ago. "
            end
        end
        return msg
    end

    function HOUND.Contact.Emitter:getRadioItemText()
        if not self:hasPos() then return self:getName() end
        local GridPos,BePos = self:getTextData(true,1)
        BePos = BePos:gsub(" for ","/")
        return self:getName() .. " - BE: " .. BePos .. " (".. GridPos ..")"
    end

    function HOUND.Contact.Emitter:generatePopUpReport(isTTS,sectorName)
        local msg = self:getName()
        if self:isAccurate() then
            msg = msg .. " has been reported"
        else
            msg = msg .. " is now Alive"
        end

        if sectorName then
            msg = msg .. " in " .. sectorName
        else
            if self:hasPos() then
                local GridPos,BePos
                if isTTS then
                    GridPos,BePos = self:getTtsData(true,1)
                    msg = msg .. ", bullseye " .. BePos .. ", grid ".. GridPos
                else
                    GridPos,BePos = self:getTextData(true,1)
                    msg = msg .. " BE: " .. BePos .. " (grid ".. GridPos ..")"
                end
            end
        end
        return msg .. "."
    end

    function HOUND.Contact.Emitter:generateDeathReport(isTTS,sectorName)
        local msg = self:getName() .. " has been destroyed"
        if sectorName then
            msg = msg .. " in " .. sectorName
        else
            if self:hasPos() then
                local GridPos,BePos
                if isTTS then
                    GridPos,BePos = self:getTtsData(true,1)
                    msg = msg .. ", bullseye " .. BePos .. ", grid ".. GridPos
                else
                    GridPos,BePos = self:getTextData(true,1)
                    msg = msg .. " BE: " .. BePos .. " (grid ".. GridPos ..")"
                end
            end
        end
        return msg .. "."
    end

    function HOUND.Contact.Emitter:generateIntelBrief()
        local msg = ""
        if self:hasPos() then
            local GridPos,BePos = self:getTextData(true,HOUND.MGRS_PRECISION)
            msg = {
                self:getTrackId(),self:getType(),
                HoundUtils.TTS.getVerbalContactAge(self.last_seen,true,true),
                BePos,string.format("%02.6f",self.pos.LL.lat),string.format("%03.6f",self.pos.LL.lon), GridPos,
                HoundUtils.TTS.getVerbalConfidenceLevel( self.uncertenty_data.r ),
                HoundUtils.Text.getTime(self.last_seen),self.DcsTypeName,self.DcsObjectName
            }
            msg = table.concat(msg,",")
        end
        return msg
    end
end
do
    HOUND.Contact.Site = {}
    HOUND.Contact.Site = HOUND.inheritsFrom(HOUND.Contact.Base)

    local l_math = math
    local l_mist = HOUND.Mist
    local HoundUtils = HOUND.Utils

    function HOUND.Contact.Site:New(HoundContact,HoundCoalition,SiteId)
        if not HoundContact or type(HoundContact) ~= "table" or not HoundContact.getDcsGroupName or not HoundCoalition then
            HOUND.Logger.warn("failed to create HOUND.Contact.Site instance")
            return
        end
        local instance = self:superClass():New(HoundContact:getDcsObject(),HoundCoalition)
        setmetatable(instance, HOUND.Contact.Site)
        self.__index = self
        instance.DcsObject = HoundContact:getDcsObject():getGroup()
        instance.gid = SiteId or tonumber(instance.DcsObject:getId())
        instance.DcsGroupName = instance.DcsObject:getName()
        instance.DcsObjectName = instance.DcsObject:getName()
        instance.typeAssigned = HoundContact.typeAssigned

        instance.emitters = { HoundContact }
        instance.primaryEmitter = HoundContact
        instance.last_seen = HoundContact:getLastSeen()
        instance.first_seen = HoundContact.first_seen
        instance.last_launch_notify = 0
        instance.maxWeaponsRange = HoundContact:getMaxWeaponsRange()
        instance.detectionRange = HoundContact:getRadarDetectionRange()
        instance.isEWR = HoundContact.isEWR
        instance.state = HOUND.EVENTS.SITE_NEW
        instance.preBriefed = HoundContact:isAccurate()
        instance.DcsRadarUnits = HoundUtils.Dcs.getRadarUnitsInGroup(instance.DcsObject)
        setmetatable(instance.emitters,{__mode="v"})
        return instance
    end

    function HOUND.Contact.Site:destroy()
        self:removeMarkers()
    end

    function HOUND.Contact.Site:getName()
        local prefix = 'T'
        if self.isEWR then
            prefix = 'S'
        end

        return self.name or string.format("%s%03d",prefix,self:getId())
    end

    function HOUND.Contact.Site:setName(requestedName)
        if type(requestedName) == "string" or type(requestedName) == "nil" then
            self.name = requestedName
        end
    end

    function HOUND.Contact.Site:getType()
        return self:getTypeAssigned()
    end

    function HOUND.Contact.Site:getId()
        return self.gid%1000
    end

    function HOUND.Contact.Site:getDcsGroupName()
        return self.DcsGroupName
    end

    function HOUND.Contact.Site:getDcsName()
        return self.DcsGroupName
    end

    function HOUND.Contact.Site:getDcsObject()
        return self.DcsObject or self.DcsGroupName
    end

    function HOUND.Contact.Site:getLastSeen()
        return HoundUtils.absTimeDelta(self.last_seen)
    end

    function HOUND.Contact.Site:getTypeAssigned()
        return table.concat(self.typeAssigned," or ")
    end

    function HOUND.Contact.Site:isActive()
        return self:getLastSeen()/16 < 1.0
    end

    function HOUND.Contact.Site:isRecent()
        return self:getLastSeen()/120 < 1.0
    end

    function HOUND.Contact.Site:isAccurate()
        return self.preBriefed
    end

    function HOUND.Contact.Site:isAlive()
        return #self.emitters > 0
    end

    function HOUND.Contact.Site:isTimedout()
        return self:getLastSeen() > HOUND.CONTACT_TIMEOUT
    end

    function HOUND.Contact.Site:getState()
        return self.state
    end

    function HOUND.Contact.Site:getPos()
        return self.pos.p or nil
    end

    function HOUND.Contact.Site:hasRadarUnits()
        if not HoundUtils.Dcs.isGroup(self.DcsObject) or self.DcsObject:getSize() == 0 then return false end
        local lastUnit = self.DcsObject:getUnit(self.DcsObject:getSize())
        return lastUnit:hasSensors(Unit.SensorType.RADAR)
    end

    function HOUND.Contact.Site:addEmitter(HoundEmitter)
        self.state = HOUND.EVENTS.NO_CHANGE
        if HoundEmitter:getDcsGroupName() == self:getDcsGroupName() and
            not HOUND.setContainsValue(self.emitters,HoundEmitter) then
                table.insert(self.emitters,HoundEmitter)
                self:selectPrimaryEmitter()
                self:updateTypeAssigned()
                self:updateSector()
                self:updateGroupRadars()
                self.state = HOUND.EVENTS.SITE_UPDATED
        end
        return self.state
    end

    function HOUND.Contact.Site:removeEmitter(HoundEmitter)
        self.state = HOUND.EVENTS.NO_CHANGE
        if HoundEmitter:getDcsGroupName() == self:getDcsGroupName() then
            for idx,emitter in ipairs(self.emitters) do
                if emitter == HoundEmitter then
                    table.remove(self.emitters,idx)
                    if #self.emitters > 0 then
                        self:selectPrimaryEmitter()
                    end
                    self:updateGroupRadars()
                    self.state = HOUND.EVENTS.SITE_UPDATED
                    break
                end
            end
        end
        return self.state
    end

    function HOUND.Contact.Site:gcEmitters()
        for idx=#self.emitters,1,-1 do
            if self.emitters[idx] == nil then
                table.remove(self.emitters,idx)
            end
        end
    end

    function HOUND.Contact.Site:updateGroupRadars()
        self.DcsRadarUnits = HoundUtils.Dcs.getRadarUnitsInGroup(self.DcsObject)
    end

    function HOUND.Contact.Site:getPrimary()
        if not self.primaryEmitter then
            self:selectPrimaryEmitter()
        end
        return self.primaryEmitter
    end

    function HOUND.Contact.Site:getEmitters()
        return self.emitters
    end

    function HOUND.Contact.Site:countEmitters()
        return #self.emitters
    end
    function HOUND.Contact.Site:sortEmitters()
        table.sort(self.emitters,HoundUtils.Sort.ContactsByPrio)
    end

    function HOUND.Contact.Site:selectPrimaryEmitter()
        self:sortEmitters()
        if self.primaryEmitter ~= self.emitters[1] then
            self.primaryEmitter = self.emitters[1]
            self.isEWR = self.primaryEmitter.isEWR
            self.state = HOUND.EVENTS.SITE_UPDATED
            return true
        end
        return false
    end

    function HOUND.Contact.Site:updateTypeAssigned()
        local type = self.primaryEmitter.typeAssigned or {}
        if HOUND.Length(type) > 1 then
            for _,emitter in ipairs(self.emitters) do
                type = HOUND.setIntersection(type,emitter.typeAssigned)
            end
        end
        if self:getTypeAssigned() ~= table.concat(type," or ") then
            self.typeAssigned = type

            if self.state ~= HOUND.EVENTS.SITE_NEW then
               self:queueEvent(HOUND.EVENTS.SITE_CLASSIFIED)
            end
            self.state = HOUND.EVENTS.SITE_UPDATED
        end
    end

    function HOUND.Contact.Site:updatePos()
        local noPos = (self.pos.p == nil)
        self:ensurePrimaryHasPos()
        for _,emitter in ipairs(self.emitters) do
            if emitter:hasPos() then
                self.pos.p = l_mist.utils.deepCopy(emitter:getPos())
                break
            end
        end
        if noPos and self.pos.p ~= nil then
            self:queueEvent(HOUND.EVENTS.SITE_CREATED)
        end
    end

    function HOUND.Contact.Site:ensurePrimaryHasPos(refPos)
        local primary = self:getPrimary()
        if ( not primary:hasPos() ) then
            for _,emitter in ipairs(self.emitters) do
                if ( emitter:hasPos() ) then
                    primary.pos = l_mist.utils.deepCopy(emitter.pos)
                    primary.uncertenty_data = l_mist.utils.deepCopy(emitter.uncertenty_data)
                    break
                end
            end

            if ( not primary:hasPos() and HoundUtils.Dcs.isPoint(refPos)) then
                local uncertenty = primary:getMaxWeaponsRange() * 0.75
                primary.pos.p = l_mist.utils.deepCopy(refPos)
                primary.pos = primary:calculateExtrasPosData(primary.pos)
                primary.uncertenty_data = {}
                primary.uncertenty_data.major = uncertenty
                primary.uncertenty_data.minor = uncertenty
                primary.uncertenty_data.theta = 0
                primary.uncertenty_data.az = 0
                primary.uncertenty_data.r  = uncertenty
            end
        end
    end

    function HOUND.Contact.Site:updateSector()
        for _,emitter in ipairs(self.emitters) do
            if emitter:hasPos() then
                self.threatSectors = emitter.threatSectors
                self.primarySector = emitter.primarySector
                break
            end
        end
        self:updateDefaultSector()
    end

    function HOUND.Contact.Site:LaunchDetected(cooldown)
        local cooldown = cooldown or 30
        if ( HoundUtils.absTimeDelta(self.last_launch_notify) > cooldown ) then

            self.last_launch_notify = timer.getAbsTime()
            local event = {
                id = HOUND.EVENTS.SITE_LAUNCH,
                initiator = self,
                time = timer.getTime()
            }
            return event
        end
    end

    function HOUND.Contact.Site:processData()
        self:update()
    end

    function HOUND.Contact.Site:update()
        if #self.emitters > 0 then
            self:gcEmitters()
            self:selectPrimaryEmitter()
            self:updateTypeAssigned()
            self:updatePos()
            self:updateSector()
            local isPB = false
            for _,emitter in ipairs(self.emitters) do
                self.last_seen = l_math.max(self.last_seen,emitter.last_seen)
                self.maxWeaponsRange = l_math.max(self.maxWeaponsRange,emitter:getMaxWeaponsRange())
                self.detectionRange = l_math.max(self.detectionRange,emitter:getRadarDetectionRange())
                isPB = isPB or emitter:isAccurate()
            end
            self:setPreBriefed(isPB)
        end
        if self.state ~=  HOUND.EVENTS.SITE_ASLEEP then
            if (self:isTimedout() and not self:isAccurate()) or #self.emitters == 0 then
                self.state = HOUND.EVENTS.SITE_ASLEEP
                self:queueEvent(self.state)
            end
        end
        if #self.emitters == 0 and not self:hasRadarUnits() then
            self:queueEvent(HOUND.EVENTS.SITE_REMOVED)
        end
    end

    function HOUND.Contact.Site:drawAreaMarker(numPoints)
        if numPoints == nil then numPoints = 1 end
        if numPoints ~= 1 and numPoints ~= 4 and numPoints ~=8 and numPoints ~= 16 then
            HOUND.Logger.error("DCS limitation, only 1,4,8 or 16 points are allowed")
            numPoints = 1
            end

        local alpha = HoundUtils.Mapping.linear(l_math.floor(HoundUtils.absTimeDelta(self.last_seen)),0,HOUND.CONTACT_TIMEOUT,HOUND.MARKER_MAX_ALPHA,HOUND.MARKER_MIN_ALPHA,true)
        local fillColor = {0,0,0,0}
        local lineColor = {0,0.2,0,alpha}
        local lineType = 4
        if (HoundUtils.absTimeDelta(self.last_seen) < 15) then
            lineType = 3
        end
        if self._platformCoalition == coalition.side.BLUE then
            fillColor[1] = 1
            lineColor[1] = 1
        end

        if self._platformCoalition == coalition.side.RED then
            fillColor[3] = 1
            lineColor[3] = 1
        end

        local markArgs = {
            fillColor = fillColor,
            lineColor = lineColor,
            coalition = self._platformCoalition,
            lineType = lineType
        }
        if numPoints == 1 then
            markArgs.pos = {
                p = self:getPos(),
                r = self.maxWeaponsRange
            }
        else
            markArgs.pos = HOUND.Contact.Emitter.calculatePoly(self.uncertenty_data,numPoints,self.pos.p)
        end
        return self._markpoints.area:update(markArgs)
    end

    function HOUND.Contact.Site:updateMarker(MarkerType)
        if not HoundUtils.Dcs.isPoint(self:getPos()) or type(self.maxWeaponsRange) ~= "number"  then return end
        self._markpoints.area:remove()

        local textColor = 0
        local textAlpha = 1
        if not self:isAccurate() then
            textAlpha = HoundUtils.Mapping.linear(l_math.floor(HoundUtils.absTimeDelta(self.last_seen)),10,HOUND.CONTACT_TIMEOUT,1,0.5,true)
        end
        if self:isTimedout() and not self:isAccurate() then
            textAlpha = 0.5
            Colorfactor = 0.3
        end

        local lineColor = {textColor,textColor,textColor,textAlpha}

        if self._platformCoalition == coalition.side.BLUE then
            lineColor[1] = 0.7
        elseif self._platformCoalition == coalition.side.RED then
            lineColor[3] = 0.7
        end

        local markerArgs = {
            text = self:getName() .. " (" .. self:getDesignation(true).. ")",
            pos = self:getPos(),
            coalition = self._platformCoalition,
            lineColor = lineColor,
            useLegacyMarker = false
        }
        self._markpoints.pos:update(markerArgs)

    end

    function HOUND.Contact.Site:updateMarkers(markerType,drawSite)
        if (type(markerType) ~= "number" or markerType == HOUND.MARKER.NONE) and not drawSite then return end
        if markerType > HOUND.MARKER.SITE_ONLY then
            for _,emitter in pairs(self.emitters) do
                emitter:updateMarker(markerType)
            end
        end
        if drawSite then
            self:updateMarker(HOUND.MARKER.SITE_ONLY)
            HOUND.Logger.debug(self:getName() .. " Done")
        end
    end

end--- HOUND.Contact.Site_comms
do
    local l_mist = HOUND.Mist
    local HoundUtils = HOUND.Utils

    function HOUND.Contact.Site:getTextData(utmZone,MGRSdigits)
        local primary = self:getPrimary()
        if not primary:hasPos() then return end
        return primary:getTextData(utmZone,MGRSdigits)
    end

    function HOUND.Contact.Site:getTtsData(utmZone,MGRSdigits)
        local primary = self:getPrimary()
        if not primary:hasPos() then return end
        return primary:getTtsData(utmZone,MGRSdigits)
    end

    function HOUND.Contact.Site:getRadioItemText()
        local primary = self:getPrimary()
        if not primary:hasPos() then return self:getName() end

        local GridPos,BePos = primary:getTextData(true,1)
        BePos = BePos:gsub(" for ","/")
        return self:getName() .. " - BE: " .. BePos .. " (".. GridPos ..")"
    end

    function HOUND.Contact.Site:getRadioItemsText()
        local items = {
            ['dcsName'] = self:getDcsName(),
            ['txt'] = self:getRadioItemText(),
            ['typeAssigned'] = self:getTypeAssigned(),
            ['emitters'] = {}
        }
        for _,emitter in ipairs(self.emitters) do
            if emitter:hasPos() then
                local emitterEntry = {
                    ['dcsName'] = emitter:getDcsName(),
                    ['txt'] = emitter:getRadioItemText()
                }
                if emitter == self.primaryEmitter then
                    emitterEntry.txt = "(*) " .. emitterEntry.txt
                end
                table.insert(items['emitters'],emitterEntry)
            end
        end
        return items
    end

    function HOUND.Contact.Site:generatePopUpReport(isTTS,sectorName)
        local msg = self:getName() .. ", identified as " .. self:getDesignation(true) .. ", is active"

        if sectorName then
            msg = msg .. " in " .. sectorName
        else
            local primary = self:getPrimary()
            if primary:hasPos() then
                local GridPos,BePos
                if isTTS then
                    GridPos,BePos = primary:getTtsData(true,1)
                    msg = msg .. ", bullseye " .. BePos .. ", grid ".. GridPos
                else
                    GridPos,BePos = primary:getTextData(true,1)
                    msg = msg .. " BE: " .. BePos .. " (grid ".. GridPos ..")"
                end
            end
        end
        return msg .. "."
    end

    function HOUND.Contact.Site:generateDeathReport(isTTS,sectorName)
        local msg = self:getName() ..  ", identified as " .. self:getDesignation(true) .. " is down"
        if sectorName then
            msg = msg .. " in " .. sectorName
        else
            if self:hasPos() then
                local GridPos,BePos
                if isTTS then
                    GridPos,BePos = self:getTtsData(true,1)
                    msg = msg .. ", bullseye " .. BePos .. ", grid ".. GridPos
                else
                    GridPos,BePos = self:getTextData(true,1)
                    msg = msg .. " BE: " .. BePos .. " (grid ".. GridPos ..")"
                end
            end
        end
        return msg .. "."
    end

    function HOUND.Contact.Site:generateAsleepReport(isTTS,sectorName)
        local msg = self:getName() ..  ", identified as " .. self:getDesignation(true) .. " is asleep"
        if sectorName then
            msg = msg .. " in " .. sectorName
        else
            if self:hasPos() then
                local GridPos,BePos
                if isTTS then
                    GridPos,BePos = self:getTtsData(true,1)
                    msg = msg .. ", bullseye " .. BePos .. ", grid ".. GridPos
                else
                    GridPos,BePos = self:getTextData(true,1)
                    msg = msg .. " BE: " .. BePos .. " (grid ".. GridPos ..")"
                end
            end
        end
        return msg .. "."
    end

    function HOUND.Contact.Site:generateLaunchAlert(isTTS,sectorName)
    local msg = "SAM LAUNCH! SAM LAUNCH! " .. self:getDesignation(true)
    if sectorName then
        msg = msg .. " in " .. sectorName
    else
        if self:hasPos() then
            local GridPos,BePos
            if isTTS then
                GridPos,BePos = self:getTtsData(true,1)
                msg = msg .. ", bullseye " .. BePos
            else
                GridPos,BePos = self:getTextData(true,1)
                msg = msg .. " BE: " .. BePos .. " (grid ".. GridPos ..")"
            end
        end
    end
    return  msg .. "!"
    end

    function HOUND.Contact.Site:generateIdentReport(isTTS,sectorName)
        local msg = self:getName()

        if sectorName then
            msg = msg .. " in " .. sectorName
            msg = msg .. ", has been reclassified as " .. self:getDesignation(true)
        else
            msg = msg .. ", has been reclassified as " .. self:getDesignation(true)
            local primary = self:getPrimary()
            if primary:hasPos() then
                local GridPos,BePos
                if isTTS then
                    GridPos,BePos = primary:getTtsData(true,1)
                    msg = msg .. ", bullseye " .. BePos .. ", grid ".. GridPos
                else
                    GridPos,BePos = primary:getTextData(true,1)
                    msg = msg .. " BE: " .. BePos .. " (grid ".. GridPos ..")"
                end
            end
        end
        return msg .. "."
    end

    function HOUND.Contact.Site:generateTtsBrief(NATO)
        if self:getType() == "Naval" then
            local boatData = {}
            for _,emitter in ipairs(self:getEmitters()) do
                table.insert(boatData,emitter:generateTtsBrief(NATO))
            end
            return table.concat(boatData," ")
        end
        local str = ""

        local primary = self:getPrimary()
        if getmetatable(primary) ~= HOUND.Contact.Emitter or primary.pos.p == nil or primary.uncertenty_data == nil then return str end
        local phoneticGridPos,phoneticBulls = primary:getTtsData(false,1)
        local reportedName = self:getName() .. " "
        if NATO then
            reportedName = ""
        end
        str = reportedName .. self:getDesignation(NATO)
        if primary:isAccurate() then
            str = str .. ", reported"
        else
            str = str .. ", " .. HoundUtils.TTS.getVerbalContactAge(self.last_seen,true,NATO)
        end
        if NATO then
            str = str .. " bullseye " .. phoneticBulls
        else
            str = str .. " at " .. phoneticGridPos
        end
        if not primary:isAccurate() then
            str = str .. ", accuracy " .. HoundUtils.TTS.getVerbalConfidenceLevel( primary.uncertenty_data.r )
        end
        str = str .. "."
        return str
    end

    function HOUND.Contact.Site:generateIntelBrief()
        if #self.emitters == 0 then return end
        local items = {}

        for _,emitter in ipairs(self.emitters) do
            local body = emitter:generateIntelBrief()
            if body ~= "" then
                local entry = table.concat({self:getName(),self:getDesignation(true),body,self.DcsObjectName},",")
                table.insert(items,entry)
            end
        end
        return items
    end

    function HOUND.Contact.Site:export()
        local report = {
            name = self:getName(),
            DcsObjectName = self:getDcsName(),
            gid = self.gid % 100,
            Type = self:getDesignation(true),
            last_seen = self.last_seen,
            emitters = {}
        }
        if #self.emitters == 0 then return report end
        for _,emitter in ipairs(self.emitters) do
            table.insert(report.emitters,emitter:export())
        end
        return l_mist.utils.deepCopy(report)
    end
end--- Hound Comms Manager (Base class)
do
    local HoundUtils = HOUND.Utils
    HOUND.Comms.Manager = {}
    HOUND.Comms.Manager.__index = HOUND.Comms.Manager

    function HOUND.Comms.Manager:create(sector,houndConfig,settings)
        if (not houndConfig and type(houndConfig) ~= "table") or
            (not sector and type(sector) ~= "string") then
                HOUND.Logger.warn("[Hound] - Comm Controller could not be initilized, missing params")
                return nil
        end
        local CommsManager = {}
        setmetatable(CommsManager, HOUND.Comms.Manager)
        CommsManager.enabled = false
        CommsManager.transmitter = nil
        CommsManager.sector = nil
        CommsManager.houndConfig = houndConfig

        CommsManager._queue = {
            {},{},{}
        }

        CommsManager.settings = {
            freq = 250.000,
            volume = "1.0",
            name = "Hound",
            speed = 0,
            voice = nil,
            gender = nil,
            culture = nil,
            interval = 0.5,
            freqAlias = nil
        }

        CommsManager.preferences = {
            enabletts = true,
            enabletext = false
        }

        if not HoundUtils.TTS.isAvailable() then
            CommsManager.preferences.enabletts = false
        end

        CommsManager.scheduler = nil

        if type(settings) == "table" and HOUND.Length(settings) > 0 then
            CommsManager:updateSettings(settings)
        end
        return CommsManager
    end

    function HOUND.Comms.Manager:updateSettings(settings)
        for k,v in pairs(settings) do
            local k0 = tostring(k):lower()
            if HOUND.setContainsValue({"enabletts","enabletext","alerts"},k0) then
                self.preferences[k0] = v
            else
                self.settings[k0] = v
            end
        end
    end
    function HOUND.Comms.Manager:enable()
        self.enabled = true
        if self.scheduler == nil then
            self.scheduler = timer.scheduleFunction(self.TransmitFromQueue, self, timer.getTime() + self.settings.interval)
        end
        self:startCallbackLoop()
    end

    function HOUND.Comms.Manager:disable()
        if self.scheduler then
            timer.removeFunction(self.scheduler)
            self.scheduler = nil
        end
        self:stopCallbackLoop()
        self.enabled = false
    end

    function HOUND.Comms.Manager:isEnabled()
        return self.enabled
    end

    function HOUND.Comms.Manager:getSettings(key)
        local k0 = tostring(key):lower()
        if HOUND.setContainsValue({"enabletts","enabletext","alerts"},k0) then
            return self.preferences[tostring(key):lower()]
        else
            return self.settings[tostring(key):lower()]
        end
    end

    function HOUND.Comms.Manager:setSettings(key,value)
        local k0 = tostring(key):lower()
        if HOUND.setContainsValue({"enabletts","enabletext","alerts"},k0) then
            self.preferences[k0] = value
        else
            self.settings[k0] = value
        end
    end

    function HOUND.Comms.Manager:enableText()
        self:setSettings("enableText",true)
    end

    function HOUND.Comms.Manager:disableText()
        self:setSettings("enableText",false)
    end

    function HOUND.Comms.Manager:enableTTS()
        if HoundUtils.TTS.isAvailable() then
            self:setSettings("enableTTS",true)
        end
    end

    function HOUND.Comms.Manager:disableTTS()
        self:setSettings("enableTTS",false)
    end

    function HOUND.Comms.Manager:enableAlerts()
        self:setSettings("alerts",true)
    end

    function HOUND.Comms.Manager:disableAlerts()
        self:setSettings("alerts",false)
    end

    function HOUND.Comms.Manager:setTransmitter(transmitterName)
        if not transmitterName then transmitterName = "" end
        local candidate = Unit.getByName(transmitterName)
        if not HoundUtils.Dcs.isUnit(candidate) then
            candidate = StaticObject.getByName(transmitterName)
        end
        if not HoundUtils.Dcs.isStaticObject(candidate) and self.transmitter then
            self:removeTransmitter()
            return
        end
        if self.transmitter ~= candidate then
            self.transmitter = candidate
            HOUND.EventHandler.publishEvent({
                    id = HOUND.EVENTS.TRANSMITTER_ADDED,
                    houndId = self.houndConfig:getId(),
                    initiator = self.sector,
                    transmitter = candidate
                })
        end
    end

    function HOUND.Comms.Manager:removeTransmitter()
        if self.transmitter ~= nil then
            self.transmitter = nil
            HOUND.EventHandler.publishEvent({
                    id = HOUND.EVENTS.TRANSMITTER_REMOVED,
                    houndId = self.houndConfig:getId(),
                    initiator = self.sector
                })
        end
    end

    function HOUND.Comms.Manager:getCallsign()
        return self:getSettings("name")
    end

    function HOUND.Comms.Manager:setCallsign(callsign)
        if type(callsign) == "string" then
            self:setSettings("name",callsign)
        end
    end

    function HOUND.Comms.Manager:getFreq()
        return self:getFreqs()[1]
    end

    function HOUND.Comms.Manager:getFreqs()
        local freqs = string.split(self.settings.freq,",")
        local mod = string.split(self.settings.modulation,",")
        local retval = {}

        for i,freq in ipairs(freqs) do
            local str = string.format("%.3f",tonumber(freq)) .. " " .. (mod[i] or HoundUtils.TTS.getdefaultModulation(freq))
            table.insert(retval,str)
        end
        return retval
    end

    function HOUND.Comms.Manager:getAlias()
        return self:getSettings("freqAlias")
    end

    function HOUND.Comms.Manager:setAlias(alias)
        if type(alias) == "string" then
            self:setSettings("freqAlias",alias)
        end
    end

    function HOUND.Comms.Manager:addMessageObj(obj)
        if obj.coalition == nil or not self.enabled then return end
        if obj.txt == nil and obj.tts == nil then return end
        if obj.priority == nil or obj.priority > 3 or obj.priority < 0 then obj.priority = 3 end
        if obj.priority == 0 then
            obj.priority = 1
            obj.push = true
        end
        if obj.priority == "loop" then
            self.loop.msg = obj
            return
        end
        if obj.gid and type(obj.gid) ~= "table" then
            obj.gid = {obj.gid}
        end
        if obj.contactId ~= nil then
            for id,queueObj in ipairs(self._queue[obj.priority]) do
                if obj.gid == queueObj.gid and obj.contactId == queueObj.contactId then
                    self._queue[obj.priority][id].txt = obj.txt
                    self._queue[obj.priority][id].tts = obj.tts
                    return
                end
            end
        end
        if obj.push then
            table.insert(self._queue[obj.priority],1,obj)
        else
            table.insert(self._queue[obj.priority],obj)
        end
    end

    function HOUND.Comms.Manager:addMessage(coalition,msg,prio)
        if msg == nil or coalition == nil or ( type(msg) ~= "string" and string.len(tostring(msg)) <= 0) or not self.enabled then return end
        if prio == nil or prio > 3 or prio < 0 then prio = 3 end

        local obj = {
            coalition = coalition,
            tts = msg,
            priority = prio
        }
        self:addMessageObj(obj)
    end

    function HOUND.Comms.Manager:addTxtMsg(coalition,msg,prio)
        if msg == nil or string.len(tostring(msg)) == 0 or coalition == nil  or not self.enabled then return end
        if prio == nil then prio = 1 end
        local obj = {
            coalition = coalition,
            priority = prio,
            txt = msg
        }
        self:addMessageObj(obj)
    end

    function HOUND.Comms.Manager:getNextMsg()
        for i,v in ipairs(self._queue) do
            if #v > 0 then return table.remove(self._queue[i],1) end
        end
    end

    function HOUND.Comms.Manager:getTransmitterPos()
        if self.transmitter == nil then return nil end
        if self.transmitter ~= nil and (self.transmitter:isExist() == false or self.transmitter:getLife() < 1) then
            return false
        end
        local pos = self.transmitter:getPoint()
        local transmitterObjectCat, transmitterSubCat = self.transmitter:getCategory()
        if transmitterObjectCat == Object.Category.STATIC or (transmitterObjectCat == Object.Category.UNIT and transmitterSubCat == Unit.Category.GROUND_UNIT) then
            local verticalOffset = (self.transmitter:getDesc()["box"]["max"]["y"] + 5) or 20
            pos.y = pos.y + verticalOffset
        end
        return pos
    end

    function HOUND.Comms.Manager.TransmitFromQueue(gSelf)
        local msgObj = gSelf:getNextMsg()
        local readTime = gSelf.settings.interval
        if msgObj == nil then return timer.getTime() + readTime end

        local transmitterPos = gSelf:getTransmitterPos()

        if transmitterPos == false then
            env.info("[Hound] - Transmitter destroyed")
            HOUND.EventHandler.publishEvent({
                    id = HOUND.EVENTS.TRANSMITTER_DESTROYED,
                    houndId = gSelf.houndConfig:getId(),
                    initiator = gSelf.sector,
                    transmitter = gSelf.transmitter
                })

            return timer.getTime() + 10
        end

        if gSelf.enabled and HoundUtils.TTS.isAvailable() and msgObj.tts ~= nil and gSelf.preferences.enabletts then
            HoundUtils.TTS.Transmit(msgObj.tts,msgObj.coalition,gSelf.settings,transmitterPos)
            readTime = HoundUtils.TTS.getReadTime(msgObj.tts,gSelf.settings.speed,gSelf.settings.googletts)

        end

        if gSelf.enabled and gSelf.preferences.enabletext and msgObj.txt ~= nil then
            readTime =  HoundUtils.TTS.getReadTime(msgObj.tts,gSelf.settings.speed) or HoundUtils.TTS.getReadTime(msgObj.txt,gSelf.settings.speed)
            if msgObj.gid then
                for _,gid in ipairs(msgObj.gid) do
                    trigger.action.outTextForGroup(gid,msgObj.txt,readTime+2)
                end
            else
                trigger.action.outTextForCoalition(msgObj.coalition,msgObj.txt,readTime+2)
            end
        end
        return timer.getTime() + readTime + gSelf.settings.interval
    end

    function HOUND.Comms.Manager:startCallbackLoop()
        return nil
    end

    function HOUND.Comms.Manager:stopCallbackLoop()
        return nil
    end

    function HOUND.Comms.Manager:SetMsgCallback()
        return nil
    end

    function HOUND.Comms.Manager:runCallback()
        return nil
    end
end

do

    HOUND.Comms.InformationSystem = {}
    HOUND.Comms.InformationSystem = HOUND.inheritsFrom(HOUND.Comms.Manager)

    function HOUND.Comms.InformationSystem:create(sector,houndConfig,settings)
        local instance = self:superClass():create(sector,houndConfig,settings)
        setmetatable(instance, HOUND.Comms.InformationSystem)
        self.__index = self

        instance.settings.freq = 250.500
        instance.settings.interval = 4
        instance.settings.speed = 1
        instance.preferences.reportewr = false

        if settings and type(settings) == "table" then
            instance:updateSettings(settings)
        end

        instance.callback = {
            scheduler = nil,
            func = nil,
            args = nil,
            interval = 300
        }

        instance.loop = {
            body = "",
            msg = nil,
            reportIdx = 'Z'
        }

        return instance
    end

    function HOUND.Comms.InformationSystem:reportEWR(state)
        if type(state) == "boolean" then
            self:setSettings("reportEWR",state)
        end
    end

    function HOUND.Comms.InformationSystem:startCallbackLoop()
        if self.enabled and not self.callback.scheduler then
            self.callback.scheduler = timer.scheduleFunction(self.runCallback, self, timer.getTime()+0.1)
        end
    end

    function HOUND.Comms.InformationSystem:stopCallbackLoop()
        if self.callback.scheduler then
            timer.removeFunction(self.callback.scheduler)
            self.callback.scheduler = nil
        end
        self.loop.msg = nil
        self.loop.header = ""
        self.loop.body = ""
        self.loop.footer = ""
        self.callback = {}
    end

    function HOUND.Comms.InformationSystem:SetMsgCallback(callback,args)
        if callback ~= nil and type(callback) == "function" then
            self.callback.func = callback
            self.callback.args = args
            self.callback.interval = self.houndConfig:getAtisUpdateInterval()
        end
        if self.callback.scheduler == nil and self.scheduler ~= nil then
            self.startCallbackLoop()
        end
    end

    function HOUND.Comms.InformationSystem:runCallback()
        local nextDelay = self.callback.interval or 300
        if self.callback ~= nil and type(self.callback.func) == "function"  then
            self.callback.func(self.callback.args,self.loop,self.preferences)
        end
        return timer.getTime() + nextDelay
    end

    function HOUND.Comms.InformationSystem:getNextMsg()
        if self.loop and not self.loop.msg then
            self:runCallback()
        end
        if self.loop and self.loop.msg and self.loop.msg.tts ~= nil and (string.len(self.loop.msg.tts) > 0 or string.len(self.loop.msg.txt) > 0) then
            return self.loop.msg
        end
    end
end

do

    HOUND.Comms.Controller = {}
    HOUND.Comms.Controller = HOUND.inheritsFrom(HOUND.Comms.Manager)

    function HOUND.Comms.Controller:create(sector,houndConfig,settings)
        local instance = self:superClass():create(sector,houndConfig,settings)
        setmetatable(instance, HOUND.Comms.Controller)
        self.__index = self

        instance.preferences.alerts = true

        if settings and type(settings) == "table" then
            instance:updateSettings(settings)
        end

        return instance
    end
end
do
    HOUND.Comms.Notifier = {}
    HOUND.Comms.Notifier = HOUND.inheritsFrom(HOUND.Comms.Manager)

    function HOUND.Comms.Notifier:create(sector,houndConfig,settings)
        local instance = self:superClass():create(sector,houndConfig,settings)
        setmetatable(instance, HOUND.Comms.Notifier)
        self.__index = self

        instance.settings.freq = "243.000,121.500"
        instance.settings.modulation = "AM,AM"
        instance.settings.speed = 1

        instance.preferences.alerts = true

        if settings and type(settings) == "table" then
            instance:updateSettings(settings)
        end
        return instance
    end
end
do
    local HoundUtils = HOUND.Utils

    HOUND.ElintWorker = {}
    HOUND.ElintWorker.__index = HOUND.ElintWorker

    local l_math = math
    function HOUND.ElintWorker.create(HoundInstanceId)
        local instance = {}
        setmetatable(instance, HOUND.ElintWorker)

        instance.contacts = {}
        instance.platforms = {}
        instance.sites = {}
        instance.settings =  HOUND.Config.get(HoundInstanceId)
        instance.coalitionId = nil
        instance.TrackIdCounter = 0
        return instance
    end

    function HOUND.ElintWorker:setCoalition(coalitionId)
        if not coalitionId then return false end
        if not self.settings:getCoalition() then
            self.settings:setCoalition(coalitionId)
            return true
        end
        return false
    end

    function HOUND.ElintWorker:getCoalition()
        return self.settings:getCoalition()
    end

    function HOUND.ElintWorker:getNewTrackId()
        self.TrackIdCounter = self.TrackIdCounter + 1
        return self.TrackIdCounter
    end

    function HOUND.ElintWorker:addPlatform(platformName)
        local candidate = Unit.getByName(platformName) or StaticObject.getByName(platformName)
        if HOUND.Utils.Dcs.isUnit(platformName) or HOUND.Utils.Dcs.isStaticObject(platformName) then
            candidate = platformName
        end

        if not (HOUND.Utils.Dcs.isUnit(candidate) or HOUND.Utils.Dcs.isStaticObject(candidate)) then
            HOUND.Logger.warn("Failed to add platform "..platformName..". Could not find the Object.")
            return false
        end
        if self:getCoalition() == nil and candidate ~= nil then
            self:setCoalition(candidate:getCoalition())
        end

        if candidate ~= nil and candidate:getCoalition() == self:getCoalition()
            and not HOUND.setContainsValue(self.platforms,candidate) and HOUND.DB.isValidPlatform(candidate) then
                table.insert(self.platforms, candidate)
                HOUND.EventHandler.publishEvent({
                    id = HOUND.EVENTS.PLATFORM_ADDED,
                    initiator = candidate,
                    houndId = self.settings:getId(),
                    coalition = self.settings:getCoalition()
                })
                return true
        end
        HOUND.Logger.warn("[Hound] - Failed to add platform "..platformName..". Make sure you use unit name and that all requirments are met.")
        return false
    end

    function HOUND.ElintWorker:removePlatform(platformName)
        local candidate = Unit.getByName(platformName)
        if candidate == nil then
            candidate = StaticObject.getByName(platformName)
        end

        if candidate ~= nil then
            for k,v in ipairs(self.platforms) do
                if v == candidate then
                    table.remove(self.platforms, k)
                    HOUND.EventHandler.publishEvent({
                        id = HOUND.EVENTS.PLATFORM_REMOVED,
                        initiator = candidate,
                        houndId = self.settings:getId(),
                        coalition = self.settings:getCoalition()
                    })
                    return true
                end
            end
        end
        return false
    end

    function HOUND.ElintWorker:platformRefresh()
        if HOUND.Length(self.platforms) < 1 then return end
        for id,platform in ipairs(self.platforms) do
            if platform:isExist() == false or platform:getLife() <1 then
                table.remove(self.platforms, id)
                HOUND.EventHandler.publishEvent({
                    id = HOUND.EVENTS.PLATFORM_DESTROYED,
                    initiator = platform,
                    houndId = self.settings:getId(),
                    coalition = self.settings:getCoalition()
                })
            end
        end
    end

    function HOUND.ElintWorker:removeDeadPlatforms()
        if HOUND.Length(self.platforms) < 1 then return end
        for id,platform in ipairs(self.platforms) do
            if platform:isExist() == false or platform:getLife() <1  or (platform:getCategory() ~= Object.Category.STATIC and platform:isActive() == false) then
                table.remove(self.platforms, id)
                HOUND.EventHandler.publishEvent({
                    id = HOUND.EVENTS.PLATFORM_DESTROYED,
                    initiator = platform,
                    houndId = self.settings:getId(),
                    coalition = self.settings:getCoalition()
                })
            end
        end
    end

    function HOUND.ElintWorker:countPlatforms()
        return HOUND.Length(self.platforms)
    end

    function HOUND.ElintWorker:listPlatforms()
        local platforms = {}
        for _,platform in ipairs(self.platforms) do
            table.insert(platforms,platform:getName())
        end
        return platforms
    end

    function HOUND.ElintWorker:isContact(emitter)
        if emitter == nil then return false end
        local emitterName = nil
        if type(emitter) == "string" then
            emitterName = emitter
        end
        if type(emitter) == "table" and emitter.getName ~= nil then
            emitterName = emitter:getName()
        end
        return HOUND.setContains(self.contacts,emitterName)
    end

    function HOUND.ElintWorker:addContact(emitter)
        if emitter == nil or emitter.getName == nil then return end
        local emitterName = emitter:getName()
        if self.contacts[emitterName] ~= nil then return emitterName end
        self.contacts[emitterName] = HOUND.Contact.Emitter:New(emitter, self:getCoalition(), self:getNewTrackId())
        local site = self:getSite(self.contacts[emitterName])
        if site then
            site:addEmitter(self.contacts[emitterName])
        else
            HOUND.Logger.debug("failed to create site")
        end
        self.contacts[emitterName]:queueEvent(HOUND.EVENTS.RADAR_NEW)
        return emitterName
    end

    function HOUND.ElintWorker:getContact(emitter,getOnly)
        if emitter == nil then return nil end
        local emitterName = nil
        if type(emitter) == "string" then
            emitterName = emitter
        end
        if HoundUtils.Dcs.isUnit(emitter) then
            emitterName = emitter:getName()
        end
        if getmetatable(emitter) == HOUND.Contact.Emitter then
            emitterName = emitter:getDcsName()
        end
        if emitterName ~= nil and self.contacts[emitterName] ~= nil then return self.contacts[emitterName] end
        if not self.contacts[emitterName] and type(emitter) == "table" and not getOnly then
            self:addContact(emitter)
            return self.contacts[emitterName]
        end
        return nil
    end

    function HOUND.ElintWorker:removeContact(emitterName)
        if type(emitterName) == "table" and getmetatable(emitterName) == HOUND.Contact.Emitter then
            emitterName = emitterName:getDcsName()
        end
        if type(emitterName) ~= "string" then return false end
        if self.contacts[emitterName] then
            local site = self:getSite(self.contacts[emitterName]:getDcsGroupName(),true)
            if site then
                site:removeEmitter(self.contacts[emitterName])
            end

            self.contacts[emitterName]:updateDeadDcsObject()
        end
        self.contacts[emitterName] = nil
        return true
    end

    function HOUND.ElintWorker:setPreBriefedContact(emitter)
        if not emitter:isExist() then return end
        local contact = self:getContact(emitter)
        local contactState = contact:useUnitPos(l_math.min(self.settings:getMarkerType(),HOUND.MARKER.POINT))
        if contactState then
            HOUND.EventHandler.publishEvent({
                id = contactState,
                initiator = contact,
                houndId = self.settings:getId(),
                coalition = self.settings:getCoalition()
            })
        end
    end

    function HOUND.ElintWorker:setDead(emitter)
        local contact = self:getContact(emitter,true)
        if contact then
            contact:setDead()
         end
    end

    function HOUND.ElintWorker:ensureSitePrimaryHasPos(fireGrp,refPos)
        local site = self:getSite(fireGrp,true)
        if site then
            site:ensurePrimaryHasPos(refPos)
        end
    end
    function HOUND.ElintWorker:AlertOnLaunch(fireGrp)
        if not self.settings:getAlertOnLaunch() then return end
        local site = self:getSite(fireGrp,true)
        if site then
            local event = site:LaunchDetected()
            if type(event) == "table" then
                event.houndId = self.settings:getId()
                event.coalition = self.settings:getCoalition()
                HOUND.EventHandler.publishEvent(event)
            end
        end
    end

    function HOUND.ElintWorker:isTracked(emitter)
        if emitter == nil then return false end
        if type(emitter) =="string" and self.contacts[emitter] ~= nil then return true end
        if type(emitter) == "table" and emitter.getName ~= nil and self.contacts[emitter:getName()] ~= nil then return true end
        return false
    end

    function HOUND.ElintWorker:isSite(site)
        if site == nil then return false end
        local groupName = nil
        if type(site) == "string" then
            groupName = site
        end
        if HOUND.Utils.Dcs.isGroup(site) then
            groupName = site:getName()
        end
        return HOUND.setContains(self.sites,groupName)
    end

    function HOUND.ElintWorker:addSite(emitter)
        if emitter == nil or emitter.getName == nil then return end
        local groupName = emitter:getDcsGroupName()
        if self.sites[groupName] ~= nil then return groupName end
        self.sites[groupName] = HOUND.Contact.Site:New(emitter, self:getCoalition(), self:getNewTrackId())
        self.sites[groupName]:queueEvent(HOUND.EVENTS.SITE_NEW)
        return groupName
    end

    function HOUND.ElintWorker:getSite(emitter,getOnly)
        if emitter == nil then return nil end
        local groupName = nil
        if type(emitter) == "string" then
            groupName = emitter
        end
        if HOUND.Utils.Dcs.isGroup(emitter) then
            groupName = emitter:getName()
        elseif HOUND.Utils.Dcs.isUnit(emitter) then
            groupName = Group.getName(emitter:getGroup())
        end
        if getmetatable(emitter) == HOUND.Contact.Emitter then
            groupName = emitter:getDcsGroupName()
        end
        if groupName ~= nil and self.sites[groupName] ~= nil then return self.sites[groupName] end
        if not self.sites[groupName] and type(emitter) == "table" and not getOnly then
            self:addSite(emitter)
            return self.sites[groupName]
        end
        return nil
    end

    function HOUND.ElintWorker:removeSite(groupName)
        if type(groupName) == "table" and getmetatable(groupName) == HOUND.Contact.Site then
            groupName = groupName:getDcsName()
        end
        if type(groupName) ~= "string" then return false end
        self.sites[groupName] = nil
        return true
    end

    function HOUND.ElintWorker:UpdateMarkers()
        if self.settings:getUseMarkers() then
            local drawSites = self.settings:getMarkSites()
            local emitterMarker = self.settings:getMarkerType()
            for _,site in pairs(self.sites) do
                site:updateMarkers(emitterMarker,drawSites)
            end
        end
    end

    function HOUND.ElintWorker:Sniff(GroupName)
        self:removeDeadPlatforms()

        for _, contact in pairs(self.contacts) do
            contact:KalmanPredict()
        end
        if HOUND.Length(self.platforms) == 0 then return end
        local Radars = {}
        if GroupName then
            Radars = HoundUtils.Elint.getActiveRadarsInGroup(GroupName)
        else
            Radars = HoundUtils.Elint.getActiveRadars(self:getCoalition())
        end
        if HOUND.Length(Radars) == 0 then return end
        for _,RadarName in ipairs(Radars) do
            local radar = Unit.getByName(RadarName)
            local radarPos = radar:getPosition().p
            radarPos.y = radarPos.y + radar:getDesc()["box"]["max"]["y"] -- use vehicle bounting box for height
            local _,isRadarTracking = radar:getRadar()

            isRadarTracking = HoundUtils.Dcs.isUnit(isRadarTracking)

            for _,platform in ipairs(self.platforms) do
                local platformData = HOUND.DB.getPlatformData(platform)

                if HoundUtils.Geo.checkLOS(platformData.pos, radarPos) then
                    local contact = self:getContact(radar)
                    local sampleAngularResolution = HOUND.DB.getSensorPrecision(platform,contact:getWavelenght(isRadarTracking))
                    if sampleAngularResolution < l_math.rad(10.0) then
                        local az,el = HoundUtils.Elint.getAzimuth( platformData.pos, radarPos, sampleAngularResolution )
                        if not platformData.isAerial then
                            el = nil
                        end

                        if not platform.isStatic and self.settings:getPosErr() then
                            for axis,value in pairs(platformData.pos) do
                                platformData.pos[axis] = value + platformData.posErr[axis]
                            end
                        end
                        local signalStrength = HoundUtils.Elint.getSignalStrength(platformData.pos,radarPos,contact.detectionRange)
                        local datapoint = HOUND.Contact.Datapoint.New(platform,platformData.pos, az, el, signalStrength, timer.getAbsTime(),sampleAngularResolution,platformData.isStatic)
                        contact:AddPoint(datapoint)
                    end
                end
            end
        end
    end

    function HOUND.ElintWorker:Process()
        if HOUND.Length(self.contacts) < 1 then return end
        for contactName, contact in pairs(self.contacts) do
            if contact ~= nil then
                local contactState = contact:processData()
                if contactState == HOUND.EVENTS.RADAR_DETECTED then
                    if self.settings:getUseMarkers() then
                        contact:updateMarker(self.settings:getMarkerType())
                    end
                end

                if contact:isTimedout() and not contact:getPreBriefed() then
                    contactState = contact:CleanTimedout()
                end
                if self.settings:getBDA() and contact:isAlive() and contact:getLife() < 1 then
                    contact:setDead()
                end
                if not contact:isAlive() and (contact:getLastSeen() > 60 or contact:getPreBriefed()) then
                    contact:destroy()
                end

                contactState = contact:getState()
                if contactState and contactState ~= HOUND.EVENTS.NO_CHANGE then
                    local contactEvents = contact:getEventQueue()
                    while #contactEvents > 0 do
                        local event = table.remove(contactEvents,1)
                        event.houndId = self.settings:getId()
                        event.coalition = self.settings:getCoalition()
                        HOUND.EventHandler.publishEvent(event)
                    end
                end
            end
        end
        for _, site in pairs(self.sites) do
            if site ~= nil then
                site:processData()
                local siteEvents = site:getEventQueue()
                while #siteEvents > 0 do
                    local event = table.remove(siteEvents,1)
                    event.houndId = self.settings:getId()
                    event.coalition = self.settings:getCoalition()
                    HOUND.EventHandler.publishEvent(event)
                end
            end
        end
    end
end
do
    local HoundUtils = HOUND.Utils

    function HOUND.ElintWorker:listContactsInSector(sectorName)
        local emitters = {}
        for _,emitter in ipairs(self.contacts) do
            if emitter:isInSector(sectorName) then
                table.insert(emitters,emitter)
            end
        end
        table.sort(emitters,HoundUtils.Sort.ContactsByRange)
        return emitters
    end

    function HOUND.ElintWorker:listAllContacts(sectorName)
        if sectorName then
            local contacts = {}
            for _,emitter in pairs(self.contacts) do
                if emitter:isInSector(sectorName) then
                        table.insert(contacts,emitter)
                end
            end
            return contacts
        end
        return self.contacts
    end

    function HOUND.ElintWorker:listAllContactsByRange(sectorName)
        return self:sortContacts(HoundUtils.Sort.ContactsByRange,sectorName)
    end

    function HOUND.ElintWorker:countContacts(sectorName)
        if sectorName then
            local contacts = 0
            for _,contact in pairs(self.contacts) do
                if contact:isInSector(sectorName) then
                    contacts = contacts + 1
                end
            end
            return contacts
        end
        return HOUND.Length(self.contacts)
    end

    function HOUND.ElintWorker:getContacts(sectorName)
        local contacts = {}
        for _,emitter in pairs(self.contacts) do
            if sectorName then
                if emitter:isInSector(sectorName) then
                    table.insert(contacts,emitter)
                end
            else
                table.insert(contacts,emitter)
            end
        end
        return contacts
    end

    function HOUND.ElintWorker:sortContacts(sortFunc,sectorName)
        if type(sortFunc) ~= "function" then return end
        local sorted = self:getContacts(sectorName)
        table.sort(sorted, sortFunc)
        return sorted
    end

    function HOUND.ElintWorker:countSites(sectorName)
        if sectorName then
            local sites = 0
            for _,site in pairs(self.sites) do
                if site:isInSector(sectorName) then
                    sites = sites + 1
                end
            end
            return sites
        end
        return HOUND.Length(self.sites)
    end

    function HOUND.ElintWorker:getSites(sectorName)
        local sites = {}
        for _,site in pairs(self.sites) do
            if sectorName then
                if site:isInSector(sectorName) then
                    table.insert(sites,site)
                end
            else
                table.insert(sites,site)
            end
        end
        return sites
    end

    function HOUND.ElintWorker:sortSites(sortFunc,sectorName)
        if type(sortFunc) ~= "function" then return end
        local sorted = self:getSites(sectorName)
        table.sort(sorted, sortFunc)
        return sorted
    end

    function HOUND.ElintWorker:listAllSites(sectorName)
        if sectorName then
            local sites = {}
            for _,site in pairs(self.sites) do
                if site:isInSector(sectorName) then
                        table.insert(sites,site)
                end
            end
            return sites
        end
        return self.sites
    end

    function HOUND.ElintWorker:listAllSitesByRange(sectorName)
        return self:sortSites(HoundUtils.Sort.ContactsByRange,sectorName)
    end

end    --- HOUND.ContactManager
do
    HOUND.ContactManager = {
        _workers = {}
    }

    HOUND.ContactManager.__index = HOUND.ContactManager

    function HOUND.ContactManager.get(HoundInstanceId)
        if HOUND.ContactManager._workers[HoundInstanceId] then
            return HOUND.ContactManager._workers[HoundInstanceId]
        end

        local worker = HOUND.ElintWorker.create(HoundInstanceId)
        HOUND.ContactManager._workers[HoundInstanceId] = worker

        return HOUND.ContactManager._workers[HoundInstanceId]
    end
end
do
    local l_mist = HOUND.Mist
    local l_math = math
    local HoundUtils = HOUND.Utils

    HOUND.Sector = {}
    HOUND.Sector.__index = HOUND.Sector

    function HOUND.Sector.create(HoundId, name, settings, priority)
        if type(HoundId) ~= "number" or type(name) ~= "string" then
            HOUND.Logger.warn("[Hound] - HOUND.Sector.create() missing params")
            return
        end

        local instance = {}
        setmetatable(instance, HOUND.Sector)
        instance.name = name
        instance._hSettings = HOUND.Config.get(HoundId)
        instance._contacts = HOUND.ContactManager.get(HoundId)
        instance.callsign = "HOUND"
        instance.settings = {
            controller = nil,
            atis = nil,
            notifier = nil,
            transmitter = nil,
            zone = nil,
            hound_menu = nil
        }
        instance.comms = {
            controller = nil,
            atis = nil,
            notifier = nil,
            enrolled = {},
            menu = {
                root = nil ,noData = nil
            }
        }
        instance.priority = priority or 10

        if settings ~= nil and type(settings) == "table" and HOUND.Length(settings) > 0 then
            instance:updateSettings(settings)
        end
        if instance.name ~= "default" then
            instance:setCallsign(instance._hSettings:getUseNATOCallsigns())
        end
        return instance
    end

    function HOUND.Sector:updateSettings(settings)
        for k, v in pairs(settings) do
            local k0 = tostring(k):lower()
            if type(v) == "table" and
                HOUND.setContainsValue({"controller", "atis", "notifier"}, k0) then
                if not self.settings[k0] then
                    self.settings[k0] = {}
                end
                for k1, v1 in pairs(v) do
                    self.settings[k0][tostring(k1):lower()] = v1
                end
                self.settings[k0]["name"] = self.callsign
            else
                self.settings[k0] = v
            end
        end
        self:updateServices()
    end

    function HOUND.Sector:destroy()
        self:removeRadioMenu()
        for _,contact in pairs(self._contacts:listAllContacts()) do
            contact:removeSector(self.name)
        end
        return
    end

    function HOUND.Sector:updateServices()
        if type(self.settings.controller) == "table" then
            if not self.comms.controller then
                self.settings.controller.name = self.callsign
                self.comms.controller = HOUND.Comms.Controller:create(self.name,self._hSettings,self.settings.controller)
            else
                self.settings.controller.name = self.callsign
                self.comms.controller:updateSettings(self.settings.controller)
                self.comms.controller:setCallsign(self.callsign)

            end
        end
        if type(self.settings.atis) == "table" then
            if not self.comms.atis then
                self.settings.atis.name = self.callsign
                self.comms.atis = HOUND.Comms.InformationSystem:create(self.name,self._hSettings,self.settings.atis)
            else
                self.settings.atis.name = self.callsign
                self.comms.atis:updateSettings(self.settings.atis)
                self.comms.atis:setCallsign(self.callsign)
            end
        end
        if type(self.settings.notifier) == "table" then
            if not self.comms.notifier then
                self.settings.notifier.name = self.callsign
                self.comms.notifier = HOUND.Comms.Notifier:create(self.name,self._hSettings,self.settings.notifier)
            else
                self.settings.notifier.name = self.callsign
                self.comms.notifier:updateSettings(self.settings.notifier)
                self.comms.notifier:setCallsign(self.callsign)
            end
        end
        if self.settings.zone and type(self.settings.zone) ~= "table" then
            self:setZone(self.settings.zone)
        end
        if self.settings.transmitter then
            self:updateTransmitter()
        end
    end

    function HOUND.Sector:getName()
        return self.name
    end

    function HOUND.Sector:getPriority()
        return self.priority
    end

    function HOUND.Sector:setCallsign(callsign, NATO)
        local namePool = "GENERIC"
        if callsign ~= nil and type(callsign) == "boolean" then
            NATO = callsign
            callsign = nil
        end
        if NATO == true then namePool = "NATO" end

        callsign = string.upper(callsign or HoundUtils.getHoundCallsign(namePool))

        while HOUND.setContainsValue(self._hSettings.callsigns, callsign) do
            callsign = HoundUtils.getHoundCallsign(namePool)
        end

        if self.callsign ~= nil or self.callsign ~= "HOUND" then
            for k, v in ipairs(self._hSettings.callsigns) do
                if v == self.callsign then
                    table.remove(self._hSettings.callsigns, k)
                end
            end
        end
        table.insert(self._hSettings.callsigns, callsign)
        self.callsign = callsign
        self:updateServices()
    end

    function HOUND.Sector:getCallsign()
        return self.callsign
    end

    function HOUND.Sector:getZone()
        return self.settings.zone
    end

    function HOUND.Sector:hasZone()
        return self:getZone() ~= nil
    end

    function HOUND.Sector:setZone(zonecandidate)
        if self.name == "default" then
            HOUND.Logger.warn("[Hound] - cannot set zone to default sector")
            return
        end
        local zone = nil
        if not zonecandidate then
            zone = HoundUtils.Zone.getDrawnZone(self.name .. " Sector")
        end
        if type(zonecandidate) == "string" then
            zone = HoundUtils.Zone.getDrawnZone(zonecandidate) or HoundUtils.Zone.getGroupRoute(zonecandidate)
        end
        if zone then
            self.settings.zone = zone
        end
    end

    function HOUND.Sector:removeZone() self.settings.zone = nil end

    function HOUND.Sector:setTransmitter(userTransmitter)
        if not userTransmitter then return end
        self.settings.transmitter = userTransmitter
        self:updateTransmitter()
    end

    function HOUND.Sector:updateTransmitter()
        for k, v in pairs(self.comms) do
            if k ~= "menu" and v.setTransmitter then v:setTransmitter(self.settings.transmitter) end
        end
    end

    function HOUND.Sector:removeTransmitter()
        self.settings.transmitter = nil
        for k, v in pairs(self.comms) do
            if k ~= "menu" then v:removeTransmitter() end
        end
    end

    function HOUND.Sector:enableController(userSettings)
        if not userSettings then userSettings = {} end
        local settings = { controller = userSettings }
        self:updateSettings(settings)
        self:updateTransmitter()
        self.comms.controller:enable()
        self:populateRadioMenu()
    end

    function HOUND.Sector:disableController()
        if self.comms.controller then
            self:removeRadioMenu()
            self.comms.controller:disable()
        end
    end

    function HOUND.Sector:removeController()
        self.settings.controller = nil
        if self.comms.controller then
            self:disableController()
            self.comms.controller = nil
        end
    end

    function HOUND.Sector:getControllerFreq()
        if self.comms.controller then
            return self.comms.controller:getFreqs()
        end
        return {}
    end

    function HOUND.Sector:hasController() return self.comms.controller ~= nil end

    function HOUND.Sector:isControllerEnabled()
        return self.comms.controller ~= nil and self.comms.controller:isEnabled()
    end

    function HOUND.Sector:getController()
        if self:hasController() then
            return self.comms.controller
        end
        return
    end

    function HOUND.Sector:transmitOnController(msg,priority)
        if not self.comms.controller or not self.comms.controller:isEnabled() then return end
        if type(msg) ~= "string" then return end
        if type(priority) ~= "number" then priority = 1 end
        local msgObj = {priority = priority,coalition = self._hSettings:getCoalition()}
        msgObj.tts = msg
        if self.comms.controller:isEnabled() then
            self.comms.controller:addMessageObj(msgObj)
        end
    end

    function HOUND.Sector:enableText()
        if self.comms.controller then self.comms.controller:enableText() end
    end

    function HOUND.Sector:disableText()
        if self.comms.controller then self.comms.controller:disableText() end
    end

    function HOUND.Sector:enableAlerts()
        if self.comms.controller then self.comms.controller:enableAlerts() end
    end

    function HOUND.Sector:disableAlerts()
        if self.comms.controller then self.comms.controller:disableAlerts() end
    end

    function HOUND.Sector:enableTTS()
        if self.comms.controller then self.comms.controller:enableTTS() end
    end

    function HOUND.Sector:disableTTS()
        if self.comms.controller then self.comms.controller:disableTTS() end
    end

    function HOUND.Sector:enableAtis(userSettings)
        if not userSettings then userSettings = {} end
        local settings = { atis = userSettings }
        self:updateSettings(settings)
        self:updateTransmitter()
        self.comms.atis:SetMsgCallback(HOUND.Sector.generateAtis, self)
        self.comms.atis:enable()
    end

    function HOUND.Sector:disableAtis()
        if self.comms.atis then self.comms.atis:disable() end
    end

    function HOUND.Sector:removeAtis()
        self.settings.atis = nil
        if self.comms.atis then
            self:disableAtis()
            self.comms.atis = nil
        end
    end

    function HOUND.Sector:getAtisFreq()
        if self.comms.atis then
            return self.comms.atis:getFreqs()
        end
        return {}
    end

    function HOUND.Sector:reportEWR(state)
        if self.comms.atis then self.comms.atis:reportEWR(state) end
    end

    function HOUND.Sector:hasAtis() return self.comms.atis ~= nil end

    function HOUND.Sector:isAtisEnabled()
        return self.comms.atis ~= nil and self.comms.atis:isEnabled()
    end

    function HOUND.Sector:enableNotifier(userSettings)
        if not userSettings then userSettings = {} end
        local settings = { notifier = userSettings }
        self:updateSettings(settings)
        self:updateTransmitter()
        self.comms.notifier:enable()
    end

    function HOUND.Sector:disableNotifier()
        if self.comms.notifier then self.comms.notifier:disable() end
    end

    function HOUND.Sector:removeNotifier()
        self.settings.notifier = nil
        if self.comms.notifier then
            self:disableNotifier()
            self.comms.notifier = nil
        end
    end

    function HOUND.Sector:getNotifierFreq()
        if self.comms.notifier then
            return self.comms.notifier:getFreqs()
        end
        return {}
    end

    function HOUND.Sector:hasNotifier()
        return self.comms.notifier ~= nil
    end

    function HOUND.Sector:isNotifierEnabled()
        return self.comms.notifier ~= nil and self.comms.notifier:isEnabled()
    end

    function HOUND.Sector:getNotifier()
        if self:hasNotifier() then
            return self.comms.notifier
        end
        return
    end

    function HOUND.Sector:transmitOnNotifier(msg,priority)
        if not self.comms.notifier or not self.comms.notifier:isEnabled() then return end
        if type(msg) ~= "string" then return end
        if type(priority) ~= "number" then priority = 1 end

        local msgObj = {priority = priority,coalition = self._hSettings:getCoalition()}
        msgObj.tts = msg
        if self.comms.notifier:isEnabled() then
            self.comms.notifier:addMessageObj(msgObj)
        end
    end

    function HOUND.Sector:getContacts()
        local effectiveSectorName = self.name
        if not self:getZone() then
            effectiveSectorName = "default"
        end
        return self._contacts:listAllContactsByRange(effectiveSectorName)
    end

    function HOUND.Sector:countContacts()
        local effectiveSectorName = self.name
        if not self:getZone() then
            effectiveSectorName = "default"
        end
        return self._contacts:countContacts(effectiveSectorName)
    end

    function HOUND.Sector:updateSectorMembership(contact)
        local inSector, threatsSector = HoundUtils.Polygon.threatOnSector(self.settings.zone,contact:getPos(),contact:getMaxWeaponsRange())
        contact:updateSector(self.name, inSector, threatsSector)
        self._contacts:getSite(contact):updateSector()
    end

    function HOUND.Sector:getSites()
        local effectiveSectorName = self.name
        if not self:getZone() then
            effectiveSectorName = "default"
        end
        return self._contacts:listAllSitesByRange(effectiveSectorName)
    end

    function HOUND.Sector:countSites()
        local effectiveSectorName = self.name
        if not self:getZone() then
            effectiveSectorName = "default"
        end
        return self._contacts:countSites(effectiveSectorName)
    end

    function HOUND.Sector.removeRadioMenu(self)
        if self.comms.menu.root ~= nil then
            missionCommands.removeItemForCoalition(self._hSettings:getCoalition(),self.comms.menu.root)
        end
        self.comms.menu = {}
        self.comms.menu.root = nil
        self.comms.enrolled = {}
    end

    function HOUND.Sector:findGrpInPlayerList(grpId,playersList)
        if not playersList or type(playersList) ~= "table" then
            playersList = self.comms.enrolled
        end
        local playersInGrp = {}
        for _,player in pairs(playersList) do
            if player.groupId == grpId then
                table.insert(playersInGrp,player)
            end
        end
        return playersInGrp
    end

    function HOUND.Sector:getSubscribedGroups()
        local subscribedGid = {}
        for _,player in pairs(self.comms.enrolled) do
            local grpId = player.groupId
            if not HOUND.setContainsValue(subscribedGid,grpId) then
                table.insert(subscribedGid,grpId)
            end
        end
        return subscribedGid
    end

    function HOUND.Sector:validateEnrolled()
        if HOUND.Length(self.comms.enrolled) == 0 then return end
        for playerUnitName, player in pairs(self.comms.enrolled) do
            local playerUnit = Unit.getByName(playerUnitName)
            if not HoundUtils.Dcs.isHuman(playerUnit) then
                self.comms.enrolled[player.unitName] = nil
            end
        end
    end

    function HOUND.Sector.checkIn(args,skipAck)
        local gSelf = args["self"]
        local player = args["player"]
        for _,PlayerInGrp in pairs(HOUND.Utils.Dcs.getPlayersInGroup(player.groupName)) do
            gSelf.comms.enrolled[PlayerInGrp.unitName] = PlayerInGrp
        end
        gSelf:populateRadioMenu()
        if not skipAck then
            gSelf:TransmitCheckInAck(player)
        end
    end

    function HOUND.Sector.checkOut(args,skipAck,onlyPlayer)
        local gSelf = args["self"]
        local player = args["player"]
        gSelf.comms.enrolled[player.unitName] = nil

        if not onlyPlayer then
            for _,PlayerInGrp in pairs(HOUND.Utils.Dcs.getPlayersInGroup(player.groupName)) do
                if player.unitName ~= PlayerInGrp.unitName then
                    gSelf.comms.enrolled[PlayerInGrp.unitName] = nil
                end
            end
        end
        gSelf:populateRadioMenu()
        if not skipAck then
            gSelf:TransmitCheckOutAck(player)
        end
    end

    function HOUND.Sector:isNotifiying()
        local controller = self.comms.controller
        local notifier = self.comms.notifier
        if not controller and not notifier then return false end
        if (not controller or not controller:getSettings("alerts") or not controller:isEnabled()) and (not notifier or not notifier:isEnabled())
             then return false end
        return true
    end
    function HOUND.Sector:getTransmissionAnnounce(index)
        local messages = {
            "Attention All Aircraft! This is " .. self.callsign .. ". ",
            "All Aircraft, " .. self.callsign .. ". ",
            "This is " .. self.callsign .. ". "
        }
        local retIndex = l_math.random(1,#messages)
        if type(index) == "number" then
            retIndex = l_math.max(1,l_math.min(#messages,index))
        end
        return messages[retIndex]
    end

    function HOUND.Sector:notifyEmitterDead(contact)
        if not self:isNotifiying() then return end

        local controller = self.comms.controller
        local notifier = self.comms.notifier

        local contactPrimarySector = contact:getPrimarySector()
        if self.name ~= "default" and self.name ~= contactPrimarySector then return end

        if self.name == contactPrimarySector then
            contactPrimarySector = nil
        end

        local announce = self:getTransmissionAnnounce()
        local enrolledGid = self:getSubscribedGroups()

        local msg = {coalition =  self._hSettings:getCoalition(), priority = 3, gid=enrolledGid}
        msg.contactId = contact:getId()
            msg.txt = contact:generateDeathReport(false,contactPrimarySector)
            msg.tts = announce .. contact:generateDeathReport(true,contactPrimarySector)
        if controller and controller:isEnabled() and controller:getSettings("alerts") then
            controller:addMessageObj(msg)
        end
        if notifier and notifier:isEnabled() then
            notifier:addMessageObj(msg)
        end
    end

    function HOUND.Sector:notifyEmitterNew(contact)
        if not self:isNotifiying() then return end

        local controller = self.comms.controller
        local notifier = self.comms.notifier

        local contactPrimarySector = contact:getPrimarySector()
        if self.name ~= "default" and self.name ~= contactPrimarySector then return end

        if self.name == contactPrimarySector then
            contactPrimarySector = nil
        end

        local announce = self:getTransmissionAnnounce()
        local enrolledGid = self:getSubscribedGroups()

        local msg = {coalition = self._hSettings:getCoalition(), priority = 2 , gid=enrolledGid}
        msg.contactId = contact:getId()

            msg.txt = self.callsign .. " Reports " .. contact:generatePopUpReport(false,contactPrimarySector)
            msg.tts = announce .. contact:generatePopUpReport(true,contactPrimarySector)

        if controller and controller:isEnabled() and controller:getSettings("alerts") then
            controller:addMessageObj(msg)
        end

        if notifier and notifier:isEnabled() then
            notifier:addMessageObj(msg)
        end
    end

    function HOUND.Sector:notifySiteIdentified(site)
        if not self:isNotifiying() then return end

        local controller = self.comms.controller
        local notifier = self.comms.notifier

        local sitePrimarySector = site:getPrimarySector()
        if self.name ~= "default" and self.name ~= sitePrimarySector then return end

        if self.name == sitePrimarySector then
            sitePrimarySector = nil
        end

        local announce = self:getTransmissionAnnounce()
        local enrolledGid = self:getSubscribedGroups()

        local msg = {coalition = self._hSettings:getCoalition(), priority = 2 , gid=enrolledGid}

            msg.txt = self.callsign .. " Reports " .. site:generateIdentReport(false,sitePrimarySector)
            msg.tts = announce .. site:generateIdentReport(true,sitePrimarySector)

        if controller and controller:isEnabled() and controller:getSettings("alerts") then
            controller:addMessageObj(msg)
        end

        if notifier and notifier:isEnabled() then
            notifier:addMessageObj(msg)
        end
    end
    function HOUND.Sector:notifySiteNew(site)
        if not self:isNotifiying() then return end

        local controller = self.comms.controller
        local notifier = self.comms.notifier

        local sitePrimarySector = site:getPrimarySector()
        if self.name ~= "default" and self.name ~= sitePrimarySector then return end

        if self.name == sitePrimarySector then
            sitePrimarySector = nil
        end

        local announce = self:getTransmissionAnnounce()
        local enrolledGid = self:getSubscribedGroups()

        local msg = {coalition = self._hSettings:getCoalition(), priority = 2 , gid=enrolledGid}
        msg.contactId = site:getId()
            msg.txt = self.callsign .. " Reports " .. site:generatePopUpReport(false,sitePrimarySector)
            msg.tts = announce .. site:generatePopUpReport(true,sitePrimarySector)
        if controller and controller:isEnabled() and controller:getSettings("alerts") then
            controller:addMessageObj(msg)
        end

        if notifier and notifier:isEnabled() then
            notifier:addMessageObj(msg)
        end

    end
    function HOUND.Sector:notifySiteDead(site,isDead)
        if not self:isNotifiying() then return end

        local controller = self.comms.controller
        local notifier = self.comms.notifier

        local sitePrimarySector = site:getPrimarySector()
        if self.name ~= "default" and self.name ~= sitePrimarySector then return end

        if self.name == sitePrimarySector then
            sitePrimarySector = nil
        end

        local announce = self:getTransmissionAnnounce()
        local enrolledGid = self:getSubscribedGroups()

        local msg = {coalition = self._hSettings:getCoalition(), priority = 3 , gid=enrolledGid}
        msg.contactId = site:getId()
        local body = {}
        if isDead then
            body.txt = site:generateDeathReport(false,sitePrimarySector)
            body.tts = site:generateDeathReport(true,sitePrimarySector)
        else
            body.txt = site:generateAsleepReport(false,sitePrimarySector)
            body.tts = site:generateAsleepReport(true,sitePrimarySector)
        end
        msg.txt = self.callsign .. " Reports " .. body.txt
        msg.tts = announce .. body.tts
        if controller and controller:isEnabled() and controller:getSettings("alerts") then
            controller:addMessageObj(msg)
        end

        if notifier and notifier:isEnabled() then
            notifier:addMessageObj(msg)
        end
    end

function HOUND.Sector:notifySiteLaunching(site)
        if not self._hSettings:getAlertOnLaunch() or not self:isNotifiying() then return end
        local controller = self.comms.controller
        local notifier = self.comms.notifier
        local sitePrimarySector = site:getPrimarySector()
        if self.name ~= "default" and self.name ~= sitePrimarySector then return end

        if self.name == sitePrimarySector then
            sitePrimarySector = nil
        end

        local enrolledGid = self:getSubscribedGroups()

        local msg = {coalition = self._hSettings:getCoalition(), priority = 1 , gid=enrolledGid}
        msg.contactId = site:getId()
        msg.txt = site:generateLaunchAlert(false,sitePrimarySector)
        msg.tts = site:generateLaunchAlert(true,sitePrimarySector)

        if controller and controller:isEnabled() and controller:getSettings("alerts") then
            controller:addMessageObj(msg)
        end

        if notifier and notifier:isEnabled() then
            notifier:addMessageObj(msg)
        end

    end

    function HOUND.Sector:generateAtis(loopData,AtisPreferences)
        local body = ""
        local numberEWR = 0
        local siteCount = self:countSites()
        if siteCount > 0 then
            local sortedSites = self:getSites()
            for _, site in pairs(sortedSites) do
                if site:getPos() ~= nil then
                    if not site.isEWR or
                        (AtisPreferences.reportewr and site.isEWR) then
                        body = body ..
                                    site:generateTtsBrief(
                                        self._hSettings:getNATO()) .. " "
                    end
                    if (not AtisPreferences.reportewr and site.isEWR) then
                        numberEWR = numberEWR + 1
                    end
                end
            end
            if numberEWR > 0 then
                body = body .. numberEWR .. " EWRs are tracked. "
            end
        end

        if body == "" then
            if self._hSettings:getNATO() then
                body = ". EMPTY. "
            else
                body = "No threats had been detected "
            end
        end

        if loopData.body == body then return end
        loopData.body = body

        local reportId
        reportId, loopData.reportIdx =
            HoundUtils.getReportId(loopData.reportIdx)

        local header = self.callsign
        local footer = reportId .. "."

        if self._hSettings:getNATO() then
            header = header .. " Lowdown "
            footer = "Lowdown " .. footer
        else
            header = header .. " SAM information "
            footer = "you have " .. footer
        end
        header = header .. reportId .. " " ..
                                    HoundUtils.TTS.getTtsTime() .. ". "

        local msgObj = {
            coalition = self._hSettings:getCoalition(),
            priority = "loop",
            updateTime = timer.getAbsTime(),
            tts = header .. loopData.body .. footer
        }
        loopData.msg = msgObj
    end

    function HOUND.Sector.TransmitSamReport(args)
        local gSelf = args["self"]
        local contact = gSelf._contacts:getContact(args["contact"],true)
        local requester = args["requester"]
        local coalitionId = gSelf._hSettings:getCoalition()
        local msgObj = {coalition = coalitionId, priority = 1}
        local useDMM = false
        local preferMGRS = false

        if requester == nil then return end
        if contact.isEWR then msgObj.priority = 2 end

        if requester ~= nil then
            msgObj.gid = requester.groupId
            useDMM =  HoundUtils.useDMM(requester.type)
            preferMGRS = HoundUtils.useMGRS(requester.type)
        end

        if gSelf.comms.controller:isEnabled() then
            HOUND.Logger.debug(args["contact"].. ":\n" .. HOUND.Mist.utils.tableShow(contact))

            msgObj.contactId = contact:getId()
            msgObj.tts = contact:generateTtsReport(useDMM,preferMGRS)
            if requester ~= nil then
                msgObj.tts = HoundUtils.getFormationCallsign(requester,gSelf._hSettings:getCallsignOverride()) .. ", " .. gSelf.callsign .. ", " .. msgObj.tts
            end
            if gSelf.comms.controller:getSettings("enableText") == true then
                msgObj.txt = contact:generateTextReport(useDMM)
            end
            HOUND.Logger.debug("msg: \n"..HOUND.Mist.utils.tableShow(msgObj))
            gSelf.comms.controller:addMessageObj(msgObj)
        end
    end

    function HOUND.Sector:TransmitCheckInAck(player)
        if not player then return end
        local msgObj = {priority = 1,coalition = self._hSettings:getCoalition(), gid = player.groupId}
        local msg = HoundUtils.getFormationCallsign(player,self._hSettings:getCallsignOverride()) .. ", " .. self.callsign .. ", Roger. "
        if self:countContacts() > 0 then
            msg = msg .. "Tasking is available."
        else
            msg = msg .. "No known threats."
        end
        msgObj.tts = msg
        msgObj.txt = msg
        if self.comms.controller:isEnabled() then
            self.comms.controller:addMessageObj(msgObj)
        end
    end

    function HOUND.Sector:TransmitCheckOutAck(player)
        if not player then return end
        local msgObj = {priority = 1,coalition = self._hSettings:getCoalition(), gid = player.groupId}
        local msg = HoundUtils.getFormationCallsign(player,self._hSettings:getCallsignOverride()) .. ", " .. self.callsign .. ", copy checking out. "
        msgObj.tts = msg .. "Frequency change approved."
        msgObj.txt = msg
        if self.comms.controller:isEnabled() then
            self.comms.controller:addMessageObj(msgObj)
        end
    end
end

do
    local l_mist = HOUND.Mist

    function HOUND.Sector:getRadioItemsText()
        local menuItems = {
            ['noData'] = "No radars are currently tracked"
        }
        local sites = self:getSites()
        if HOUND.Length(sites) > 0 then
            menuItems.noData = nil
            for _, site in ipairs(sites) do
                if site:getPos() then
                    table.insert(menuItems,site:getRadioItemsText())
                end
            end
        end
        return menuItems
    end

    function HOUND.Sector:createCheckIn()
        for _,player in pairs(self.comms.enrolled) do
            local playerUnit = Unit.getByName(player.unitName)
            if not HOUND.Utils.Dcs.isHuman(playerUnit) then
                    self.comms.enrolled[player.unitName] = nil
            end
        end
        grpMenuDone = {}
        for _,player in pairs(HOUND.DB.HumanUnits.byName[self._hSettings:getCoalition()]) do
            local grpId = player.groupId
            local playerUnit = Unit.getByName(player.unitName)
            if playerUnit and not grpMenuDone[grpId] then
                grpMenuDone[grpId] = true

                if not self.comms.menu[player] then
                    self.comms.menu[player] = self:getMenuObj()
                end

                local grpMenu = self.comms.menu[player]
                local grpPage = self:getMenuPage(grpMenu,grpId,self.comms.menu.root)
                if grpMenu.items.check_in ~= nil then
                    grpMenu.items.check_in = missionCommands.removeItemForGroup(grpId,grpMenu.items.check_in)
                end

                if HOUND.setContainsValue(self.comms.enrolled, player) then
                    grpMenu.items.check_in =
                        missionCommands.addCommandForGroup(grpId,
                                            self.comms.controller:getCallsign() .. " (" ..
                                            self.comms.controller:getFreq() ..") - Check out",
                                            grpPage,HOUND.Sector.checkOut,
                                            {
                                                self = self,
                                                player = player
                                            })
                else
                    grpMenu.items.check_in =
                        missionCommands.addCommandForGroup(grpId,
                                                        self.comms.controller:getCallsign() ..
                                                            " (" ..
                                                            self.comms.controller:getFreq() ..
                                                            ") - Check In",
                                                            grpPage,
                                                        HOUND.Sector.checkIn, {
                            self = self,
                            player = player
                        })
                end
            end
        end
    end

    function HOUND.Sector:populateRadioMenu()
        if self.comms.menu.root ~= nil then
            self.comms.menu.root =
                missionCommands.removeItemForCoalition(self._hSettings:getCoalition(),self.comms.menu.root)
                self.comms.menu.root = nil
        end

        if not self.comms.controller or not self.comms.controller:isEnabled() then return end

        if HOUND.Length(self.comms.menu) > 0 then
            for player,grpMenu in pairs(self.comms.menu) do
                self:removeMenuItems(grpMenu,player.groupId)
            end
        end

        if not self.comms.menu.root then
            self.comms.menu.root =
            missionCommands.addSubMenuForCoalition(self._hSettings:getCoalition(),
                                               self.name,
                                               self._hSettings:getRadioMenu())
        end
        self:validateEnrolled()
        self:createCheckIn()
        local sitesData = self:getRadioItemsText()
        local typesSpotted = {}

        if HOUND.setContains(sitesData.noData) and
            not self.comms.menu.noData then
                self.comms.menu.noData = missionCommands.addCommandForCoalition(self._hSettings:getCoalition(),
                            sitesData.noData,
                            self.comms.menu.root, timer.getAbsTime)
        end

        if not HOUND.setContains(sitesData.noData) then
            if self.comms.menu.noData ~= nil then
                self.comms.menu.noData = missionCommands.removeItemForCoalition(self._hSettings:getCoalition(),
                self.comms.menu.noData)
            end
        end

        local grpMenuDone = {}
        if HOUND.Length(self.comms.enrolled) > 0 then
            if HOUND.Length(sitesData) and not HOUND.setContains(sitesData.noData) then
                for _,siteData in ipairs(sitesData) do
                    if not HOUND.setContainsValue(typesSpotted,siteData.typeAssigned) then
                        table.insert(typesSpotted,siteData.typeAssigned)
                    end
                end
            end
            for _, player in pairs(self.comms.enrolled) do
                local grpId = player.groupId
                local grpMenu = self.comms.menu[player]

                if not grpMenuDone[grpId] and grpMenu ~= nil then
                    grpMenuDone[grpId] = true

                    if not grpMenu.pages then
                        grpMenu.pages = {}
                    end
                    if not grpMenu.items then
                        grpMenu.items = {}
                    end
                    if not grpMenu.objs then
                        grpMenu.objs = {}
                    end

                    for _,typeAssigned in ipairs(typesSpotted) do
                        local newObj = self:getMenuObj()
                        local grpPage = self:getMenuPage(grpMenu,grpId,self.comms.menu.root)
                        grpMenu.items[typeAssigned] = missionCommands.addSubMenuForGroup(grpId,typeAssigned,grpPage)
                        self:getMenuPage(newObj,grpId,grpMenu.items[typeAssigned])
                        grpMenu.objs[typeAssigned] = newObj
                    end

                    for _, siteData in ipairs(sitesData) do
                        local typeMenu = grpMenu.objs[siteData.typeAssigned]
                        self:removeSiteRadioItems(typeMenu,player,siteData)
                        self:addSiteRadioItems(typeMenu,player,siteData)
                    end
                end
            end
        end
    end

    function HOUND.Sector:removeMenuItems(menu,grpId)
        if HOUND.Length(menu.objs) > 0 then
            for objName,obj in pairs (menu.objs) do
                menu.objs[objName]=self:removeMenuItems(obj,grpId)
            end
        end
        if HOUND.Length(menu.items) > 0 then
            for itemName,item in pairs(menu.items) do
                menu.items[itemName]=missionCommands.removeItemForGroup(grpId,item)
            end
        end
        if HOUND.Length(menu.pages) > 0 then
            for idx,page in ipairs(menu.pages) do
                if page ~= nil then
                   menu.pages[idx] = missionCommands.removeItemForGroup(grpId,page)
                end
            end
        end
        return nil
    end

    function HOUND.Sector:getMenuPage(menu,grpId,parent)
        if not menu or type(grpId) ~= "number" then return end

        if not menu.pages then
            menu.pages = {}
        end
        if not menu.items then
            menu.items = {}
        end
        if not menu.objs then
            menu.objs = {}
        end
        if HOUND.Length(menu.pages) == 0 and type(parent) == "table" then
            table.insert(menu.pages,parent)
        end

        local totalItems = (HOUND.Length(menu.items) + #menu.pages)-1
        if (totalItems == HOUND.MENU_PAGE_LENGTH) or (totalItems % #menu.pages) == HOUND.MENU_PAGE_LENGTH then
            table.insert(menu.pages,missionCommands.addSubMenuForGroup(grpId,"More (Page " .. #menu.pages+1 .. ")", menu.pages[#menu.pages]))
        end
        return menu.pages[#menu.pages]
    end

    function HOUND.Sector:getMenuObj()
        return {
            objs = {},
            pages = {},
            items = {}
        }
    end

    function HOUND.Sector:addSiteRadioItems(typeMenu,requester,siteData)
        local playerGid = requester.groupId
        local typePage = self:getMenuPage(typeMenu,playerGid)
        local siteObj = self:getMenuObj()

        typeMenu.items[siteData.dcsName] = missionCommands.addSubMenuForGroup(playerGid, siteData.txt, typePage)
        typeMenu.objs[siteData.dcsName] = siteObj

        for _,emitterData in ipairs(siteData.emitters) do
            local sitePage = self:getMenuPage(typeMenu.objs[siteData.dcsName],playerGid,typeMenu.items[siteData.dcsName])
            siteObj.items[emitterData.dcsName] = missionCommands.addCommandForGroup(playerGid, emitterData.txt, sitePage, self.TransmitSamReport,{self=self,contact=emitterData.dcsName,requester=requester})
        end
    end

    function HOUND.Sector:removeSiteRadioItems(typeMenu,requester,siteData)

        if not self.comms.controller or not self.comms.controller:isEnabled() or not typeMenu or not requester then
            return
        end
        local playerGid = requester.groupId

        local siteObj = typeMenu.objs[siteData.dcsName]
        if HOUND.setContains(siteObj,'items') then
            for emitterName,emitter in (siteObj.items) do
                siteObj.items[emitterName] = missionCommands.removeItemForGroup(playerGid,emitter)
            end
        end

        if HOUND.setContains(typeMenu.items[siteData.dcsName]) then
            typeMenu.items[siteData.dcsName] = missionCommands.removeItemForGroup(playerGid,typeMenu.items[siteData.dcsName] )
        end
    end
end--- Hound Main interface
do
    local HoundUtils = HOUND.Utils
    HoundElint = {}
    HoundElint.__index = HoundElint

    function HoundElint:create(platformName)
        if not platformName then
            HOUND.Logger.error("Failed to initialize Hound instace. Please provide coalition")
            return
        end
        local elint = {}
        setmetatable(elint, HoundElint)
        elint.settings = HOUND.Config.get()
        elint.HoundId = elint.settings:getId()
        elint.contacts = HOUND.ContactManager.get(elint.HoundId)
        elint.elintTaskID = nil
        elint.radioAdminMenu = nil
        elint.coalitionId = nil

        elint.timingCounters = {}

        if platformName ~= nil then
            if type(platformName) == "string" then
                elint:addPlatform(platformName)
            else
                elint:setCoalition(platformName)
            end
        end

        elint.sectors = {
            default = HOUND.Sector.create(elint.HoundId,"default",nil,100)
        }
        elint:defaultEventHandler()

        HOUND.INSTANCES[elint.HoundId] = elint
        return elint
    end

    function HoundElint:destroy()
        self:systemOff(false)
        self:defaultEventHandler(false)

        for name,sector in pairs(self.sectors) do
            self.sectors[name] = sector:destroy()
        end
        self:purgeRadioMenu()
        HOUND.INSTANCES[self.HoundId] = nil
        self.contacts = nil
        self.settings = nil
        return nil
    end

    function HoundElint:getId()
        return self.settings:getId()
    end

    function HoundElint:getCoalition()
        return self.settings:getCoalition()
    end

    function HoundElint:setCoalition(side)
        if side == coalition.side.BLUE or side == coalition.side.RED then
            return self.settings:setCoalition(side)
        end
        return false
    end

    function HoundElint:onScreenDebug(value)
        return self.settings:setOnScreenDebug(value)
    end

    function HoundElint:addPlatform(platformName)
        return self.contacts:addPlatform(platformName)
    end

    function HoundElint:removePlatform(platformName)
        return self.contacts:removePlatform(platformName)
    end

    function HoundElint:countPlatforms()
        return self.contacts:countPlatforms()
    end

    function HoundElint:listPlatforms()
        return self.contacts:listPlatforms()
    end

    function HoundElint:countContacts(sectorName)
        return self.contacts:countContacts(sectorName)
    end

    function HoundElint:countActiveContacts(sectorName)
        local activeContactCount = 0
        local contacts =  self.contacts:getContacts(sectorName)
        for _,contact in pairs(contacts) do
            if contact:isActive() then
                activeContactCount = activeContactCount +1
            end
        end
        return activeContactCount
    end

    function HoundElint:countPreBriefedContacts(sectorName)
        local pbContactCount = 0
        local contacts =  self.contacts:getContacts(sectorName)
        for _,contact in pairs(contacts) do
            if contact:isAccurate() then
                pbContactCount = pbContactCount +1
            end
        end
        return pbContactCount
    end

    function HoundElint:preBriefedContact(DCS_Object_Name,codeName)
        if type(DCS_Object_Name) ~= "string" then return end
        local units = {}
        local obj = Group.getByName(DCS_Object_Name) or Unit.getByName(DCS_Object_Name)
        local grpName = DCS_Object_Name
        if not obj then
            HOUND.Logger.info("Cannot pre-brief " .. DCS_Object_Name .. ": object does not exist.")
            return
        end
        if HoundUtils.Dcs.isGroup(obj) then
            units = obj:getUnits()
        elseif HoundUtils.Dcs.isUnit(obj) then
            table.insert(units,obj)
            grpName = obj:getGroup():getName()
        end

        for _,unit in pairs(units) do
            if unit:getCoalition() ~= self.settings:getCoalition() and unit:isExist() and HOUND.setContains(HOUND.DB.Radars,unit:getTypeName()) then
                self.contacts:setPreBriefedContact(unit)
            end
        end
        if type(codeName) == "string" then
            local site = self.contacts:getSite(grpName,true)
            if site then
                site:setName(codeName)
            end
        end
    end

    function HoundElint:markDeadContact(radarUnit)
        local units={}
        local obj = radarUnit
        if type(radarUnit) == "string" then
            obj = Group.getByName(radarUnit) or Unit.getByName(radarUnit)
        end
        if HoundUtils.Dcs.isGroup(obj) then
            units = obj:getUnits()
            for _,unit in pairs(units) do
                unit = unit:getName()
            end
        elseif HoundUtils.Dcs.isUnit(obj) then
            table.insert(units,obj:getName())
        end
        if not obj then
            if type(radarUnit) == "string" then
                table.insert(units,radarUnit)
            else
                HOUND.Logger.info("Cannot mark as dead: object does not exist.")
                return
            end
        end
        for _,unit in pairs(units) do
            if self.contacts:isContact(unit) then
                self.contacts:setDead(unit)
            end
        end

    end

    function HoundElint:AlertOnLaunch(fireUnit)
        if not self:getAlertOnLaunch() or (not HoundUtils.Dcs.isGroup(fireUnit) and not HoundUtils.Dcs.isUnit(fireUnit)) then return end

        self.contacts:AlertOnLaunch(fireUnit)
    end

    function HoundElint:countSites(sectorName)
        return self.contacts:countSites(sectorName)
    end

    function HoundElint:addSector(sectorName,sectorSettings,priority)
        if type(sectorName) ~= "string" then return false end
        if string.lower(sectorName) == "default" or string.lower(sectorName) == "all" then
            HOUND.Logger.info(sectorName.. " is a reserved sector name")
            return nil
        end
        priority = priority or 50
        if not self.sectors[sectorName] then
            self.sectors[sectorName] = HOUND.Sector.create(self.settings:getId(),sectorName,sectorSettings,priority)
            if self.settings:getOnScreenDebug() then
                HOUND.Logger.onScreenDebug("Sector " .. sectorName  .. " was added to Hound instance ".. self:getId(),10)
            end
            return self.sectors[sectorName]
        end

        return nil
    end

    function HoundElint:removeSector(sectorName)
        if sectorName == nil then return false end
        self.sectors[sectorName] = self.sectors[sectorName]:destroy()
        if self.settings:getOnScreenDebug() then
            HOUND.Logger.onScreenDebug("Sector " .. sectorName .. " was removed from Hound instance ".. self:getId(),10)
        end
        return true
    end

    function HoundElint:updateSectorSettings(sectorName,sectorSettings,subSettingName)
        if sectorName == nil then sectorName = "default" end
        if not self.sectors[sectorName] then
            env.warn("No sector named ".. sectorName .." was found.")
            return false
        end
        if sectorSettings == nil or type(sectorSettings) ~= "table" then return false end
        local sector = self.sectors[sectorName]
        if subSettingName ~= nil and type(subSettingName) == "string" then
            local subSetting = string.lower(subSettingName)
            if subSetting == "controller" or subSetting == "atis" or subSetting == "notifier" then
                local generatedSettings = {}
                generatedSettings[subSetting] = sectorSettings
                sector:updateSettings(generatedSettings)
                return true
            end
        end
        sector:updateSettings(sectorSettings)
        return true
    end

    function HoundElint:listSectors(element)
        local sectors = {}
        for name,sector in pairs(self.sectors) do
            local addToList = true
            if element then
                if string.lower(element) == "controller" then
                    addToList=sector:hasController()
                end
                if string.lower(element) == "atis" then
                    addToList=sector:hasAtis()
                end
                if string.lower(element) == "notifier" then
                    addToList=sector:hasNotifier()
                end
                if string.lower(element) == "zone" then
                    addToList=sector:hasZone()
                end
            end

            if addToList then
                table.insert(sectors,name)
            end
        end
        return sectors
    end

    function HoundElint:getSectors(element)
        local sectors = {}
        for _,sector in pairs(self.sectors) do
            local addToList = true
            if element then
                if string.lower(element) == "controller" then
                    addToList=sector:hasController()
                end
                if string.lower(element) == "atis" then
                    addToList=sector:hasAtis()
                end
                if string.lower(element) == "notifier" then
                    addToList=sector:hasNotifier()
                end
                if string.lower(element) == "zone" then
                    addToList=sector:hasZone()
                end
            end

            if addToList then
                table.insert(sectors,sector)
            end
        end
        return sectors
    end

    function HoundElint:countSectors(element)
        return HOUND.Length(self:listSectors(element))
    end

    function HoundElint:getSector(sectorName)
        if HOUND.setContains(self.sectors,sectorName) then
            return self.sectors[sectorName]
        end
    end

    function HoundElint:enableController(sectorName,settings)
        if type(sectorName) == "table" and settings == nil then
            settings  = sectorName
            sectorName = "default"
        end
        if sectorName == nil then sectorName = "default" end
        if self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:enableController(settings)
            return
        end
        if string.lower(sectorName) == "all" and settings == nil then
            for _,sector in pairs(self.sectors) do
                sector:enableController()
            end
        end

    end

    function HoundElint:disableController(sectorName)
        if sectorName == nil then
            sectorName = "default"
        end
        if self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:disableController()
        end
        if sectorName:lower() == "all" then
            for _,sector in pairs(self.sectors) do
                sector:disableController()
            end
        end
    end

    function HoundElint:removeController(sectorName)
        if sectorName == nil then
            sectorName = "default"
        end
        if sectorName:lower() == "all" then
            for _,sector in pairs(self.sectors) do
                sector:removeController()
            end
        elseif self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:removeController()
        end
    end

    function HoundElint:configureController(sectorName,settings)
        if sectorName == nil and settings == nil then return end
        if sectorName == nil and type(settings) == "table" then
            sectorName = "default"
        end
        if type(sectorName) =="table" and settings == nil then
            settings = sectorName
            sectorName = "default"
        end
        local controllerSettings = { controller = settings}
        if self.sectors[sectorName] == nil then
            self:addSector(sectorName,controllerSettings)
        elseif self.sectors[sectorName] then
            self.sectors[sectorName]:updateSettings(controllerSettings)
        end
    end

    function HoundElint:getControllerFreq(sectorName)
        sectorName = sectorName or "default"
        return self.sectors[sectorName]:getControllerFreq() or {}
    end

    function HoundElint:getControllerState(sectorName)
        sectorName = sectorName or "default"

        if self.sectors[sectorName] then
            return (self.sectors[sectorName]:isControllerEnabled())
        end
        return false
    end

    function HoundElint:transmitOnController(sectorName,msg,priority)
        if not sectorName or not msg then return end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:transmitOnController(msg,priority)
            return
        end
        if sectorName == "all" then
            for _,sector in pairs(self.sectors) do
                sector:transmitOnController(msg,priority)
            end
        end
    end

    function HoundElint:enableAtis(sectorName,settings)
        if type(sectorName) == "table" and settings == nil then
            settings  = sectorName
            sectorName = "default"
        end
        if sectorName == nil then sectorName = "default" end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:enableAtis()
            end
            return
        end
        if self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:enableAtis(settings)
        end
    end

    function HoundElint:disableAtis(sectorName)
        if sectorName == nil then
            sectorName = "default"
        end
        if self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:disableAtis()
            return
        end
        if sectorName == "all" then
            for _,sector in pairs(self.sectors) do
                sector:disableAtis()
            end
        end
    end

    function HoundElint:removeAtis(sectorName)
        if sectorName == nil then
            sectorName = "default"
        end
        if sectorName == "all" then
            for _,sector in pairs(self.sectors) do
                sector:removeAtis()
            end
        elseif self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:removeAtis()
        end
    end

    function HoundElint:configureAtis(sectorName,settings)
        if sectorName == nil and settings == nil then return end
        if sectorName == nil and type(settings) == "table" then
            sectorName = "default"
        end
        if type(sectorName) =="table" and settings == nil then
            settings = sectorName
            sectorName = "default"
        end
        local userSettings = { atis = settings}
        if self.sectors[sectorName] == nil then
            self:addSector(sectorName,userSettings)
        elseif self.sectors[sectorName] then
            self.sectors[sectorName]:updateSettings(userSettings)
        end
    end

    function HoundElint:getAtisFreq(sectorName)
        sectorName = sectorName or "default"
        return self.sectors[sectorName]:getAtisFreq() or {}
    end

    function HoundElint:reportEWR(name,state)
        if type(name) == "boolean" then
            state = name
            name = "default"
        end
        if self.sectors[name] then
            self.sectors[name]:reportEWR(state)
            return
        end
        if name == "all" then
            for _,sector in pairs(self.sectors) do
                sector:reportEWR(state)
            end
        end
    end

    function HoundElint:getAtisState(sectorName)
        sectorName = sectorName or "default"
        if self.sectors[sectorName] then
            return (self.sectors[sectorName]:isAtisEnabled())
        end
        return false
    end

    function HoundElint:enableNotifier(sectorName,settings)
        if type(sectorName) == "table" and settings == nil then
            settings  = sectorName
            sectorName = "default"
        end
        if sectorName == nil then sectorName = "default" end
        if self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:enableNotifier(settings)
        end
    end

    function HoundElint:disableNotifier(sectorName)
        if sectorName == nil then
            sectorName = "default"
        end
        if sectorName == "all" then
            for _,sector in pairs(self.sectors) do
                sector:disableNotifier()
            end
            return
        end
        if self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:disableNotifier()
        end
    end

    function HoundElint:removeNotifier(sectorName)
        if sectorName == nil then
            sectorName = "default"
        end
        if sectorName == "all" then
            for _,sector in pairs(self.sectors) do
                sector:removeNotifier()
            end
            return
        end
        if self.sectors[sectorName] ~= nil then
            self.sectors[sectorName]:removeNotifier()
        end
    end

    function HoundElint:configureNotifier(sectorName,settings)
        if sectorName == nil and settings == nil then return end
        if sectorName == nil and type(settings) == "table" then
            sectorName = "default"
        end
        if type(sectorName) =="table" and settings == nil then
            settings = sectorName
            sectorName = "default"
        end
        local notifierSettings = { notifier = settings}
        if self.sectors[sectorName] == nil then
            self:addSector(sectorName,notifierSettings)
        elseif self.sectors[sectorName] then
            self.sectors[sectorName]:updateSettings(notifierSettings)
        end
    end

    function HoundElint:getNotifierFreq(sectorName)
        sectorName = sectorName or "default"
        return self.sectors[sectorName]:getNotifierFreq() or {}
    end

    function HoundElint:getNotifierState(sectorName)
        sectorName = sectorName or "default"
        if self.sectors[sectorName] then
            return (self.sectors[sectorName]:isNotifierEnabled())
        end
        return false
    end

    function HoundElint:transmitOnNotifier(sectorName,msg,priority)
        if not sectorName or not msg then return end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:transmitOnNotifier(msg,priority)
            return
        end
        if sectorName == "all" then
            for _,sector in pairs(self.sectors) do
                sector:transmitOnNotifier(msg,priority)
            end
        end
    end

    function HoundElint:enableText(sectorName)
        if sectorName == nil or type(sectorName) ~= "string" then
            sectorName = "default"
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:enableText()
            return
        end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:enableText()
            end
        end

    end

    function HoundElint:disableText(sectorName)
        if sectorName == nil or type(sectorName) ~= "string" then
            sectorName = "default"
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:disableText()
            return
        end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:disableText()
            end
        end
    end

    function HoundElint:enableTTS(sectorName)
        if sectorName == nil or type(sectorName) ~= "string" then
            sectorName = "default"
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:enableTTS()
            return
        end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:enableTTS()
            end
        end
    end

    function HoundElint:disableTTS(sectorName)
        if sectorName == nil or type(sectorName) ~= "string" then
            sectorName = "default"
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:disableTTS()
            return
        end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:disableTTS()
            end
        end
    end

    function HoundElint:enableAlerts(sectorName)
        if sectorName == nil or type(sectorName) ~= "string" then
            sectorName = "default"
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:enableAlerts()
            return
        end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:enableAlerts()
            end
        end

    end

    function HoundElint:disableAlerts(sectorName)
        if sectorName == nil or type(sectorName) ~= "string" then
            sectorName = "default"
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:disableAlerts()
            return
        end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:disableAlerts()
            end
        end
    end

    function HoundElint:setCallsign(sectorName,sectorCallsign)
        if not sectorName then return false end
        local NATO = self.settings:getUseNATOCallsigns()
        if sectorCallsign == "NATO" then
            sectorCallsign = true
        end
        if type(sectorCallsign) == "boolean" then
            NATO = sectorCallsign
            sectorCallsign = nil
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:setCallsign(sectorCallsign,NATO)
            return true
        end
        return false
    end

    function HoundElint:getCallsign(sectorName)
        if not sectorName then return "" end
        if self.sectors[sectorName] then
            return self.sectors[sectorName]:getCallsign()
        end
        return ""
    end

    function HoundElint:setTransmitter(sectorName,transmitter)
        if not sectorName and not transmitter then return end
        if sectorName and not transmitter then
            transmitter = sectorName
            sectorName = "default"
        end
        if sectorName == nil then sectorName = "default" end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:setTransmitter(transmitter)
            end
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:setTransmitter(transmitter)
        end
    end

    function HoundElint:removeTransmitter(sectorName)
        if sectorName == nil then sectorName = "default" end
        if string.lower(sectorName) == "all" then
            for _,sector in pairs(self.sectors) do
                sector:removeTransmitter()
            end
        end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:removeTransmitter()
        end
    end

    function HoundElint:getZone(sectorName)
        sectorName = sectorName or "default"
        if self.sectors[sectorName] then
            return self.sectors[sectorName]:getZone()
        end
    end

    function HoundElint:setZone(sectorName,zoneCandidate)
        if type(sectorName) ~= "string" then return end
        if type(zoneCandidate) ~= "string" and zoneCandidate ~= nil then return end
        if self.sectors[sectorName] then
            self.sectors[sectorName]:setZone(zoneCandidate)
        end
        self:updateSectorMembership()
    end

    function HoundElint:removeZone(sectorName)
        if self.sectors[sectorName] then
            self.sectors[sectorName]:removeZone()
        end
        self:updateSectorMembership()
    end

    function HoundElint:updateSectorMembership()
        local sectors = self:getSectors()
        table.sort(sectors,HoundUtils.Sort.sectorsByPriorityLowFirst)
        for _,contact in ipairs(self.contacts:listAllContacts()) do
            for _,sector in pairs(sectors) do
                sector:updateSectorMembership(contact)
            end
        end
        for _,site in ipairs(self.contacts:listAllSites()) do
            site:updateSector()
        end
    end

    function HoundElint:enableMarkers(markerType)
        if markerType and HOUND.setContainsValue(HOUND.MARKER,markerType) then
            self:setMarkerType(markerType)
        end
        return self.settings:setUseMarkers(true)
    end

    function HoundElint:disableMarkers()
        return self.settings:setUseMarkers(false)
    end

    function HoundElint:enableSiteMarkers()
        return self.settings:setMarkSites(true)
    end

    function HoundElint:disableSiteMarkers()
        return self.settings:setMarkSites(false)
    end

    function HoundElint:setMarkerType(markerType)
        if markerType and HOUND.setContainsValue(HOUND.MARKER,markerType) then
            return self.settings:setMarkerType(markerType)
        end
        return false
    end

    function HoundElint:setTimerInterval(setIntervalName,setValue)
        if self.settings and HOUND.setContains(self.settings.intervals,string.lower(setIntervalName)) then
            return self.settings:setInterval(setIntervalName,setValue)
        end
        return false
    end

    function HoundElint:enablePlatformPosErrors()
        return self.settings:setPosErr(true)
    end

    function HoundElint:disablePlatformPosErrors()
        return self.settings:setPosErr(false)
    end

    function HoundElint:getCallsignOverride()
        return self.settings:getCallsignOverride()
    end

    function HoundElint:setCallsignOverride(overrides)
        return self.settings:setCallsignOverride(overrides)
    end

    function HoundElint:getBDA()
        return self.settings:getBDA()
    end

    function HoundElint:enableBDA()
        return self.settings:setBDA(true)
    end

    function HoundElint:disableBDA()
        return self.settings:setBDA(false)
    end

    function HoundElint:getNATO()
        return self.settings:getNATO()
    end

    function HoundElint:enableNATO()
        return self.settings:setNATO(true)
    end

    function HoundElint:disableNATO()
        return self.settings:setNATO(false)
    end

    function HoundElint:getAlertOnLaunch()
        return self.settings:getAlertOnLaunch()
    end

    function HoundElint:setAlertOnLaunch(value)
        return self.settings:setAlertOnLaunch(value)
    end

    function HoundElint:useNATOCallsignes(value)
        if type(value) ~= "boolean" then return false end
        return self.settings:setUseNATOCallsigns(value)
    end

    function HoundElint:setAtisUpdateInterval(value)
        return self.settings:setAtisUpdateInterval(value)
    end

    function HoundElint:setRadioMenuParent(parent)
        local retval = self.settings:setRadioMenuParent(parent)
        if retval == true and self:isRunning() then
            self:populateRadioMenu()
        end
        return retval or false
    end

    function HoundElint.runCycle(self)
        local runTime = timer.getAbsTime()
        local nextRun = timer.getTime() + HOUND.Gaussian(self.settings.intervals.scan,self.settings.intervals.scan/10)
        if self.settings:getCoalition() == nil then return nextRun end
        if not self.contacts then return nextRun end

        self.contacts:platformRefresh()
        self.contacts:Sniff()

        if self.contacts:countContacts() > 0 then
            local doProcess = true
            local doMenus = false
            local doMarkers = false
            if self.timingCounters.lastProcess then
                doProcess = ((HoundUtils.absTimeDelta(self.timingCounters.lastProcess,runTime)/self.settings.intervals.process) > 0.99)
            end
            if self.timingCounters.lastMenus then
                doMenus = ((HoundUtils.absTimeDelta(self.timingCounters.lastMenus,runTime)/self.settings.intervals.menus) > 0.99)
            end
            if self.timingCounters.lastMarkers then
                doMarkers = ((HoundUtils.absTimeDelta(self.timingCounters.lastMarkers,runTime)/self.settings.intervals.markers) > 0.99)
            end

            if doProcess then
                self.contacts:Process()
                self:updateSectorMembership()

                self.timingCounters.lastProcess = runTime
                if not self.timingCounters.lastMarkers then
                    self.timingCounters.lastMarkers = runTime
                end
                if not self.timingCounters.lastMenus then
                    self.timingCounters.lastMenus = runTime
                end
            end

            if doMenus then
                self:populateRadioMenu()
                self.timingCounters.lastMenus = runTime
            end

            if doMarkers then
                self.contacts:UpdateMarkers()
                self.timingCounters.lastMarkers = runTime
            end
        end
        if self.settings:getOnScreenDebug() then
            HOUND.Logger.onScreenDebug(self:printDebugging(),self.settings.intervals.scan*0.75)
        end
        return nextRun
    end

    function HoundElint:purgeRadioMenu()
        for _,sector in pairs(self:getSectors()) do
            sector:removeRadioMenu()
        end
        self.settings:removeRadioMenu()
    end

    function HoundElint:populateRadioMenu()
        if not self:isRunning() or not self.contacts or self.contacts:countContacts() == 0 or self.settings:getCoalition() == nil then
            return
        end
        HOUND.DB.updateHumanDb(self.settings:getCoalition())
        local sectors = self:getSectors()
        table.sort(sectors,HoundUtils.Sort.sectorsByPriorityLowLast)
        for _,sector in pairs(sectors) do
            sector:populateRadioMenu()
        end
    end

    function HoundElint.updateSystemState(params)
        local state = params.state
        local self = params.self
        if state == true then
            self:systemOn()
        elseif state == false then
            self:systemOff()
        end
    end

    function HoundElint:systemOn(notify)
        if self.settings:getCoalition() == nil then
            HOUND.Logger.warn("failed to start. no coalition found.")
            return false
        end
        self:systemOff(false)

        self.elintTaskID = timer.scheduleFunction(self.runCycle, self, timer.getTime() + self.settings.intervals.scan)
        if notify == nil or notify then
            trigger.action.outTextForCoalition(self.settings:getCoalition(),
                                           "Hound ELINT system is now Operating", 10)
        end
        env.info("Hound is now on")
        HOUND.EventHandler.publishEvent({
            id = HOUND.EVENTS.HOUND_ENABLED,
            houndId = self.settings:getId(),
            coalition = self.settings:getCoalition()
        })
        return true
    end

    function HoundElint:systemOff(notify)
        if self.elintTaskID ~= nil then
            timer.removeFunction(self.elintTaskID)
        end
        self:purgeRadioMenu()
        if notify == nil or notify then
            trigger.action.outTextForCoalition(self.settings:getCoalition(),
                                           "Hound ELINT system is now Offline", 10)
        end
        env.info("Hound is now off")
        HOUND.EventHandler.publishEvent({
            id = HOUND.EVENTS.HOUND_DISABLED,
            houndId = self.settings:getId(),
            coalition = self.settings:getCoalition()
        })
        return true
    end

    function HoundElint:isRunning()
        return (self.elintTaskID ~= nil)
    end

    function HoundElint:getContacts()
        local contacts = {
            ewr = { contacts = {} },
            sam = { contacts = {} }
            }
        for _,emitter in pairs(self.contacts:listAllContacts()) do
            local contact = emitter:export()
            if contact ~= nil then
                if emitter.isEWR then
                    table.insert(contacts.ewr.contacts,contact)
                else
                    table.insert(contacts.sam.contacts,contact)
                end
            end
        end
        contacts.ewr.count = #contacts.ewr.contacts or 0
        contacts.sam.count = #contacts.sam.contacts or 0
        return contacts
    end

    function HoundElint:getSites()
        local contacts = {
            ewr = { sites = {} },
            sam = { sites = {} }
        }
        for _,site in pairs(self.contacts:listAllSites()) do
            local contact = site:export()
            if contact ~= nil then
                if site.isEWR then
                    table.insert(contacts.ewr.sites,contact)
                else
                    table.insert(contacts.sam.sites,contact)
                end
            end
        end
        contacts.ewr.count = #contacts.ewr.sites or 0
        contacts.sam.count = #contacts.sam.sites or 0
        return contacts
    end

    function HoundElint:dumpIntelBrief(filename)
        if lfs == nil or io == nil then
            HOUND.Logger.info("cannot write CSV. please desanitize lfs and io")
            return
        end
        if not filename then
            filename = string.format("hound_contacts_%d.csv",self:getId())
        end
        local currentGameTime = HoundUtils.Text.getTime()
        local csvFile = io.open(lfs.writedir() .. filename, "w+")
        csvFile:write("SiteId,SiteNatoDesignation,TrackId,RadarType,State,Bullseye,Latitude,Longitude,MGRS,Accuracy,lastSeen,DcsType,DcsUnit,DcsGroup,ReportGenerated\n")
        csvFile:flush()
        for _,site in pairs(self.contacts:listAllSitesByRange()) do
            local siteItems = site:generateIntelBrief()
            if #siteItems > 0 then
                for _,item in ipairs(siteItems) do
                    csvFile:write(item .. "," .. currentGameTime .."\n")
                    csvFile:flush()
                end
            end
        end
        csvFile:close()
    end

    function HoundElint:printDebugging()
        local debugMsg = "Hound instace " .. self:getId() .. " (".. HoundUtils.getCoalitionString(self:getCoalition()) .. ")\n"
        debugMsg = debugMsg .. "-----------------------------\n"
        debugMsg = debugMsg .. "Platforms: " .. self:countPlatforms() .. " | sectors: " .. self:countSectors()
        debugMsg = debugMsg .. " (Z:"..self:countSectors("zone").." ,C:"..self:countSectors("controller").." ,A: " .. self:countSectors("atis") .. " ,N:"..self:countSectors("notifier") ..") | "
        debugMsg = debugMsg .. "Sites: " .. self:countSites() .. " | Contacts: ".. self:countContacts() .. " (A:" .. self:countActiveContacts() .. " ,PB:" .. self:countPreBriefedContacts() .. ")"
        return debugMsg
    end
end

do

    local HoundUtils = HOUND.Utils

    function HoundElint:onHoundEvent(houndEvent)
        return nil
    end

    function HoundElint:onHoundInternalEvent(houndEvent)
        if houndEvent.houndId ~= self.settings:getId() then
            return
        end
        if houndEvent.id == HOUND.EVENTS.HOUND_DISABLED then return end

        local sectors = self:getSectors()
        table.sort(sectors,HoundUtils.Sort.sectorsByPriorityLowFirst)

        if houndEvent.id == HOUND.EVENTS.RADAR_DETECTED then
            for _,sector in pairs(sectors) do
                sector:updateSectorMembership(houndEvent.initiator)
            end
        end
        if self:isRunning() then

            for _,sector in pairs(sectors) do
                if houndEvent.id == HOUND.EVENTS.RADAR_DESTROYED then
                    sector:notifyEmitterDead(houndEvent.initiator)
                end
                if houndEvent.id == HOUND.EVENTS.SITE_CREATED then
                    if not houndEvent.initiator.isEWR then
                        sector:notifySiteNew(houndEvent.initiator)
                    end
                end
                if houndEvent.id == HOUND.EVENTS.SITE_CLASSIFIED then
                    if not houndEvent.initiator.isEWR then
                        sector:notifySiteIdentified(houndEvent.initiator)
                    end
                end
                if houndEvent.id == HOUND.EVENTS.SITE_REMOVED or houndEvent.id == HOUND.EVENTS.SITE_ASLEEP then
                    sector:notifySiteDead(houndEvent.initiator,(houndEvent.id == HOUND.EVENTS.SITE_REMOVED))
                end
                if houndEvent.id == HOUND.EVENTS.SITE_LAUNCH then
                    sector:notifySiteLaunching(houndEvent.initiator)
                end
            end

            if houndEvent.id == HOUND.EVENTS.SITE_CREATED or houndEvent.id == HOUND.EVENTS.SITE_CLASSIFIED then
                self:populateRadioMenu()
                if self.settings:getMarkSites() then
                    houndEvent.initiator:updateMarker(HOUND.MARKER.NONE)
                end
            end
            if houndEvent.id == HOUND.EVENTS.RADAR_DETECTED then
                if self.settings:getUseMarkers() then
                    houndEvent.initiator:updateMarker(self.settings:getMarkerType())
                end
            end
            if not self.settings:getBDA() then return end
            if houndEvent.id == HOUND.EVENTS.SITE_REMOVED then
                houndEvent.initiator:destroy()
                self.contacts:removeSite(houndEvent.initiator)
                self:populateRadioMenu()
            end
            if houndEvent.id == HOUND.EVENTS.RADAR_DESTROYED then
                self.contacts:removeContact(houndEvent.initiator)
                self:populateRadioMenu()
            end
        end
    end

    function HoundElint:onEvent(DcsEvent)
        if not HoundUtils.Dcs.isUnit(DcsEvent.initiator) then return end

        if DcsEvent.id == world.event.S_EVENT_UNIT_LOST
            and DcsEvent.initiator:getCoalition() ~= self.settings:getCoalition()
            and self:getBDA()
            then
                return self:markDeadContact(DcsEvent.initiator)
        end

        if not self:isRunning() then return end

        if (DcsEvent.id == world.event.S_EVENT_BIRTH)
            and DcsEvent.initiator:getCoalition() == self.settings:getCoalition()
            and HoundUtils.Dcs.isHuman(DcsEvent.initiator)
        then
            local _,catEx = DcsEvent.initiator:getCategory()
            if not HOUND.setContainsValue({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER},catEx) then return end
            return self:populateRadioMenu()
        end

        if (DcsEvent.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT
            or DcsEvent.id == world.event.S_EVENT_PILOT_DEAD
            or DcsEvent.id == world.event.S_EVENT_EJECTION)
            and DcsEvent.initiator:getCoalition() == self.settings:getCoalition()
            and HoundUtils.Dcs.isHuman(DcsEvent.initiator)
        then
            local _,catEx = DcsEvent.initiator:getCategory()
            if not HOUND.setContainsValue({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER},catEx) then return end
            return self:populateRadioMenu()
        end

        if DcsEvent.id == world.event.S_EVENT_SHOT
            and DcsEvent.initiator:getCoalition() ~= self.settings:getCoalition()
            and DcsEvent.initiator:hasAttribute("Air Defence")
            and DcsEvent.initiator:getCategory() == Object.Category.UNIT
        then
            local _,catEx = DcsEvent.initiator:getCategory()
            if not HOUND.setContainsValue({Unit.Category.GROUND_UNIT,Unit.Category.SHIP},catEx) then return end
            local grp = DcsEvent.initiator:getGroup()
            if HoundUtils.Dcs.isGroup(grp) then
                self.contacts:Sniff(grp:getName())
                if DcsEvent.weapon:getDesc().category ~= Weapon.Category.Missile then return end
                local tgtPos = nil
                local wpnTgt = DcsEvent.weapon:getTarget()
                if HoundUtils.Dcs.isUnit(wpnTgt) then
                  tgtPos = wpnTgt:getPoint()
                end
                if HoundUtils.Dcs.isPoint(tgtPos) then
                    HoundUtils.Geo.setPointHeight(tgtPos)
                end
                self.contacts:ensureSitePrimaryHasPos(grp:getName(),tgtPos)
                self:AlertOnLaunch(grp)
            end
        end

        if DcsEvent.id == world.event.S_EVENT_WEAPON_ADD
            and HOUND.AUTO_ADD_PLATFORM_BY_PAYLOAD
            and HoundUtils.Dcs.isUnit(DcsEvent.initiator)
            and DcsEvent.initiator:getCoalition() == self.settings:getCoalition()
        then
            local unit = DcsEvent.initiator
            local type = unit:getTypeName()
            HOUND.Logger.debug("S_EVENT_WEAPON_ADD for " .. unit:getName() .. " (" .. type .. ") with weapon " .. DcsEvent.weapon_name )

            if not HOUND.DB.isValidPlatform(unit,DcsEvent.weapon_name) then return end
            HOUND.Logger.debug(self:countPlatforms())
            HOUND.Logger.debug("S_EVENT_WEAPON_ADD for " .. unit:getName() .. " (" .. type .. ") with weapon " .. DcsEvent.weapon_name .. " add platform")
            self:addPlatform(unit:getName())
            HOUND.Logger.debug(self:countPlatforms())
        end
    end

    function HoundElint:defaultEventHandler(remove)
        if remove == false then
            HOUND.EventHandler.removeInternalEventHandler(self)
            world.removeEventHandler(self)
            return
        end
        HOUND.EventHandler.addInternalEventHandler(self)
        world.addEventHandler(self)
    end
end
do
    trigger.action.outText("Hound ELINT ("..HOUND.VERSION..") is loaded.", 15)
    env.info("[Hound] - finished loading (".. HOUND.VERSION..")")
end
-- Hound version 0.4.3 - Compiled on 2025-08-29 11:28