--[[--
MIST Mission Scripting Tools.
## Description:
MIssion Scripting Tools (MIST) is a collection of Lua functions
and databases that is intended to be a supplement to the standard
Lua functions included in the simulator scripting engine.

MIST functions and databases provide ready-made solutions to many common
scripting tasks and challenges, enabling easier scripting and saving
mission scripters time. The table mist.flagFuncs contains a set of
Lua functions (that are similar to Slmod functions) that do not
require detailed Lua knowledge to use.

However, the majority of MIST does require knowledge of the Lua language,
and, if you are going to utilize these components of MIST, it is necessary
that you read the Simulator Scripting Engine guide on the official ED wiki.

## Links:

ED Forum Thread: <http://forums.eagle.ru/showthread.php?t=98616>

##Github:

Development <https://github.com/mrSkortch/MissionScriptingTools>

Official Releases <https://github.com/mrSkortch/MissionScriptingTools/tree/master>

@script MIST
@author Speed
@author Grimes
@author lukrop
]]
mist = {}

-- don't change these
mist.majorVersion = 4
mist.minorVersion = 3
mist.build = 74

-- forward declaration of log shorthand
local log

do -- the main scope
	local coroutines = {}

	local tempSpawnedUnits = {} -- birth events added here
	local tempSpawnedGroups = {}
	local tempSpawnGroupsCounter = 0
	
	local mistAddedObjects = {} -- mist.dynAdd unit data added here
	local mistAddedGroups = {} -- mist.dynAdd groupdata added here
	local writeGroups = {}
	local lastUpdateTime = 0

	local updateAliveUnitsCounter = 0
	local updateTenthSecond = 0
	
	local mistGpId = 7000
	local mistUnitId = 7000
	local mistDynAddIndex = {[' air '] = 0, [' hel '] = 0, [' gnd '] = 0, [' bld '] = 0, [' static '] = 0, [' shp '] = 0}

	local scheduledTasks = {}
	local taskId = 0
	local idNum = 0

	mist.nextGroupId = 1
	mist.nextUnitId = 1

	local dbLog
	
	local function initDBs() -- mist.DBs scope
		mist.DBs = {}

		mist.DBs.missionData = {}
		if env.mission then

			mist.DBs.missionData.startTime = env.mission.start_time
			mist.DBs.missionData.theatre = env.mission.theatre
			mist.DBs.missionData.version = env.mission.version
			mist.DBs.missionData.files = {}
			if type(env.mission.resourceCounter) == 'table' then
				for fIndex, fData in pairs (env.mission.resourceCounter) do
					mist.DBs.missionData.files[#mist.DBs.missionData.files + 1] =	mist.utils.deepCopy(fIndex)
				end
			end
			-- if we add more coalition specific data then bullsye should be categorized by coaliton. For now its just the bullseye table
			mist.DBs.missionData.bullseye = {red = {}, blue = {}}
			mist.DBs.missionData.bullseye.red.x = env.mission.coalition.red.bullseye.x --should it be point.x?
			mist.DBs.missionData.bullseye.red.y = env.mission.coalition.red.bullseye.y
			mist.DBs.missionData.bullseye.blue.x = env.mission.coalition.blue.bullseye.x
			mist.DBs.missionData.bullseye.blue.y = env.mission.coalition.blue.bullseye.y
		end

		mist.DBs.zonesByName = {}
		mist.DBs.zonesByNum = {}


		if env.mission.triggers and env.mission.triggers.zones then
			for zone_ind, zone_data in pairs(env.mission.triggers.zones) do
				if type(zone_data) == 'table' then
					local zone = mist.utils.deepCopy(zone_data)
					zone.point = {}	-- point is used by SSE
					zone.point.x = zone_data.x
					zone.point.y = 0
					zone.point.z = zone_data.y

					mist.DBs.zonesByName[zone_data.name] = zone
					mist.DBs.zonesByNum[#mist.DBs.zonesByNum + 1] = mist.utils.deepCopy(zone)	--[[deepcopy so that the zone in zones_by_name and the zone in
																								zones_by_num se are different objects.. don't want them linked.]]
				end
			end
		end

		mist.DBs.navPoints = {}
		mist.DBs.units = {}
		--Build mist.db.units and mist.DBs.navPoints
		for coa_name, coa_data in pairs(env.mission.coalition) do

			if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then
				mist.DBs.units[coa_name] = {}

				-- build nav points DB
				mist.DBs.navPoints[coa_name] = {}
				if coa_data.nav_points then --navpoints
					--mist.debug.writeData (mist.utils.serialize,{'NavPoints',coa_data.nav_points}, 'NavPoints.txt')
					for nav_ind, nav_data in pairs(coa_data.nav_points) do

						if type(nav_data) == 'table' then
							mist.DBs.navPoints[coa_name][nav_ind] = mist.utils.deepCopy(nav_data)

							mist.DBs.navPoints[coa_name][nav_ind].name = nav_data.callsignStr	-- name is a little bit more self-explanatory.
							mist.DBs.navPoints[coa_name][nav_ind].point = {}	-- point is used by SSE, support it.
							mist.DBs.navPoints[coa_name][nav_ind].point.x = nav_data.x
							mist.DBs.navPoints[coa_name][nav_ind].point.y = 0
							mist.DBs.navPoints[coa_name][nav_ind].point.z = nav_data.y
						end
					end
				end
				if coa_data.country then --there is a country table
					for cntry_id, cntry_data in pairs(coa_data.country) do

						local countryName = string.lower(cntry_data.name)
						mist.DBs.units[coa_name][countryName] = {}
						mist.DBs.units[coa_name][countryName].countryId = cntry_data.id

						if type(cntry_data) == 'table' then	--just making sure

							for obj_type_name, obj_type_data in pairs(cntry_data) do

								if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then --should be an unncessary check

									local category = obj_type_name

									if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then	--there's a group!

										mist.DBs.units[coa_name][countryName][category] = {}

										for group_num, group_data in pairs(obj_type_data.group) do

											if group_data and group_data.units and type(group_data.units) == 'table' then	--making sure again- this is a valid group

												mist.DBs.units[coa_name][countryName][category][group_num] = {}
												local groupName = group_data.name
												if env.mission.version > 7 then
													groupName = env.getValueDictByKey(groupName)
												end
												mist.DBs.units[coa_name][countryName][category][group_num].groupName = groupName
												mist.DBs.units[coa_name][countryName][category][group_num].groupId = group_data.groupId
												mist.DBs.units[coa_name][countryName][category][group_num].category = category
												mist.DBs.units[coa_name][countryName][category][group_num].coalition = coa_name
												mist.DBs.units[coa_name][countryName][category][group_num].country = countryName
												mist.DBs.units[coa_name][countryName][category][group_num].countryId = cntry_data.id
												mist.DBs.units[coa_name][countryName][category][group_num].startTime = group_data.start_time
												mist.DBs.units[coa_name][countryName][category][group_num].task = group_data.task
												mist.DBs.units[coa_name][countryName][category][group_num].hidden = group_data.hidden

												mist.DBs.units[coa_name][countryName][category][group_num].units = {}

												mist.DBs.units[coa_name][countryName][category][group_num].radioSet = group_data.radioSet
												mist.DBs.units[coa_name][countryName][category][group_num].uncontrolled = group_data.uncontrolled
												mist.DBs.units[coa_name][countryName][category][group_num].frequency = group_data.frequency
												mist.DBs.units[coa_name][countryName][category][group_num].modulation = group_data.modulation

												for unit_num, unit_data in pairs(group_data.units) do
													local units_tbl = mist.DBs.units[coa_name][countryName][category][group_num].units	--pointer to the units table for this group

													units_tbl[unit_num] = {}
													if env.mission.version > 7 then
														units_tbl[unit_num].unitName = env.getValueDictByKey(unit_data.name)
													else
														units_tbl[unit_num].unitName = unit_data.name
													end
													units_tbl[unit_num].type = unit_data.type
													units_tbl[unit_num].skill = unit_data.skill	--will be nil for statics
													units_tbl[unit_num].unitId = unit_data.unitId
													units_tbl[unit_num].category = category
													units_tbl[unit_num].coalition = coa_name
													units_tbl[unit_num].country = countryName
													units_tbl[unit_num].countryId = cntry_data.id
													units_tbl[unit_num].heading = unit_data.heading
													units_tbl[unit_num].playerCanDrive = unit_data.playerCanDrive
													units_tbl[unit_num].alt = unit_data.alt
													units_tbl[unit_num].alt_type = unit_data.alt_type
													units_tbl[unit_num].speed = unit_data.speed
													units_tbl[unit_num].livery_id = unit_data.livery_id
													if unit_data.point then	--ME currently does not work like this, but it might one day
														units_tbl[unit_num].point = unit_data.point
													else
														units_tbl[unit_num].point = {}
														units_tbl[unit_num].point.x = unit_data.x
														units_tbl[unit_num].point.y = unit_data.y
													end
													units_tbl[unit_num].x = unit_data.x
													units_tbl[unit_num].y = unit_data.y

													units_tbl[unit_num].callsign = unit_data.callsign
													units_tbl[unit_num].onboard_num = unit_data.onboard_num
													units_tbl[unit_num].hardpoint_racks = unit_data.hardpoint_racks
													units_tbl[unit_num].psi = unit_data.psi


													units_tbl[unit_num].groupName = groupName
													units_tbl[unit_num].groupId = group_data.groupId

													if unit_data.AddPropAircraft then
														units_tbl[unit_num].AddPropAircraft = unit_data.AddPropAircraft
													end

													if category == 'static' then
														units_tbl[unit_num].categoryStatic = unit_data.category
														units_tbl[unit_num].shape_name = unit_data.shape_name
														if unit_data.mass then
															units_tbl[unit_num].mass = unit_data.mass
														end

														if unit_data.canCargo then
															units_tbl[unit_num].canCargo = unit_data.canCargo
														end
													end

												end --for unit_num, unit_data in pairs(group_data.units) do
											end --if group_data and group_data.units then
										end --for group_num, group_data in pairs(obj_type_data.group) do
									end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then
								end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then
							end --for obj_type_name, obj_type_data in pairs(cntry_data) do
						end --if type(cntry_data) == 'table' then
					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

		mist.DBs.unitsByName = {}
		mist.DBs.unitsById = {}
		mist.DBs.unitsByCat = {}

		mist.DBs.unitsByCat.helicopter = {}	-- adding default categories
		mist.DBs.unitsByCat.plane = {}
		mist.DBs.unitsByCat.ship = {}
		mist.DBs.unitsByCat.static = {}
		mist.DBs.unitsByCat.vehicle = {}

		mist.DBs.unitsByNum = {}

		mist.DBs.groupsByName = {}
		mist.DBs.groupsById = {}
		mist.DBs.humansByName = {}
		mist.DBs.humansById = {}

		mist.DBs.dynGroupsAdded = {} -- will be filled by mist.dbUpdate from dynamically spawned groups
		mist.DBs.activeHumans = {}

		mist.DBs.aliveUnits = {}	-- will be filled in by the "updateAliveUnits" coroutine in mist.main.

		mist.DBs.removedAliveUnits = {} -- will be filled in by the "updateAliveUnits" coroutine in mist.main.

		mist.DBs.const = {}

		-- not accessible by SSE, must use static list :-/
		mist.DBs.const.callsigns = {
			['NATO'] = {
				['rules'] = {
					['groupLimit'] = 9,
				},
				['AWACS'] = {
					['Overlord'] = 1,
					['Magic'] = 2,
					['Wizard'] = 3,
					['Focus'] =	 4,
					['Darkstar'] =	 5,
				},
                ['TANKER'] = {
					['Texaco'] = 1,
					['Arco'] = 2,
					['Shell'] = 3,
				},
				['JTAC'] = {
					['Axeman'] = 1,
					['Darknight'] = 2,
					['Warrior']	= 3,
					['Pointer']	= 4,
					['Eyeball'] = 5,
					['Moonbeam'] = 6,
					['Whiplash'] = 7,
					['Finger'] = 8,
					['Pinpoint'] = 9,
					['Ferret'] = 10,
					['Shaba'] = 11,
					['Playboy'] = 12,
					['Hammer'] = 13,
					['Jaguar'] = 14,
					['Deathstar'] =	15,
					['Anvil'] = 16,
					['Firefly']	= 17,
					['Mantis'] = 18,
					['Badger'] = 19,
				},
				['aircraft'] = {
					['Enfield'] = 1,
					['Springfield'] = 2,
					['Uzi']	= 3,
					['Colt'] = 4,
					['Dodge'] =	5,
					['Ford'] = 6,
					['Chevy'] = 7,
					['Pontiac'] = 8,
				},

				['unique'] = {
					['A10'] = {
						['Hawg'] = 9,
						['Boar'] = 10,
						['Pig'] = 11,
						['Tusk'] = 12,
						['rules'] = {
							['canUseAircraft'] = true,
							['appliesTo'] = {
								'A-10C',
								'A-10A',
							},
						},
					},
				},
			},
		}
		mist.DBs.const.shapeNames = {
			["Landmine"] = "landmine",
			["FARP CP Blindage"] = "kp_ug",
			["Subsidiary structure C"] = "saray-c",
			["Barracks 2"] = "kazarma2",
			["Small house 2C"] = "dom2c",
			["Military staff"] = "aviashtab",
			["Tech hangar A"] = "ceh_ang_a",
			["Oil derrick"] = "neftevyshka",
			["Tech combine"] = "kombinat",
			["Garage B"] = "garage_b",
			["Airshow_Crowd"] = "Crowd1",
			["Hangar A"] = "angar_a",
			["Repair workshop"] = "tech",
			["Subsidiary structure D"] = "saray-d",
			["FARP Ammo Dump Coating"] = "SetkaKP",
			["Small house 1C area"] = "dom2c-all",
			["Tank 2"] = "airbase_tbilisi_tank_01",
			["Boiler-house A"] = "kotelnaya_a",
			["Workshop A"] = "tec_a",
			["Small werehouse 1"] = "s1",
			["Garage small B"] = "garagh-small-b",
			["Small werehouse 4"] = "s4",
			["Shop"] = "magazin",
			["Subsidiary structure B"] = "saray-b",
			["FARP Fuel Depot"] = "GSM Rus",
			["Coach cargo"] = "wagon-gruz",
			["Electric power box"] = "tr_budka",
			["Tank 3"] = "airbase_tbilisi_tank_02",
			["Red_Flag"] = "H-flag_R",
			["Container red 3"] = "konteiner_red3",
			["Garage A"] = "garage_a",
			["Hangar B"] = "angar_b",
			["Black_Tyre"] = "H-tyre_B",
			["Cafe"] = "stolovaya",
			["Restaurant 1"] = "restoran1",
			["Subsidiary structure A"] = "saray-a",
			["Container white"] = "konteiner_white",
			["Warehouse"] = "sklad",
			["Tank"] = "bak",
			["Railway crossing B"] = "pereezd_small",
			["Subsidiary structure F"] = "saray-f",
			["Farm A"] = "ferma_a",
			["Small werehouse 3"] = "s3",
			["Water tower A"] = "wodokachka_a",
			["Railway station"] = "r_vok_sd",
			["Coach a tank blue"] = "wagon-cisterna_blue",
			["Supermarket A"] = "uniwersam_a",
			["Coach a platform"] = "wagon-platforma",
			["Garage small A"] = "garagh-small-a",
			["TV tower"] = "tele_bash",
			["Comms tower M"] = "tele_bash_m",
			["Small house 1A"] = "domik1a",
			["Farm B"] = "ferma_b",
			["GeneratorF"] = "GeneratorF",
			["Cargo1"] = "ab-212_cargo",
			["Container red 2"] = "konteiner_red2",
			["Subsidiary structure E"] = "saray-e",
			["Coach a passenger"] = "wagon-pass",
			["Black_Tyre_WF"] = "H-tyre_B_WF",
			["Electric locomotive"] = "elektrowoz",
			["Shelter"] = "ukrytie",
			["Coach a tank yellow"] = "wagon-cisterna_yellow",
			["Railway crossing A"] = "pereezd_big",
			[".Ammunition depot"] = "SkladC",
			["Small werehouse 2"] = "s2",
			["Windsock"] = "H-Windsock_RW",
			["Shelter B"] = "ukrytie_b",
			["Fuel tank"] = "toplivo-bak",
			["Locomotive"] = "teplowoz",
			[".Command Center"] = "ComCenter",
			["Pump station"] = "nasos",
			["Black_Tyre_RF"] = "H-tyre_B_RF",
			["Coach cargo open"] = "wagon-gruz-otkr",
			["Subsidiary structure 3"] = "hozdomik3",
			["FARP Tent"] = "PalatkaB",
			["White_Tyre"] = "H-tyre_W",
			["Subsidiary structure G"] = "saray-g",
			["Container red 1"] = "konteiner_red1",
			["Small house 1B area"] = "domik1b-all",
			["Subsidiary structure 1"] = "hozdomik1",
			["Container brown"] = "konteiner_brown",
			["Small house 1B"] = "domik1b",
			["Subsidiary structure 2"] = "hozdomik2",
			["Chemical tank A"] = "him_bak_a",
			["WC"] = "WC",
			["Small house 1A area"] = "domik1a-all",
			["White_Flag"] = "H-Flag_W",
			["Airshow_Cone"] = "Comp_cone",
		}
		
		
		-- create mist.DBs.oldAliveUnits
		-- do
		-- local intermediate_alive_units = {}	-- between 0 and 0.5 secs old
		-- local function make_old_alive_units() -- called every 0.5 secs, makes the old_alive_units DB which is just a copy of alive_units that is 0.5 to 1 sec old
		-- if intermediate_alive_units then
		-- mist.DBs.oldAliveUnits = mist.utils.deepCopy(intermediate_alive_units)
		-- end
		-- intermediate_alive_units = mist.utils.deepCopy(mist.DBs.aliveUnits)
		-- timer.scheduleFunction(make_old_alive_units, nil, timer.getTime() + 0.5)
		-- end

		-- make_old_alive_units()
		-- end

		--Build DBs
		for coa_name, coa_data in pairs(mist.DBs.units) do
			for cntry_name, cntry_data in pairs(coa_data) do
				for category_name, category_data in pairs(cntry_data) do
					if type(category_data) == 'table' then
						for group_ind, group_data in pairs(category_data) do
							if type(group_data) == 'table' and group_data.units and type(group_data.units) == 'table' and #group_data.units > 0 then	-- OCD paradigm programming
								mist.DBs.groupsByName[group_data.groupName] = mist.utils.deepCopy(group_data)
								mist.DBs.groupsById[group_data.groupId] = mist.utils.deepCopy(group_data)
								for unit_ind, unit_data in pairs(group_data.units) do
									mist.DBs.unitsByName[unit_data.unitName] = mist.utils.deepCopy(unit_data)
									mist.DBs.unitsById[unit_data.unitId] = mist.utils.deepCopy(unit_data)

									mist.DBs.unitsByCat[unit_data.category] = mist.DBs.unitsByCat[unit_data.category] or {} -- future-proofing against new categories...
									table.insert(mist.DBs.unitsByCat[unit_data.category], mist.utils.deepCopy(unit_data))
									dbLog:info('inserting $1', unit_data.unitName)
									table.insert(mist.DBs.unitsByNum, mist.utils.deepCopy(unit_data))

									if unit_data.skill and (unit_data.skill == "Client" or unit_data.skill == "Player") then
										mist.DBs.humansByName[unit_data.unitName] = mist.utils.deepCopy(unit_data)
										mist.DBs.humansById[unit_data.unitId] = mist.utils.deepCopy(unit_data)
										--if Unit.getByName(unit_data.unitName) then
										--	mist.DBs.activeHumans[unit_data.unitName] = mist.utils.deepCopy(unit_data)
										--	mist.DBs.activeHumans[unit_data.unitName].playerName = Unit.getByName(unit_data.unitName):getPlayerName()
										--end
									end
								end
							end
						end
					end
				end
			end
		end

		--DynDBs
		mist.DBs.MEunits = mist.utils.deepCopy(mist.DBs.units)
		mist.DBs.MEunitsByName = mist.utils.deepCopy(mist.DBs.unitsByName)
		mist.DBs.MEunitsById = mist.utils.deepCopy(mist.DBs.unitsById)
		mist.DBs.MEunitsByCat = mist.utils.deepCopy(mist.DBs.unitsByCat)
		mist.DBs.MEunitsByNum = mist.utils.deepCopy(mist.DBs.unitsByNum)
		mist.DBs.MEgroupsByName = mist.utils.deepCopy(mist.DBs.groupsByName)
		mist.DBs.MEgroupsById = mist.utils.deepCopy(mist.DBs.groupsById)

		mist.DBs.deadObjects = {}

		do
			local mt = {}

			function mt.__newindex(t, key, val)
				local original_key = key --only for duplicate runtime IDs.
				local key_ind = 1
				while mist.DBs.deadObjects[key] do
					dbLog:warn('duplicate runtime id of previously dead object key: $1', key)
					key = tostring(original_key) .. ' #' .. tostring(key_ind)
					key_ind = key_ind + 1
				end

				if mist.DBs.aliveUnits and mist.DBs.aliveUnits[val.object.id_] then
					--dbLog:info('object found in alive_units')
					val.objectData = mist.utils.deepCopy(mist.DBs.aliveUnits[val.object.id_])
					local pos = Object.getPosition(val.object)
					if pos then
						val.objectPos = pos.p
					end
					val.objectType = mist.DBs.aliveUnits[val.object.id_].category

				elseif mist.DBs.removedAliveUnits and mist.DBs.removedAliveUnits[val.object.id_] then	-- it didn't exist in alive_units, check old_alive_units
					--dbLog:info('object found in old_alive_units')
					val.objectData = mist.utils.deepCopy(mist.DBs.removedAliveUnits[val.object.id_])
					local pos = Object.getPosition(val.object)
					if pos then
						val.objectPos = pos.p
					end
					val.objectType = mist.DBs.removedAliveUnits[val.object.id_].category

				else	--attempt to determine if static object...
					--dbLog:info('object not found in alive units or old alive units')
					local pos = Object.getPosition(val.object)
					if pos then
						local static_found = false
						for ind, static in pairs(mist.DBs.unitsByCat.static) do
							if ((pos.p.x - static.point.x)^2 + (pos.p.z - static.point.y)^2)^0.5 < 0.1 then --really, it should be zero...
								dbLog:info('correlated dead static object to position')
								val.objectData = static
								val.objectPos = pos.p
								val.objectType = 'static'
								static_found = true
								break
							end
						end
						if not static_found then
							val.objectPos = pos.p
							val.objectType = 'building'
						end
					else
						val.objectType = 'unknown'
					end
				end
				rawset(t, key, val)
			end

			setmetatable(mist.DBs.deadObjects, mt)
		end

		do -- mist unitID funcs
			for id, idData in pairs(mist.DBs.unitsById) do
				if idData.unitId > mist.nextUnitId then
					mist.nextUnitId = mist.utils.deepCopy(idData.unitId)
				end
				if idData.groupId > mist.nextGroupId then
					mist.nextGroupId = mist.utils.deepCopy(idData.groupId)
				end
			end
		end


	end

	local function updateAliveUnits()	-- coroutine function
		local lalive_units = mist.DBs.aliveUnits -- local references for faster execution
		local lunits = mist.DBs.unitsByNum
		local ldeepcopy = mist.utils.deepCopy
		local lUnit = Unit
		local lremovedAliveUnits = mist.DBs.removedAliveUnits
		local updatedUnits = {}

		if #lunits > 0 then
			local units_per_run = math.ceil(#lunits/20)
			if units_per_run < 5 then
				units_per_run = 5
			end

			for i = 1, #lunits do
				if lunits[i].category ~= 'static' then -- can't get statics with Unit.getByName :(
					local unit = lUnit.getByName(lunits[i].unitName)
					if unit then
						--dbLog:info("unit named $1 alive!", lunits[i].unitName) -- spammy
						local pos = unit:getPosition()
						local newtbl = ldeepcopy(lunits[i])
						if pos then
							newtbl.pos = pos.p
						end
						newtbl.unit = unit
						--newtbl.rt_id = unit.id_
						lalive_units[unit.id_] = newtbl
						updatedUnits[unit.id_] = true
					end
				end
				if i%units_per_run == 0 then
					coroutine.yield()
				end
			end
			-- All units updated, remove any "alive" units that were not updated- they are dead!
			for unit_id, unit in pairs(lalive_units) do
				if not updatedUnits[unit_id] then
					lremovedAliveUnits[unit_id] = unit
					lalive_units[unit_id] = nil
				end
			end
		end
	end

	local function dbUpdate(event, objType)
		dbLog:info('dbUpdate')
		local newTable = {}
		newTable.startTime =	0
		if type(event) == 'string' then -- if name of an object.
			local newObject
			if Group.getByName(event) then
				newObject = Group.getByName(event)
			elseif StaticObject.getByName(event) then
				newObject = StaticObject.getByName(event)
				--	log:info('its static')
			else
				log:warn('$1 is not a Unit or Static Object. This should not be possible', event)
				return false
			end

			newTable.name = newObject:getName()
			newTable.groupId = tonumber(newObject:getID())
			newTable.groupName = newObject:getName()
			local unitOneRef
			if objType == 'static' then
				unitOneRef = newObject
				newTable.countryId = tonumber(newObject:getCountry())
				newTable.coalitionId = tonumber(newObject:getCoalition())
				newTable.category = 'static'
			else
				unitOneRef = newObject:getUnits()
				newTable.countryId = tonumber(unitOneRef[1]:getCountry())
				newTable.coalitionId = tonumber(unitOneRef[1]:getCoalition())
				newTable.category = tonumber(newObject:getCategory())
			end
			for countryData, countryId in pairs(country.id) do
				if newTable.country and string.upper(countryData) == string.upper(newTable.country) or countryId == newTable.countryId then
					newTable.countryId = countryId
					newTable.country = string.lower(countryData)
					for coaData, coaId in pairs(coalition.side) do
						if coaId == coalition.getCountryCoalition(countryId) then
							newTable.coalition = string.lower(coaData)
						end
					end
				end
			end
			for catData, catId in pairs(Unit.Category) do
				if objType == 'group' and Group.getByName(newTable.groupName):isExist() then
					if catId == Group.getByName(newTable.groupName):getCategory() then
						newTable.category = string.lower(catData)
					end
				elseif objType == 'static' and StaticObject.getByName(newTable.groupName):isExist() then
					if catId == StaticObject.getByName(newTable.groupName):getCategory() then
						newTable.category = string.lower(catData)
					end

				end
			end
			local gfound = false
			for index, data in pairs(mistAddedGroups) do
				if mist.stringMatch(data.name, newTable.groupName) == true then
					gfound = true
					newTable.task = data.task
					newTable.modulation = data.modulation
					newTable.uncontrolled = data.uncontrolled
					newTable.radioSet = data.radioSet
					newTable.hidden = data.hidden
					newTable.startTime = data.start_time
					mistAddedGroups[index] = nil
				end
			end

			if gfound == false then
				newTable.uncontrolled = false
				newTable.hidden = false
			end

			newTable.units = {}
			if objType == 'group' then
				for unitId, unitData in pairs(unitOneRef) do
					newTable.units[unitId] = {}
					newTable.units[unitId].unitName = unitData:getName()

					newTable.units[unitId].x = mist.utils.round(unitData:getPosition().p.x)
					newTable.units[unitId].y = mist.utils.round(unitData:getPosition().p.z)
					newTable.units[unitId].point = {}
					newTable.units[unitId].point.x = newTable.units[unitId].x
					newTable.units[unitId].point.y = newTable.units[unitId].y
					newTable.units[unitId].alt = mist.utils.round(unitData:getPosition().p.y)
					newTable.units[unitId].speed = mist.vec.mag(unitData:getVelocity())

					newTable.units[unitId].heading = mist.getHeading(unitData, true)

					newTable.units[unitId].type = unitData:getTypeName()
					newTable.units[unitId].unitId = tonumber(unitData:getID())


					newTable.units[unitId].groupName = newTable.groupName
					newTable.units[unitId].groupId = newTable.groupId
					newTable.units[unitId].countryId = newTable.countryId
					newTable.units[unitId].coalitionId = newTable.coalitionId
					newTable.units[unitId].coalition = newTable.coalition
					newTable.units[unitId].country = newTable.country
					local found = false
					for index, data in pairs(mistAddedObjects) do
						if mist.stringMatch(data.name, newTable.units[unitId].unitName) == true then
							found = true
							newTable.units[unitId].livery_id = data.livery_id
							newTable.units[unitId].skill = data.skill
							newTable.units[unitId].alt_type = data.alt_type
							newTable.units[unitId].callsign = data.callsign
							newTable.units[unitId].psi = data.psi
							mistAddedObjects[index] = nil
						end
						if found == false then
							newTable.units[unitId].skill = "High"
							newTable.units[unitId].alt_type = "BARO"
						end
					end

				end
			else -- its a static
				newTable.category = 'static'
				newTable.units[1] = {}
				newTable.units[1].unitName = newObject:getName()
				newTable.units[1].category = 'static'
				newTable.units[1].x = mist.utils.round(newObject:getPosition().p.x)
				newTable.units[1].y = mist.utils.round(newObject:getPosition().p.z)
				newTable.units[1].point = {}
				newTable.units[1].point.x = newTable.units[1].x
				newTable.units[1].point.y = newTable.units[1].y
				newTable.units[1].alt = mist.utils.round(newObject:getPosition().p.y)
				newTable.units[1].heading = mist.getHeading(newObject, true)
				newTable.units[1].type = newObject:getTypeName()
				newTable.units[1].unitId = tonumber(newObject:getID())
				newTable.units[1].groupName = newTable.name
				newTable.units[1].groupId = newTable.groupId
				newTable.units[1].countryId = newTable.countryId
				newTable.units[1].country = newTable.country
				newTable.units[1].coalitionId = newTable.coalitionId
				newTable.units[1].coalition = newTable.coalition
				if newObject:getCategory() == 6 and newObject:getCargoDisplayName() then
					local mass = newObject:getCargoDisplayName()
					mass = string.gsub(mass, ' ', '')
					mass = string.gsub(mass, 'kg', '')
					newTable.units[1].mass = tonumber(mass)
					newTable.units[1].categoryStatic = 'Cargos'
					newTable.units[1].canCargo = true
					newTable.units[1].shape_name = 'ab-212_cargo'
				end

				----- search mist added objects for extra data if applicable
				for index, data in pairs(mistAddedObjects) do
					if mist.stringMatch(data.name, newTable.units[1].unitName) == true then
						newTable.units[1].shape_name = data.shape_name -- for statics
						newTable.units[1].livery_id = data.livery_id
						newTable.units[1].airdromeId = data.airdromeId
						newTable.units[1].mass = data.mass
						newTable.units[1].canCargo = data.canCargo
						newTable.units[1].categoryStatic = data.categoryStatic
						newTable.units[1].type = 'cargo1'
						mistAddedObjects[index] = nil
					end
				end
			end
		end
		--mist.debug.writeData(mist.utils.serialize,{'msg', newTable}, timer.getAbsTime() ..'Group.lua')
		newTable.timeAdded = timer.getAbsTime() -- only on the dynGroupsAdded table. For other reference, see start time
		--mist.debug.dumpDBs()
		--end
		dbLog:info('endDbUpdate')
		return newTable
	end

	--[[DB update code... FRACK. I need to refactor some of it. 
	
	The problem is that the DBs need to account better for shared object names. Needs to write over some data and outright remove other.
	
	If groupName is used then entire group needs to be rewritten
		what to do with old groups units DB entries?. Names cant be assumed to be the same.
	
	
	-- new spawn event check.
	-- event handler filters everything into groups: tempSpawnedGroups
	-- this function then checks DBs to see if data has changed
	]]
	local function checkSpawnedEventsNew()
		if tempSpawnGroupsCounter > 0 then
			--[[local updatesPerRun = math.ceil(#tempSpawnedGroupsCounter/20)
			if updatesPerRun < 5 then
				updatesPerRun = 5
			end]]
			
			dbLog:info('iterate')
			for name, gType in pairs(tempSpawnedGroups) do
				dbLog:info(name)
				local updated = false
				
				if mist.DBs.groupsByName[name] then
					-- first check group level properties, groupId, countryId, coalition
					dbLog:info('Found in DBs, check if updated')
					local dbTable = mist.DBs.groupsByName[name]
					dbLog:info(dbTable)
					if gType ~= 'static' then
						dbLog:info('Not static')
						local _g = Group.getByName(name)
						local _u = _g:getUnit(1)
						if dbTable.groupId ~= tonumber(_g:getID()) or _u:getCountry() ~= dbTable.countryId or _u:getCoalition() ~= dbTable.coaltionId then
							dbLog:info('Group Data mismatch')
							updated = true
						else
							dbLog:info('No Mismatch')
						end

					end
				end			
				dbLog:info('Updated: $1', updated)
				if updated == false and gType ~= 'static' then -- time to check units
					dbLog:info('No Group Mismatch, Check Units')
					for index, uObject in pairs(Group.getByName(name):getUnits()) do
						dbLog:info(index)
						if mist.DBs.unitsByName[uObject:getName()] then
							dbLog:info('UnitByName table exists')
							local uTable = mist.DBs.unitsByName[uObject:getName()]
							if tonumber(uObject:getID()) ~= uTable.unitId or uObject:getTypeName() ~= uTable.type  then
								dbLog:info('Unit Data mismatch')
								updated = true
								break
							end
						end
					end
				end
				
				if updated == true or not mist.DBs.groupsByName[name] then
					dbLog:info('Get Table')
					writeGroups[#writeGroups+1] = {data = dbUpdate(name, gType), isUpdated = updated}
				
				end
				-- Work done, so remove
				tempSpawnedGroups[name] = nil
				tempSpawnGroupsCounter = tempSpawnGroupsCounter - 1
			end			
		end	
	end
	
	local function updateDBTables()
		local i = #writeGroups

		local savesPerRun = math.ceil(i/10)
		if savesPerRun < 5 then
			savesPerRun = 5
		end
		if i > 0 then
			dbLog:info('updateDBTables')
			local ldeepCopy = mist.utils.deepCopy
			for x = 1, i do
				dbLog:info(writeGroups[x])
				local newTable = writeGroups[x].data
				local updated = writeGroups[x].isUpdated
				local mistCategory
				if type(newTable.category) == 'string' then
					mistCategory = string.lower(newTable.category)
				end

				if string.upper(newTable.category) == 'GROUND_UNIT' then
					mistCategory = 'vehicle'
					newTable.category = mistCategory
				elseif string.upper(newTable.category) == 'AIRPLANE' then
					mistCategory = 'plane'
					newTable.category = mistCategory
				elseif string.upper(newTable.category) == 'HELICOPTER' then
					mistCategory = 'helicopter'
					newTable.category = mistCategory
				elseif string.upper(newTable.category) == 'SHIP' then
					mistCategory = 'ship'
					newTable.category = mistCategory
				end
				dbLog:info('Update unitsBy')
				for newId, newUnitData in pairs(newTable.units) do
					dbLog:info(newId)
					newUnitData.category = mistCategory
					if newUnitData.unitId then
						dbLog:info('byId')
						mist.DBs.unitsById[tonumber(newUnitData.unitId)] = ldeepCopy(newUnitData)
					end
					dbLog:info(updated)
					if mist.DBs.unitsByName[newUnitData.unitName] and updated == true then--if unit existed before and something was updated, write over the entry for a given unit name just in case.
						dbLog:info('Updating Unit Tables')
						for i = 1, #mist.DBs.unitsByCat[mistCategory] do
							if mist.DBs.unitsByCat[mistCategory][i].unitName == newUnitData.unitName then
								dbLog:info('Entry Found, Rewriting for unitsByCat')
								mist.DBs.unitsByCat[mistCategory][i] = ldeepCopy(newUnitData)
								break
							end
						end 
						for i = 1, #mist.DBs.unitsByNum do
							if mist.DBs.unitsByNum[i].unitName == newUnitData.unitName then
								dbLog:info('Entry Found, Rewriting for unitsByNum')
								mist.DBs.unitsByNum[i] = ldeepCopy(newUnitData)
								break
							end
						end
						
					else
						dbLog:info('Unitname not in use, add as normal')
						mist.DBs.unitsByCat[mistCategory][#mist.DBs.unitsByCat[mistCategory] + 1] = ldeepCopy(newUnitData)
						mist.DBs.unitsByNum[#mist.DBs.unitsByNum + 1] = ldeepCopy(newUnitData)
					end
					mist.DBs.unitsByName[newUnitData.unitName] = ldeepCopy(newUnitData)

					
				end
				-- this is a really annoying DB to populate. Gotta create new tables in case its missing
				dbLog:info('write mist.DBs.units')
				if not mist.DBs.units[newTable.coalition] then
					mist.DBs.units[newTable.coalition] = {}
				end

				if not mist.DBs.units[newTable.coalition][newTable.country] then
					mist.DBs.units[newTable.coalition][(newTable.country)] = {}
					mist.DBs.units[newTable.coalition][(newTable.country)].countryId = newTable.countryId
				end
				if not mist.DBs.units[newTable.coalition][newTable.country][mistCategory] then
					mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] = {}
				end
				
				if updated == true then
					dbLog:info('Updating DBsUnits')
					for i = 1, #mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] do
						if mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i].groupName == newTable.groupName then
							dbLog:info('Entry Found, Rewriting')
							mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][i] = ldeepCopy(newTable)
							break
						end
					end
				else
					mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][#mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] + 1] = ldeepCopy(newTable)
				end
				

				if newTable.groupId then
					mist.DBs.groupsById[newTable.groupId] = ldeepCopy(newTable)
				end

				mist.DBs.groupsByName[newTable.name] = ldeepCopy(newTable)
				mist.DBs.dynGroupsAdded[#mist.DBs.dynGroupsAdded + 1] = ldeepCopy(newTable)

				writeGroups[x] = nil
				if x%savesPerRun == 0 then
					coroutine.yield()
				end
			end
			if timer.getTime() > lastUpdateTime then
				lastUpdateTime = timer.getTime()
			end
			dbLog:info('endUpdateTables')
		end
	end

	local function groupSpawned(event)
		-- dont need to add units spawned in at the start of the mission if mist is loaded in init line
		if event.id == world.event.S_EVENT_BIRTH and timer.getTime0() < timer.getAbsTime() then
			dbLog:info('unitSpawnEvent')
			
				--table.insert(tempSpawnedUnits,(event.initiator))
				-------
				-- New functionality below. 
				-------
			if Object.getCategory(event.initiator) == 1 and not Unit.getPlayerName(event.initiator) then -- simple player check, will need to later check to see if unit was spawned with a player in a flight
				dbLog:info('Object is a Unit')
				dbLog:info(Unit.getGroup(event.initiator):getName())
				if not tempSpawnedGroups[Unit.getGroup(event.initiator):getName()] then
					dbLog:info('added')
					tempSpawnedGroups[Unit.getGroup(event.initiator):getName()] = 'group'
					tempSpawnGroupsCounter = tempSpawnGroupsCounter + 1
				end
			elseif Object.getCategory(event.initiator) == 3 or Object.getCategory(event.initiator) == 6 then
				dbLog:info('Object is Static')
				tempSpawnedGroups[StaticObject.getName(event.initiator)] = 'static'
				tempSpawnGroupsCounter = tempSpawnGroupsCounter + 1
			end
				
			
		end
	end

	local function doScheduledFunctions()
		local i = 1
		while i <= #scheduledTasks do
			if not scheduledTasks[i].rep then -- not a repeated process
				if scheduledTasks[i].t <= timer.getTime() then
					local task = scheduledTasks[i] -- local reference
					table.remove(scheduledTasks, i)
					local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars)))
					if not err then
						log:error('Error in scheduled function: $1', errmsg)
					end
					--task.f(unpack(task.vars, 1, table.maxn(task.vars)))	-- do the task, do not increment i
				else
					i = i + 1
				end
			else
				if scheduledTasks[i].st and scheduledTasks[i].st <= timer.getTime() then	 --if a stoptime was specified, and the stop time exceeded
					table.remove(scheduledTasks, i) -- stop time exceeded, do not execute, do not increment i
				elseif scheduledTasks[i].t <= timer.getTime() then
					local task = scheduledTasks[i] -- local reference
					task.t = timer.getTime() + task.rep	--schedule next run
					local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars)))
					if not err then
						log:error('Error in scheduled function: $1' .. errmsg)
					end
					--scheduledTasks[i].f(unpack(scheduledTasks[i].vars, 1, table.maxn(scheduledTasks[i].vars)))	-- do the task
					i = i + 1
				else
					i = i + 1
				end
			end
		end
	end

	-- Event handler to start creating the dead_objects table
	local function addDeadObject(event)
		if event.id == world.event.S_EVENT_DEAD or event.id == world.event.S_EVENT_CRASH then
			if event.initiator and event.initiator.id_ and event.initiator.id_ > 0 then

				local id = event.initiator.id_	-- initial ID, could change if there is a duplicate id_ already dead.
				local val = {object = event.initiator} -- the new entry in mist.DBs.deadObjects.

				local original_id = id	--only for duplicate runtime IDs.
				local id_ind = 1
				while mist.DBs.deadObjects[id] do
					--log:info('duplicate runtime id of previously dead object id: $1', id)
					id = tostring(original_id) .. ' #' .. tostring(id_ind)
					id_ind = id_ind + 1
				end

				if mist.DBs.aliveUnits and mist.DBs.aliveUnits[val.object.id_] then
					--log:info('object found in alive_units')
					val.objectData = mist.utils.deepCopy(mist.DBs.aliveUnits[val.object.id_])
					local pos = Object.getPosition(val.object)
					if pos then
						val.objectPos = pos.p
					end
					val.objectType = mist.DBs.aliveUnits[val.object.id_].category
					--[[if mist.DBs.activeHumans[Unit.getName(val.object)] then
					--trigger.action.outText('remove via death: ' .. Unit.getName(val.object),20)
						mist.DBs.activeHumans[Unit.getName(val.object)] = nil
					end]]
				elseif mist.DBs.removedAliveUnits and mist.DBs.removedAliveUnits[val.object.id_] then	-- it didn't exist in alive_units, check old_alive_units
					--log:info('object found in old_alive_units')
					val.objectData = mist.utils.deepCopy(mist.DBs.removedAliveUnits[val.object.id_])
					local pos = Object.getPosition(val.object)
					if pos then
						val.objectPos = pos.p
					end
					val.objectType = mist.DBs.removedAliveUnits[val.object.id_].category

				else	--attempt to determine if static object...
					--log:info('object not found in alive units or old alive units')
					local pos = Object.getPosition(val.object)
					if pos then
						local static_found = false
						for ind, static in pairs(mist.DBs.unitsByCat.static) do
							if ((pos.p.x - static.point.x)^2 + (pos.p.z - static.point.y)^2)^0.5 < 0.1 then --really, it should be zero...
								--log:info('correlated dead static object to position')
								val.objectData = static
								val.objectPos = pos.p
								val.objectType = 'static'
								static_found = true
								break
							end
						end
						if not static_found then
							val.objectPos = pos.p
							val.objectType = 'building'
						end
					else
						val.objectType = 'unknown'
					end
				end
				mist.DBs.deadObjects[id] = val
			end
		end
	end

	--[[
		local function addClientsToActive(event)
			if event.id == world.event.S_EVENT_PLAYER_ENTER_UNIT or event.id == world.event.S_EVENT_BIRTH then
				log:info(event)
				if Unit.getPlayerName(event.initiator) then
					log:info(Unit.getPlayerName(event.initiator))
					local newU = mist.utils.deepCopy(mist.DBs.unitsByName[Unit.getName(event.initiator)])
					newU.playerName = Unit.getPlayerName(event.initiator)
					mist.DBs.activeHumans[Unit.getName(event.initiator)] = newU
					--trigger.action.outText('added: ' .. Unit.getName(event.initiator), 20)
				end
			elseif event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT and event.initiator then
				if mist.DBs.activeHumans[Unit.getName(event.initiator)] then
					mist.DBs.activeHumans[Unit.getName(event.initiator)] = nil
					-- trigger.action.outText('removed via control: ' .. Unit.getName(event.initiator), 20)
				end
			end
		end

	mist.addEventHandler(addClientsToActive)
	]]

	--- init function.
	-- creates logger, adds default event handler
	-- and calls main the first time.
	-- @function mist.init
	function mist.init()
		-- create logger
		mist.log = mist.Logger:new("MIST")
		dbLog = mist.Logger:new('MISTDB', 'warning')
		
		log = mist.log -- log shorthand
		-- set warning log level, showing only
		-- warnings and errors
		log:setLevel("warning")

		log:info("initializing databases")
		initDBs()

		-- add event handler for group spawns
		mist.addEventHandler(groupSpawned)
		mist.addEventHandler(addDeadObject)

		-- call main the first time therafter it reschedules itself.
		mist.main()
		--log:msg('MIST version $1.$2.$3 loaded', mist.majorVersion, mist.minorVersion, mist.build)
		return
	end

	--- The main function.
	-- Run 100 times per second.
	-- You shouldn't call this function.
	function mist.main()
		timer.scheduleFunction(mist.main, {}, timer.getTime() + 0.01)	--reschedule first in case of Lua error

		updateTenthSecond = updateTenthSecond + 1
		if updateTenthSecond == 10 then
			updateTenthSecond = 0

			checkSpawnedEventsNew()
			
			if not coroutines.updateDBTables then
				coroutines.updateDBTables = coroutine.create(updateDBTables)
			end

			coroutine.resume(coroutines.updateDBTables)

			if coroutine.status(coroutines.updateDBTables) == 'dead' then
				coroutines.updateDBTables = nil
			end
		end

		--updating alive units
		updateAliveUnitsCounter = updateAliveUnitsCounter + 1
		if updateAliveUnitsCounter == 5 then
			updateAliveUnitsCounter = 0

			if not coroutines.updateAliveUnits then
				coroutines.updateAliveUnits = coroutine.create(updateAliveUnits)
			end

			coroutine.resume(coroutines.updateAliveUnits)

			if coroutine.status(coroutines.updateAliveUnits) == 'dead' then
				coroutines.updateAliveUnits = nil
			end
		end

		doScheduledFunctions()
	end -- end of mist.main

	--- Returns next unit id.
	-- @treturn number next unit id.
	function mist.getNextUnitId()
		mist.nextUnitId = mist.nextUnitId + 1
		if mist.nextUnitId > 6900 then
			mist.nextUnitId = 14000
		end
		return mist.nextUnitId
	end

	--- Returns next group id.
	-- @treturn number next group id.
	function mist.getNextGroupId()
		mist.nextGroupId = mist.nextGroupId + 1
		if mist.nextGroupId > 6900 then
			mist.nextGroupId = 14000
		end
		return mist.nextGroupId
	end

	--- Returns timestamp of last database update.
	-- @treturn timestamp of last database update
	function mist.getLastDBUpdateTime()
		return lastUpdateTime
	end

	--- Spawns a static object to the game world.
	-- @todo write good docs
	-- @tparam table staticObj table containing data needed for the object creation
	function mist.dynAddStatic(newObj)

		if newObj.units and newObj.units[1] then -- if its mist format
			for entry, val in pairs(newObj.units[1]) do
				if newObj[entry] and newObj[entry] ~= val or not newObj[entry] then
					newObj[entry] = val
				end
			end
		end
		--log:info(newObj)
		
		local cntry = newObj.country
		if newObj.countryId then
			cntry = newObj.countryId
		end
	
		local newCountry = ''

		for countryId, countryName in pairs(country.name) do
			if type(cntry) == 'string' then
				cntry = cntry:gsub("%s+", "_")
				if tostring(countryName) == string.upper(cntry) then
					newCountry = countryName
				end
			elseif type(cntry) == 'number' then
				if countryId == cntry then
					newCountry = countryName
				end
			end
		end
		
		if newCountry == '' then
			log:error("Country not found: $1", cntry)
			return false
		end
	
		if newObj.clone or not newObj.groupId then
			mistGpId = mistGpId + 1
			newObj.groupId = mistGpId
		end

		if newObj.clone or not newObj.unitId then
			mistUnitId = mistUnitId + 1
			newObj.unitId = mistUnitId
		end

		if newObj.clone or not newObj.name then
			mistDynAddIndex[' static '] = mistDynAddIndex[' static '] + 1
			newObj.name = (newCountry .. ' static ' .. mistDynAddIndex[' static '])
		end

		if not newObj.dead then
			newObj.dead = false
		end

		if not newObj.heading then
			newObj.heading = math.random(360)
		end
		
		if newObj.categoryStatic then
			newObj.category = newObj.categoryStatic
		end
		if newObj.mass then
			newObj.category = 'Cargos'
		end
		
		if newObj.shapeName then
			newObj.shape_name = newObj.shapeName
		end
		
		if not newObj.shape_name then
			log:info('shape_name not present')
			if mist.DBs.const.shapeNames[newObj.type] then
				newObj.shape_name = mist.DBs.const.shapeNames[newObj.type]
			end
		end
		
		mistAddedObjects[#mistAddedObjects + 1] = mist.utils.deepCopy(newObj)
		if newObj.x and newObj.y and newObj.type and type(newObj.x) == 'number' and type(newObj.y) == 'number' and type(newObj.type) == 'string' then
			--log:info('addStaticObject')
			coalition.addStaticObject(country.id[newCountry], newObj)

			return newObj
		end
		log:error("Failed to add static object due to missing or incorrect value. X: $1, Y: $2, Type: $3", newObj.x, newObj.y, newObj.type)
		return false
	end

	--- Spawns a dynamic group into the game world.
	-- Same as coalition.add function in SSE. checks the passed data to see if its valid.
	-- Will generate groupId, groupName, unitId, and unitName if needed
	-- @tparam table newGroup table containting values needed for spawning a group.
	function mist.dynAdd(newGroup)

		--mist.debug.writeData(mist.utils.serialize,{'msg', newGroup}, 'newGroupOrig.lua')
		local cntry = newGroup.country
		if newGroup.countryId then
			cntry = newGroup.countryId
		end

		local groupType = newGroup.category
		local newCountry = ''
		-- validate data
		for countryId, countryName in pairs(country.name) do
			if type(cntry) == 'string' then
				cntry = cntry:gsub("%s+", "_")
				if tostring(countryName) == string.upper(cntry) then
					newCountry = countryName
				end
			elseif type(cntry) == 'number' then
				if countryId == cntry then
					newCountry = countryName
				end
			end
		end

		if newCountry == '' then
			log:error("Country not found: $1", cntry)
			return false
		end

		local newCat = ''
		for catName, catId in pairs(Unit.Category) do
			if type(groupType) == 'string' then
				if tostring(catName) == string.upper(groupType) then
					newCat = catName
				end
			elseif type(groupType) == 'number' then
				if catId == groupType then
					newCat = catName
				end
			end

			if catName == 'GROUND_UNIT' and (string.upper(groupType) == 'VEHICLE' or string.upper(groupType) == 'GROUND') then
				newCat = 'GROUND_UNIT'
			elseif catName == 'AIRPLANE' and string.upper(groupType) == 'PLANE' then
				newCat = 'AIRPLANE'
			end
		end
		local typeName
		if newCat == 'GROUND_UNIT' then
			typeName = ' gnd '
		elseif newCat == 'AIRPLANE' then
			typeName = ' air '
		elseif newCat == 'HELICOPTER' then
			typeName = ' hel '
		elseif newCat == 'SHIP' then
			typeName = ' shp '
		elseif newCat == 'BUILDING' then
			typeName = ' bld '
		end
		if newGroup.clone or not newGroup.groupId then
			mistDynAddIndex[typeName] = mistDynAddIndex[typeName] + 1
			mistGpId = mistGpId + 1
			newGroup.groupId = mistGpId
		end
		if newGroup.groupName or newGroup.name then
			if newGroup.groupName then
				newGroup.name = newGroup.groupName
			elseif newGroup.name then
				newGroup.name = newGroup.name
			end
		end

		if newGroup.clone and mist.DBs.groupsByName[newGroup.name] or not newGroup.name then
			newGroup.name = tostring(newCountry .. tostring(typeName) .. mistDynAddIndex[typeName])
		end

		if not newGroup.hidden then
			newGroup.hidden = false
		end

		if not newGroup.visible then
			newGroup.visible = false
		end

		if (newGroup.start_time and type(newGroup.start_time) ~= 'number') or not newGroup.start_time then
			if newGroup.startTime then
				newGroup.start_time = mist.utils.round(newGroup.startTime)
			else
				newGroup.start_time = 0
			end
		end


		for unitIndex, unitData in pairs(newGroup.units) do
			local originalName = newGroup.units[unitIndex].unitName or newGroup.units[unitIndex].name
			if newGroup.clone or not unitData.unitId then
				mistUnitId = mistUnitId + 1
				newGroup.units[unitIndex].unitId = mistUnitId
			end
			if newGroup.units[unitIndex].unitName or newGroup.units[unitIndex].name then
				if newGroup.units[unitIndex].unitName then
					newGroup.units[unitIndex].name = newGroup.units[unitIndex].unitName
				elseif newGroup.units[unitIndex].name then
					newGroup.units[unitIndex].name = newGroup.units[unitIndex].name
				end
			end
			if newGroup.clone or not unitData.name then
				newGroup.units[unitIndex].name = tostring(newGroup.name .. ' unit' .. unitIndex)
			end

			if not unitData.skill then
				newGroup.units[unitIndex].skill = 'Random'
			end

			if not unitData.alt then
				if newCat == 'AIRPLANE' then
					newGroup.units[unitIndex].alt = 2000
					newGroup.units[unitIndex].alt_type = 'RADIO'
					newGroup.units[unitIndex].speed = 150
				elseif newCat == 'HELICOPTER' then
					newGroup.units[unitIndex].alt = 500
					newGroup.units[unitIndex].alt_type = 'RADIO'
					newGroup.units[unitIndex].speed = 60
				else
					--[[log:info('check height')
					newGroup.units[unitIndex].alt = land.getHeight({x = newGroup.units[unitIndex].x, y = newGroup.units[unitIndex].y})
					newGroup.units[unitIndex].alt_type = 'BARO']]
				end


			end

			if newCat == 'AIRPLANE' or newCat == 'HELICOPTER' then
				if newGroup.units[unitIndex].alt_type and newGroup.units[unitIndex].alt_type ~= 'BARO' or not newGroup.units[unitIndex].alt_type then
					newGroup.units[unitIndex].alt_type = 'RADIO'
				end
				if not unitData.speed then
					if newCat == 'AIRPLANE' then
						newGroup.units[unitIndex].speed = 150
					elseif newCat == 'HELICOPTER' then
						newGroup.units[unitIndex].speed = 60
					end
				end
				if not unitData.payload then
					newGroup.units[unitIndex].payload = mist.getPayload(originalName)
				end
			end
			mistAddedObjects[#mistAddedObjects + 1] = mist.utils.deepCopy(newGroup.units[unitIndex])
		end
		mistAddedGroups[#mistAddedGroups + 1] = mist.utils.deepCopy(newGroup)
		if newGroup.route and not newGroup.route.points then
			if not newGroup.route.points and newGroup.route[1] then
				local copyRoute = newGroup.route
				newGroup.route = {}
				newGroup.route.points = copyRoute
			end
		end
		newGroup.country = newCountry


		--mist.debug.writeData(mist.utils.serialize,{'msg', newGroup}, 'newGroup.lua')

		-- sanitize table
		newGroup.groupName = nil
		newGroup.clone = nil
		newGroup.category = nil
		newGroup.country = nil

		newGroup.tasks = {}

		for unitIndex, unitData in pairs(newGroup.units) do
			newGroup.units[unitIndex].unitName = nil
		end

		coalition.addGroup(country.id[newCountry], Unit.Category[newCat], newGroup)

		return newGroup

	end

	--- Schedules a function.
	-- Modified Slmod task scheduler, superior to timer.scheduleFunction
	-- @tparam function f function to schedule
	-- @tparam table vars array containing all parameters passed to the function
	-- @tparam number t time in seconds from mission start to schedule the function to.
	-- @tparam[opt] number rep time between repetitions of the function
	-- @tparam[opt] number st time in seconds from mission start at which the function
	-- should stop to be rescheduled.
	-- @treturn number scheduled function id.
	function mist.scheduleFunction(f, vars, t, rep, st)
		--verify correct types
		assert(type(f) == 'function', 'variable 1, expected function, got ' .. type(f))
		assert(type(vars) == 'table' or vars == nil, 'variable 2, expected table or nil, got ' .. type(f))
		assert(type(t) == 'number', 'variable 3, expected number, got ' .. type(t))
		assert(type(rep) == 'number' or rep == nil, 'variable 4, expected number or nil, got ' .. type(rep))
		assert(type(st) == 'number' or st == nil, 'variable 5, expected number or nil, got ' .. type(st))
		if not vars then
			vars = {}
		end
		taskId = taskId + 1
		table.insert(scheduledTasks, {f = f, vars = vars, t = t, rep = rep, st = st, id = taskId})
		return taskId
	end

	--- Removes a scheduled function.
	-- @tparam number id function id
	-- @treturn boolean true if function was successfully removed, false otherwise.
	function mist.removeFunction(id)
		local i = 1
		while i <= #scheduledTasks do
			if scheduledTasks[i].id == id then
				table.remove(scheduledTasks, i)
			else
				i = i + 1
			end
		end
	end

	--- Registers an event handler.
	-- @tparam function f function handling event
	-- @treturn number id of the event handler
	function mist.addEventHandler(f) --id is optional!
		local handler = {}
		idNum = idNum + 1
		handler.id = idNum
		handler.f = f
		function handler:onEvent(event)
			self.f(event)
		end
		world.addEventHandler(handler)
		return handler.id
	end

	--- Removes event handler with given id.
	-- @tparam number id event handler id
	-- @treturn boolean true on success, false otherwise
	function mist.removeEventHandler(id)
		for key, handler in pairs(world.eventHandlers) do
			if handler.id and handler.id == id then
				world.eventHandlers[key] = nil
				return true
			end
		end
		return false
	end
end

-- Begin common funcs
do
	--- Returns MGRS coordinates as string.
	-- @tparam string MGRS MGRS coordinates
	-- @tparam number acc the accuracy of each easting/northing.
	-- Can be: 0, 1, 2, 3, 4, or 5.
	function mist.tostringMGRS(MGRS, acc)
		if acc == 0 then
			return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph
		else
			return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph .. ' ' .. string.format('%0' .. acc .. 'd', mist.utils.round(MGRS.Easting/(10^(5-acc)), 0))
			.. ' ' .. string.format('%0' .. acc .. 'd', mist.utils.round(MGRS.Northing/(10^(5-acc)), 0))
		end
	end

	--[[acc:
	in DM: decimal point of minutes.
	In DMS: decimal point of seconds.
	position after the decimal of the least significant digit:
	So:
	42.32 - acc of 2.
	]]
	function mist.tostringLL(lat, lon, acc, DMS)

		local latHemi, lonHemi
		if lat > 0 then
			latHemi = 'N'
		else
			latHemi = 'S'
		end

		if lon > 0 then
			lonHemi = 'E'
		else
			lonHemi = 'W'
		end

		lat = math.abs(lat)
		lon = math.abs(lon)

		local latDeg = math.floor(lat)
		local latMin = (lat - latDeg)*60

		local lonDeg = math.floor(lon)
		local lonMin = (lon - lonDeg)*60

		if DMS then	-- degrees, minutes, and seconds.
			local oldLatMin = latMin
			latMin = math.floor(latMin)
			local latSec = mist.utils.round((oldLatMin - latMin)*60, acc)

			local oldLonMin = lonMin
			lonMin = math.floor(lonMin)
			local lonSec = mist.utils.round((oldLonMin - lonMin)*60, acc)

			if latSec == 60 then
				latSec = 0
				latMin = latMin + 1
			end

			if lonSec == 60 then
				lonSec = 0
				lonMin = lonMin + 1
			end

			local secFrmtStr -- create the formatting string for the seconds place
			if acc <= 0 then	-- no decimal place.
				secFrmtStr = '%02d'
			else
				local width = 3 + acc	-- 01.310 - that's a width of 6, for example.
				secFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
			end

			return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. '	 '
			.. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi

		else	-- degrees, decimal minutes.
			latMin = mist.utils.round(latMin, acc)
			lonMin = mist.utils.round(lonMin, acc)

			if latMin == 60 then
				latMin = 0
				latDeg = latDeg + 1
			end

			if lonMin == 60 then
				lonMin = 0
				lonDeg = lonDeg + 1
			end

			local minFrmtStr -- create the formatting string for the minutes place
			if acc <= 0 then	-- no decimal place.
				minFrmtStr = '%02d'
			else
				local width = 3 + acc	-- 01.310 - that's a width of 6, for example.
				minFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
			end

			return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. '	 '
			.. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi

		end
	end

	--[[ required: az - radian
		required: dist - meters
		optional: alt - meters (set to false or nil if you don't want to use it).
		optional: metric - set true to get dist and alt in km and m.
		precision will always be nearest degree and NM or km.]]
	function mist.tostringBR(az, dist, alt, metric)
		az = mist.utils.round(mist.utils.toDegree(az), 0)

		if metric then
			dist = mist.utils.round(dist/1000, 0)
		else
			dist = mist.utils.round(mist.utils.metersToNM(dist), 0)
		end

		local s = string.format('%03d', az) .. ' for ' .. dist

		if alt then
			if metric then
				s = s .. ' at ' .. mist.utils.round(alt, 0)
			else
				s = s .. ' at ' .. mist.utils.round(mist.utils.metersToFeet(alt), 0)
			end
		end
		return s
	end

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

	--- Returns skill of the given unit.
	-- @tparam string unitName unit name
	-- @return skill of the unit
	function mist.getUnitSkill(unitName)
		if mist.DBs.unitsByName[unitName] then
			if Unit.getByName(unitName) then
				local lunit = Unit.getByName(unitName)
				local data = mist.DBs.unitsByName[unitName]
				if data.unitName == unitName and data.type == lunit:getTypeName() and data.unitId == tonumber(lunit:getID()) and data.skill then
					return data.skill
				end
			end
		end
		log:error("Unit not found in DB: $1", unitName)
		return false
	end

	--- Returns an array containing a group's units positions.
	--	e.g.
	--		{
	--			[1] = {x = 299435.224, y = -1146632.6773},
	--			[2] = {x = 663324.6563, y = 322424.1112}
	--		}
	--	@tparam number|string groupIdent group id or name
	--	@treturn table array containing positions of each group member
	function mist.getGroupPoints(groupIdent)
		-- search by groupId and allow groupId and groupName as inputs
		local gpId = groupIdent
		if type(groupIdent) == 'string' and not tonumber(groupIdent) then
			if mist.DBs.MEgroupsByName[groupIdent] then
				gpId = mist.DBs.MEgroupsByName[groupIdent].groupId
			else
				log:error("Group not found in mist.DBs.MEgroupsByName: $1", groupIdent)
			end
		end

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

	--- getUnitAttitude(unit) return values.
	-- Yaw, AoA, ClimbAngle - relative to earth reference
	-- DOES NOT TAKE INTO ACCOUNT WIND.
	-- @table attitude
	-- @tfield number Heading in radians, range of 0 to 2*pi,
	-- relative to true north.
	-- @tfield number Pitch in radians, range of -pi/2 to pi/2
	-- @tfield number Roll in radians, range of 0 to 2*pi,
	-- right roll is positive direction.
	-- @tfield number Yaw in radians, range of -pi to pi,
	-- right yaw is positive direction.
	-- @tfield number AoA in radians, range of -pi to pi,
	-- rotation of aircraft to the right in comparison to
	-- flight direction being positive.
	-- @tfield number ClimbAngle in radians, range of -pi/2 to pi/2

	--- Returns the attitude of a given unit.
	-- Will work on any unit, even if not an aircraft.
	-- @tparam Unit unit unit whose attitude is returned.
	-- @treturn table @{attitude}
	function mist.getAttitude(unit)
		local unitpos = unit:getPosition()
		if unitpos then

			local Heading = math.atan2(unitpos.x.z, unitpos.x.x)

			Heading = Heading + mist.getNorthCorrection(unitpos.p)

			if Heading < 0 then
				Heading = Heading + 2*math.pi	-- put heading in range of 0 to 2*pi
			end
			---- heading complete.----

			local Pitch = math.asin(unitpos.x.y)
			---- pitch complete.----

			-- now get roll:
			--maybe not the best way to do it, but it works.

			--first, make a vector that is perpendicular to y and unitpos.x with cross product
			local cp = mist.vec.cp(unitpos.x, {x = 0, y = 1, z = 0})

			--now, get dot product of of this cross product with unitpos.z
			local dp = mist.vec.dp(cp, unitpos.z)

			--now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|)
			local Roll = math.acos(dp/(mist.vec.mag(cp)*mist.vec.mag(unitpos.z)))

			--now, have to get sign of roll.
			-- by convention, making right roll positive
			-- to get sign of roll, use the y component of unitpos.z.	For right roll, y component is negative.

			if unitpos.z.y > 0 then -- left roll, flip the sign of the roll
				Roll = -Roll
			end
			---- roll complete. ----

			--now, work on yaw, AoA, climb, and abs velocity
			local Yaw
			local AoA
			local ClimbAngle

			-- get unit velocity
			local unitvel = unit:getVelocity()
			if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity!
				local AxialVel = {}	--unit velocity transformed into aircraft axes directions

				--transform velocity components in direction of aircraft axes.
				AxialVel.x = mist.vec.dp(unitpos.x, unitvel)
				AxialVel.y = mist.vec.dp(unitpos.y, unitvel)
				AxialVel.z = mist.vec.dp(unitpos.z, unitvel)

				--Yaw is the angle between unitpos.x and the x and z velocities
				--define right yaw as positive
				Yaw = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/mist.vec.mag({x = AxialVel.x, y = 0, z = AxialVel.z}))

				--now set correct direction:
				if AxialVel.z > 0 then
					Yaw = -Yaw
				end

				-- AoA is angle between unitpos.x and the x and y velocities
				AoA = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/mist.vec.mag({x = AxialVel.x, y = AxialVel.y, z = 0}))

				--now set correct direction:
				if AxialVel.y > 0 then
					AoA = -AoA
				end

				ClimbAngle = math.asin(unitvel.y/mist.vec.mag(unitvel))
			end
			return { Heading = Heading, Pitch = Pitch, Roll = Roll, Yaw = Yaw, AoA = AoA, ClimbAngle = ClimbAngle}
		else
			log:error("Couldn't get unit's position")
		end
	end

	--- Returns heading of given unit.
	-- @tparam Unit unit unit whose heading is returned.
	-- @param rawHeading
	-- @treturn number heading of the unit, in range
	-- of 0 to 2*pi.
	function mist.getHeading(unit, rawHeading)
		local unitpos = unit:getPosition()
		if unitpos then
			local Heading = math.atan2(unitpos.x.z, unitpos.x.x)
			if not rawHeading then
				Heading = Heading + mist.getNorthCorrection(unitpos.p)
			end
			if Heading < 0 then
				Heading = Heading + 2*math.pi	-- put heading in range of 0 to 2*pi
			end
			return Heading
		end
	end

	--- Returns given unit's pitch
	-- @tparam Unit unit unit whose pitch is returned.
	-- @treturn number pitch of given unit
	function mist.getPitch(unit)
		local unitpos = unit:getPosition()
		if unitpos then
			return math.asin(unitpos.x.y)
		end
	end

	--- Returns given unit's roll.
	-- @tparam Unit unit unit whose roll is returned.
	-- @treturn number roll of given unit
	function mist.getRoll(unit)
		local unitpos = unit:getPosition()
		if unitpos then
			-- now get roll:
			--maybe not the best way to do it, but it works.

			--first, make a vector that is perpendicular to y and unitpos.x with cross product
			local cp = mist.vec.cp(unitpos.x, {x = 0, y = 1, z = 0})

			--now, get dot product of of this cross product with unitpos.z
			local dp = mist.vec.dp(cp, unitpos.z)

			--now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|)
			local Roll = math.acos(dp/(mist.vec.mag(cp)*mist.vec.mag(unitpos.z)))

			--now, have to get sign of roll.
			-- by convention, making right roll positive
			-- to get sign of roll, use the y component of unitpos.z.	For right roll, y component is negative.

			if unitpos.z.y > 0 then -- left roll, flip the sign of the roll
				Roll = -Roll
			end
			return Roll
		end
	end

	--- Returns given unit's yaw.
	-- @tparam Unit unit unit whose yaw is returned.
	-- @treturn number yaw of given unit.
	function mist.getYaw(unit)
		local unitpos = unit:getPosition()
		if unitpos then
			-- get unit velocity
			local unitvel = unit:getVelocity()
			if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity!
				local AxialVel = {}	--unit velocity transformed into aircraft axes directions

				--transform velocity components in direction of aircraft axes.
				AxialVel.x = mist.vec.dp(unitpos.x, unitvel)
				AxialVel.y = mist.vec.dp(unitpos.y, unitvel)
				AxialVel.z = mist.vec.dp(unitpos.z, unitvel)

				--Yaw is the angle between unitpos.x and the x and z velocities
				--define right yaw as positive
				local Yaw = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = 0, z = AxialVel.z})/mist.vec.mag({x = AxialVel.x, y = 0, z = AxialVel.z}))

				--now set correct direction:
				if AxialVel.z > 0 then
					Yaw = -Yaw
				end
				return Yaw
			end
		end
	end

	--- Returns given unit's angle of attack.
	-- @tparam Unit unit unit to get AoA from.
	-- @treturn number angle of attack of the given unit.
	function mist.getAoA(unit)
		local unitpos = unit:getPosition()
		if unitpos then
			local unitvel = unit:getVelocity()
			if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity!
				local AxialVel = {}	--unit velocity transformed into aircraft axes directions

				--transform velocity components in direction of aircraft axes.
				AxialVel.x = mist.vec.dp(unitpos.x, unitvel)
				AxialVel.y = mist.vec.dp(unitpos.y, unitvel)
				AxialVel.z = mist.vec.dp(unitpos.z, unitvel)

				-- AoA is angle between unitpos.x and the x and y velocities
				local AoA = math.acos(mist.vec.dp({x = 1, y = 0, z = 0}, {x = AxialVel.x, y = AxialVel.y, z = 0})/mist.vec.mag({x = AxialVel.x, y = AxialVel.y, z = 0}))

				--now set correct direction:
				if AxialVel.y > 0 then
					AoA = -AoA
				end
				return AoA
			end
		end
	end

	--- Returns given unit's climb angle.
	-- @tparam Unit unit unit to get climb angle from.
	-- @treturn number climb angle of given unit.
	function mist.getClimbAngle(unit)
		local unitpos = unit:getPosition()
		if unitpos then
			local unitvel = unit:getVelocity()
			if mist.vec.mag(unitvel) ~= 0 then --must have non-zero velocity!
				return math.asin(unitvel.y/mist.vec.mag(unitvel))
			end
		end
	end

	--[[--
	Unit name table.
	Many Mist functions require tables of unit names, which are known
	in Mist as UnitNameTables. These follow a special set of shortcuts
	borrowed from Slmod. These shortcuts alleviate the problem of entering
	huge lists of unit names by hand, and in many cases, they remove the
	need to even know the names of the units in the first place!

	These are the unit table "short-cut" commands:

	Prefixes:
			"[-u]<unit name>" - subtract this unit if its in the table
			"[g]<group name>" - add this group to the table
			"[-g]<group name>" - subtract this group from the table
			"[c]<country name>"	- add this country's units
			"[-c]<country name>" - subtract this country's units if any are in the table

	Stand-alone identifiers
			"[all]" - add all units
			"[-all]" - subtract all units (not very useful by itself)
			"[blue]" - add all blue units
			"[-blue]" - subtract all blue units
			"[red]" - add all red coalition units
			"[-red]" - subtract all red units

	Compound Identifiers:
			"[c][helicopter]<country name>"	- add all of this country's helicopters
			"[-c][helicopter]<country name>" - subtract all of this country's helicopters
			"[c][plane]<country name>"	- add all of this country's planes
			"[-c][plane]<country name>" - subtract all of this country's planes
			"[c][ship]<country name>"	- add all of this country's ships
			"[-c][ship]<country name>" - subtract all of this country's ships
			"[c][vehicle]<country name>"	- add all of this country's vehicles
			"[-c][vehicle]<country name>" - subtract all of this country's vehicles

			"[all][helicopter]" -	add all helicopters
			"[-all][helicopter]" - subtract all helicopters
			"[all][plane]" - add all	planes
			"[-all][plane]" - subtract all planes
			"[all][ship]" - add all ships
			"[-all][ship]" - subtract all ships
			"[all][vehicle]" - add all vehicles
			"[-all][vehicle]" - subtract all vehicles

			"[blue][helicopter]" -	add all blue coalition helicopters
			"[-blue][helicopter]" - subtract all blue coalition helicopters
			"[blue][plane]" - add all blue coalition planes
			"[-blue][plane]" - subtract all blue coalition planes
			"[blue][ship]" - add all blue coalition ships
			"[-blue][ship]" - subtract all blue coalition ships
			"[blue][vehicle]" - add all blue coalition vehicles
			"[-blue][vehicle]" - subtract all blue coalition vehicles

			"[red][helicopter]" -	add all red coalition helicopters
			"[-red][helicopter]" - subtract all red coalition helicopters
			"[red][plane]" - add all red coalition planes
			"[-red][plane]" - subtract all red coalition planes
			"[red][ship]" - add all red coalition ships
			"[-red][ship]" - subtract all red coalition ships
			"[red][vehicle]" - add all red coalition vehicles
			"[-red][vehicle]" - subtract all red coalition vehicles

	Country names to be used in [c] and [-c] short-cuts:
			Turkey
			Norway
			The Netherlands
			Spain
			11
			UK
			Denmark
			USA
			Georgia
			Germany
			Belgium
			Canada
			France
			Israel
			Ukraine
			Russia
			South Ossetia
			Abkhazia
			Italy
			Australia
			Austria
			Belarus
			Bulgaria
			Czech Republic
			China
			Croatia
			Finland
			Greece
			Hungary
			India
			Iran
			Iraq
			Japan
			Kazakhstan
			North Korea
			Pakistan
			Poland
			Romania
			Saudi Arabia
			Serbia, Slovakia
			South Korea
			Sweden
			Switzerland
			Syria
			USAF Aggressors

	Do NOT use a '[u]' notation for single units. Single units are referenced
	the same way as before: Simply input their names as strings.

	These unit tables are evaluated in order, and you cannot subtract a unit
	from a table before it is added. For example:

			{'[blue]', '[-c]Georgia'}

	will evaluate to all of blue coalition except those units owned by the
	country named "Georgia"; however:

			{'[-c]Georgia', '[blue]'}

	will evaluate to all of the units in blue coalition, because the addition
	of all units owned by blue coalition occurred AFTER the subtraction of all
	units owned by Georgia (which actually subtracted nothing at all, since
	there were no units in the table when the subtraction occurred).

	More examples:

			{'[blue][plane]', '[-c]Georgia', '[-g]Hawg 1'}

	Evaluates to all blue planes, except those blue units owned by the country
	named "Georgia" and the units in the group named "Hawg1".


			{'[g]arty1', '[g]arty2', '[-u]arty1_AD', '[-u]arty2_AD', 'Shark 11' }

	Evaluates to the unit named "Shark 11", plus all the units in groups named
	"arty1" and "arty2" except those that are named "arty1\_AD" and "arty2\_AD".

	@table UnitNameTable
	]]

	--- Returns a table containing unit names.
	-- @tparam table tbl sequential strings
	-- @treturn table @{UnitNameTable}
	function mist.makeUnitTable(tbl)
		--Assumption: will be passed a table of strings, sequential
		log:info(tbl)
		local units_by_name = {}

		local l_munits = mist.DBs.units	--local reference for faster execution
		for i = 1, #tbl do
			local unit = tbl[i]
			if unit:sub(1,4) == '[-u]' then --subtract a unit
				if units_by_name[unit:sub(5)] then -- 5 to end
				units_by_name[unit:sub(5)] = nil	--remove
			end
		elseif unit:sub(1,3) == '[g]' then -- add a group
			for coa, coa_tbl in pairs(l_munits) do
				for country, country_table in pairs(coa_tbl) do
					for unit_type, unit_type_tbl in pairs(country_table) do
						if type(unit_type_tbl) == 'table' then
							for group_ind, group_tbl in pairs(unit_type_tbl) do
								if type(group_tbl) == 'table' and group_tbl.groupName == unit:sub(4) then
									-- index 4 to end
									for unit_ind, unit in pairs(group_tbl.units) do
										units_by_name[unit.unitName] = true	--add
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,4) == '[-g]' then -- subtract a group
			for coa, coa_tbl in pairs(l_munits) do
				for country, country_table in pairs(coa_tbl) do
					for unit_type, unit_type_tbl in pairs(country_table) do
						if type(unit_type_tbl) == 'table' then
							for group_ind, group_tbl in pairs(unit_type_tbl) do
								if type(group_tbl) == 'table' and group_tbl.groupName == unit:sub(5) then
									-- index 5 to end
									for unit_ind, unit in pairs(group_tbl.units) do
										if units_by_name[unit.unitName] then
											units_by_name[unit.unitName] = nil --remove
										end
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,3) == '[c]' then -- add a country
			local category = ''
			local country_start = 4
			if unit:sub(4,15) == '[helicopter]' then
				category = 'helicopter'
				country_start = 16
			elseif unit:sub(4,10) == '[plane]' then
				category = 'plane'
				country_start = 11
			elseif unit:sub(4,9) == '[ship]' then
				category = 'ship'
				country_start = 10
			elseif unit:sub(4,12) == '[vehicle]' then
				category = 'vehicle'
				country_start = 13
			end
			for coa, coa_tbl in pairs(l_munits) do
				for country, country_table in pairs(coa_tbl) do
					if country == string.lower(unit:sub(country_start)) then	 -- match
						for unit_type, unit_type_tbl in pairs(country_table) do
							if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then
								for group_ind, group_tbl in pairs(unit_type_tbl) do
									if type(group_tbl) == 'table' then
										for unit_ind, unit in pairs(group_tbl.units) do
											units_by_name[unit.unitName] = true	--add
										end
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,4) == '[-c]' then -- subtract a country
			local category = ''
			local country_start = 5
			if unit:sub(5,16) == '[helicopter]' then
				category = 'helicopter'
				country_start = 17
			elseif unit:sub(5,11) == '[plane]' then
				category = 'plane'
				country_start = 12
			elseif unit:sub(5,10) == '[ship]' then
				category = 'ship'
				country_start = 11
			elseif unit:sub(5,13) == '[vehicle]' then
				category = 'vehicle'
				country_start = 14
			end
			for coa, coa_tbl in pairs(l_munits) do
				for country, country_table in pairs(coa_tbl) do
					if country == string.lower(unit:sub(country_start)) then	 -- match
						for unit_type, unit_type_tbl in pairs(country_table) do
							if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then
								for group_ind, group_tbl in pairs(unit_type_tbl) do
									if type(group_tbl) == 'table' then
										for unit_ind, unit in pairs(group_tbl.units) do
											if units_by_name[unit.unitName] then
												units_by_name[unit.unitName] = nil	--remove
											end
										end
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,6) ==	'[blue]' then -- add blue coalition
			local category = ''
			if unit:sub(7) == '[helicopter]' then
				category = 'helicopter'
			elseif unit:sub(7) == '[plane]' then
				category = 'plane'
			elseif unit:sub(7) == '[ship]' then
				category = 'ship'
			elseif unit:sub(7) == '[vehicle]' then
				category = 'vehicle'
			end
			for coa, coa_tbl in pairs(l_munits) do
				if coa == 'blue' then
					for country, country_table in pairs(coa_tbl) do
						for unit_type, unit_type_tbl in pairs(country_table) do
							if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then
								for group_ind, group_tbl in pairs(unit_type_tbl) do
									if type(group_tbl) == 'table' then
										for unit_ind, unit in pairs(group_tbl.units) do
											units_by_name[unit.unitName] = true	--add
										end
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,7) == '[-blue]' then -- subtract blue coalition
			local category = ''
			if unit:sub(8) == '[helicopter]' then
				category = 'helicopter'
			elseif unit:sub(8) == '[plane]' then
				category = 'plane'
			elseif unit:sub(8) == '[ship]' then
				category = 'ship'
			elseif unit:sub(8) == '[vehicle]' then
				category = 'vehicle'
			end
			for coa, coa_tbl in pairs(l_munits) do
				if coa == 'blue' then
					for country, country_table in pairs(coa_tbl) do
						for unit_type, unit_type_tbl in pairs(country_table) do
							if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then
								for group_ind, group_tbl in pairs(unit_type_tbl) do
									if type(group_tbl) == 'table' then
										for unit_ind, unit in pairs(group_tbl.units) do
											if units_by_name[unit.unitName] then
												units_by_name[unit.unitName] = nil	--remove
											end
										end
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,5) == '[red]' then -- add red coalition
			local category = ''
			if unit:sub(6) == '[helicopter]' then
				category = 'helicopter'
			elseif unit:sub(6) == '[plane]' then
				category = 'plane'
			elseif unit:sub(6) == '[ship]' then
				category = 'ship'
			elseif unit:sub(6) == '[vehicle]' then
				category = 'vehicle'
			end
			for coa, coa_tbl in pairs(l_munits) do
				if coa == 'red' then
					for country, country_table in pairs(coa_tbl) do
						for unit_type, unit_type_tbl in pairs(country_table) do
							if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then
								for group_ind, group_tbl in pairs(unit_type_tbl) do
									if type(group_tbl) == 'table' then
										for unit_ind, unit in pairs(group_tbl.units) do
											units_by_name[unit.unitName] = true	--add
										end
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,6) == '[-red]' then -- subtract red coalition
			local category = ''
			if unit:sub(7) == '[helicopter]' then
				category = 'helicopter'
			elseif unit:sub(7) == '[plane]' then
				category = 'plane'
			elseif unit:sub(7) == '[ship]' then
				category = 'ship'
			elseif unit:sub(7) == '[vehicle]' then
				category = 'vehicle'
			end
			for coa, coa_tbl in pairs(l_munits) do
				if coa == 'red' then
					for country, country_table in pairs(coa_tbl) do
						for unit_type, unit_type_tbl in pairs(country_table) do
							if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then
								for group_ind, group_tbl in pairs(unit_type_tbl) do
									if type(group_tbl) == 'table' then
										for unit_ind, unit in pairs(group_tbl.units) do
											if units_by_name[unit.unitName] then
												units_by_name[unit.unitName] = nil	--remove
											end
										end
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,5) == '[all]' then -- add all of a certain category (or all categories)
			local category = ''
			if unit:sub(6) == '[helicopter]' then
				category = 'helicopter'
			elseif unit:sub(6) == '[plane]' then
				category = 'plane'
			elseif unit:sub(6) == '[ship]' then
				category = 'ship'
			elseif unit:sub(6) == '[vehicle]' then
				category = 'vehicle'
			end
			for coa, coa_tbl in pairs(l_munits) do
				for country, country_table in pairs(coa_tbl) do
					for unit_type, unit_type_tbl in pairs(country_table) do
						if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then
							for group_ind, group_tbl in pairs(unit_type_tbl) do
								if type(group_tbl) == 'table' then
									for unit_ind, unit in pairs(group_tbl.units) do
										units_by_name[unit.unitName] = true	--add
									end
								end
							end
						end
					end
				end
			end
		elseif unit:sub(1,6) == '[-all]' then -- subtract all of a certain category (or all categories)
			local category = ''
			if unit:sub(7) == '[helicopter]' then
				category = 'helicopter'
			elseif unit:sub(7) == '[plane]' then
				category = 'plane'
			elseif unit:sub(7) == '[ship]' then
				category = 'ship'
			elseif unit:sub(7) == '[vehicle]' then
				category = 'vehicle'
			end
			for coa, coa_tbl in pairs(l_munits) do
				for country, country_table in pairs(coa_tbl) do
					for unit_type, unit_type_tbl in pairs(country_table) do
						if type(unit_type_tbl) == 'table' and (category == '' or unit_type == category) then
							for group_ind, group_tbl in pairs(unit_type_tbl) do
								if type(group_tbl) == 'table' then
									for unit_ind, unit in pairs(group_tbl.units) do
										if units_by_name[unit.unitName] then
											units_by_name[unit.unitName] = nil	--remove
										end
									end
								end
							end
						end
					end
				end
			end
		else -- just a regular unit
			units_by_name[unit] = true	--add
		end
	end

	local units_tbl = {}	-- indexed sequentially
	for unit_name, val in pairs(units_by_name) do
		if val then
			units_tbl[#units_tbl + 1] = unit_name	-- add all the units to the table
		end
	end


	units_tbl.processed = timer.getTime()	--add the processed flag
	return units_tbl
end

function mist.getDeadMapObjsInZones(zone_names)
	-- zone_names: table of zone names
	-- returns: table of dead map objects (indexed numerically)
	local map_objs = {}
	local zones = {}
	for i = 1, #zone_names do
		if mist.DBs.zonesByName[zone_names[i]] then
			zones[#zones + 1] = mist.DBs.zonesByName[zone_names[i]]
		end
	end
	for obj_id, obj in pairs(mist.DBs.deadObjects) do
		if obj.objectType and obj.objectType == 'building' then --dead map object
			for i = 1, #zones do
				if ((zones[i].point.x - obj.objectPos.x)^2 + (zones[i].point.z - obj.objectPos.z)^2)^0.5 <= zones[i].radius then
					map_objs[#map_objs + 1] = mist.utils.deepCopy(obj)
				end
			end
		end
	end
	return map_objs
end

function mist.getDeadMapObjsInPolygonZone(zone)
	-- zone_names: table of zone names
	-- returns: table of dead map objects (indexed numerically)
	local map_objs = {}
	for obj_id, obj in pairs(mist.DBs.deadObjects) do
		if obj.objectType and obj.objectType == 'building' then --dead map object
			if mist.pointInPolygon(obj.objectPos, zone) then
				map_objs[#map_objs + 1] = mist.utils.deepCopy(obj)
			end
		end
	end
	return map_objs
end

function mist.pointInPolygon(point, poly, maxalt) --raycasting point in polygon. Code from http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm
	--[[local type_tbl = {
		point = {'table'},
		poly = {'table'},
		maxalt = {'number', 'nil'},
		}

	local err, errmsg = mist.utils.typeCheck('mist.pointInPolygon', type_tbl, {point, poly, maxalt})
	assert(err, errmsg)
	]]
	point = mist.utils.makeVec3(point)
	local px = point.x
	local pz = point.z
	local cn = 0
	local newpoly = mist.utils.deepCopy(poly)

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

		newpoly[1] = mist.utils.makeVec3(newpoly[1])

		for k = 1, polysize do
			newpoly[k+1] = 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 mist.getUnitsInPolygon(unit_names, polyZone, max_alt)
	local units = {}

	for i = 1, #unit_names do
		units[#units + 1] = Unit.getByName(unitNames[i])
	end

	local inZoneUnits = {}
	for i =1, #units do
		if units[i]:isActive() and mist.pointInPolygon(units[i]:getPosition().p, polyZone, max_alt) then
			inZoneUnits[inZoneUnits + 1] = units[i]
		end
	end

	return inZoneUnits
end

function mist.getUnitsInZones(unit_names, zone_names, zone_type)

	zone_type = zone_type or 'cylinder'
	if zone_type == 'c' or zone_type == 'cylindrical' or zone_type == 'C' then
		zone_type = 'cylinder'
	end
	if zone_type == 's' or zone_type == 'spherical' or zone_type == 'S' then
		zone_type = 'sphere'
	end

	assert(zone_type == 'cylinder' or zone_type == 'sphere', 'invalid zone_type: ' .. tostring(zone_type))

	local units = {}
	local zones = {}

	for k = 1, #unit_names do
		local unit = Unit.getByName(unit_names[k])
		if unit then
			units[#units + 1] = unit
		end
	end


	for k = 1, #zone_names do
		local zone = trigger.misc.getZone(zone_names[k])
		if zone then
			zones[#zones + 1] = {radius = zone.radius, x = zone.point.x, y = zone.point.y, z = zone.point.z}
		end
	end

	local in_zone_units = {}

	for units_ind = 1, #units do
		for zones_ind = 1, #zones do
			if zone_type == 'sphere' then	--add land height value for sphere zone type
				local alt = land.getHeight({x = zones[zones_ind].x, y = zones[zones_ind].z})
				if alt then
					zones[zones_ind].y = alt
				end
			end
			local unit_pos = units[units_ind]:getPosition().p
			if unit_pos and units[units_ind]:isActive() == true then
				if zone_type == 'cylinder' and (((unit_pos.x - zones[zones_ind].x)^2 + (unit_pos.z - zones[zones_ind].z)^2)^0.5 <= zones[zones_ind].radius) then
					in_zone_units[#in_zone_units + 1] = units[units_ind]
					break
				elseif zone_type == 'sphere' and (((unit_pos.x - zones[zones_ind].x)^2 + (unit_pos.y - zones[zones_ind].y)^2 + (unit_pos.z - zones[zones_ind].z)^2)^0.5 <= zones[zones_ind].radius) then
					in_zone_units[#in_zone_units + 1] = units[units_ind]
					break
				end
			end
		end
	end
	return in_zone_units
end

function mist.getUnitsInMovingZones(unit_names, zone_unit_names, radius, zone_type)

	zone_type = zone_type or 'cylinder'
	if zone_type == 'c' or zone_type == 'cylindrical' or zone_type == 'C' then
		zone_type = 'cylinder'
	end
	if zone_type == 's' or zone_type == 'spherical' or zone_type == 'S' then
		zone_type = 'sphere'
	end

	assert(zone_type == 'cylinder' or zone_type == 'sphere', 'invalid zone_type: ' .. tostring(zone_type))

	local units = {}
	local zone_units = {}

	for k = 1, #unit_names do
		local unit = Unit.getByName(unit_names[k])
		if unit then
			units[#units + 1] = unit
		end
	end

	for k = 1, #zone_unit_names do
		local unit = Unit.getByName(zone_unit_names[k])
		if unit then
			zone_units[#zone_units + 1] = unit
		end
	end

	local in_zone_units = {}

	for units_ind = 1, #units do
		for zone_units_ind = 1, #zone_units do
			local unit_pos = units[units_ind]:getPosition().p
			local zone_unit_pos = zone_units[zone_units_ind]:getPosition().p
			if unit_pos and zone_unit_pos and units[units_ind]:isActive() == true then
				if zone_type == 'cylinder' and (((unit_pos.x - zone_unit_pos.x)^2 + (unit_pos.z - zone_unit_pos.z)^2)^0.5 <= radius) then
					in_zone_units[#in_zone_units + 1] = units[units_ind]
					break
				elseif zone_type == 'sphere' and (((unit_pos.x - zone_unit_pos.x)^2 + (unit_pos.y - zone_unit_pos.y)^2 + (unit_pos.z - zone_unit_pos.z)^2)^0.5 <= radius) then
					in_zone_units[#in_zone_units + 1] = units[units_ind]
					break
				end
			end
		end
	end
	return in_zone_units
end

function mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius)
	log:info("$1, $2, $3, $4, $5", unitset1, altoffset1, unitset2, altoffset2, radius)
	radius = radius or math.huge
	local unit_info1 = {}
	local unit_info2 = {}

	-- get the positions all in one step, saves execution time.
	for unitset1_ind = 1, #unitset1 do
		local unit1 = Unit.getByName(unitset1[unitset1_ind])
		if unit1 and unit1:isActive() == true then
			unit_info1[#unit_info1 + 1] = {}
			unit_info1[#unit_info1].unit = unit1
			unit_info1[#unit_info1].pos	= unit1:getPosition().p
		end
	end

	for unitset2_ind = 1, #unitset2 do
		local unit2 = Unit.getByName(unitset2[unitset2_ind])
		if unit2 and unit2:isActive() == true then
			unit_info2[#unit_info2 + 1] = {}
			unit_info2[#unit_info2].unit = unit2
			unit_info2[#unit_info2].pos	= unit2:getPosition().p
		end
	end

	local LOS_data = {}
	-- now compute los
	for unit1_ind = 1, #unit_info1 do
		local unit_added = false
		for unit2_ind = 1, #unit_info2 do
			if radius == math.huge or (mist.vec.mag(mist.vec.sub(unit_info1[unit1_ind].pos, unit_info2[unit2_ind].pos)) < radius) then -- inside radius
				local point1 = { x = unit_info1[unit1_ind].pos.x, y = unit_info1[unit1_ind].pos.y + altoffset1, z = unit_info1[unit1_ind].pos.z}
				local point2 = { x = unit_info2[unit2_ind].pos.x, y = unit_info2[unit2_ind].pos.y + altoffset2, z = unit_info2[unit2_ind].pos.z}
				if land.isVisible(point1, point2) then
					if unit_added == false then
						unit_added = true
						LOS_data[#LOS_data + 1] = {}
						LOS_data[#LOS_data].unit = unit_info1[unit1_ind].unit
						LOS_data[#LOS_data].vis = {}
						LOS_data[#LOS_data].vis[#LOS_data[#LOS_data].vis + 1] = unit_info2[unit2_ind].unit
					else
						LOS_data[#LOS_data].vis[#LOS_data[#LOS_data].vis + 1] = unit_info2[unit2_ind].unit
					end
				end
			end
		end
	end

	return LOS_data
end

function mist.getAvgPoint(points)
	local avgX, avgY, avgZ, totNum = 0, 0, 0, 0
	for i = 1, #points do
		local nPoint = 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

--Gets the average position of a group of units (by name)
function 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 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 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 mist.getAvgPos(units)

end

--[[ vars for mist.getMGRSString:
vars.units - table of unit names (NOT unitNameTable- maybe this should change).
vars.acc - integer between 0 and 5, inclusive
]]
function mist.getMGRSString(vars)
	local units = vars.units
	local acc = vars.acc or 5
	local avgPos = mist.getAvgPos(units)
	if avgPos then
		return mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(avgPos)), acc)
	end
end

--[[ vars for mist.getLLString
vars.units - table of unit names (NOT unitNameTable- maybe this should change).
vars.acc - integer, number of numbers after decimal place
vars.DMS - if true, output in degrees, minutes, seconds.	Otherwise, output in degrees, minutes.
]]
function mist.getLLString(vars)
	local units = vars.units
	local acc = vars.acc or 3
	local DMS = vars.DMS
	local avgPos = mist.getAvgPos(units)
	if avgPos then
		local lat, lon = coord.LOtoLL(avgPos)
		return mist.tostringLL(lat, lon, acc, DMS)
	end
end

--[[
vars.units- table of unit names (NOT unitNameTable- maybe this should change).
vars.ref -	vec3 ref point, maybe overload for vec2 as well?
vars.alt - boolean, if used, includes altitude in string
vars.metric - boolean, gives distance in km instead of NM.
]]
function mist.getBRString(vars)
	local units = vars.units
	local ref = mist.utils.makeVec3(vars.ref, 0)	-- turn it into Vec3 if it is not already.
	local alt = vars.alt
	local metric = vars.metric
	local avgPos = mist.getAvgPos(units)
	if avgPos then
		local vec = {x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z}
		local dir = mist.utils.getDir(vec, ref)
		local dist = mist.utils.get2DDist(avgPos, ref)
		if alt then
			alt = avgPos.y
		end
		return mist.tostringBR(dir, dist, alt, metric)
	end
end

-- Returns the Vec3 coordinates of the average position of the concentration of units most in the heading direction.
--[[ vars for mist.getLeadingPos:
vars.units - table of unit names
vars.heading - direction
vars.radius - number
vars.headingDegrees - boolean, switches heading to degrees
]]
function mist.getLeadingPos(vars)
	local units = vars.units
	local heading = vars.heading
	local radius = vars.radius
	if vars.headingDegrees then
		heading = mist.utils.toRadian(vars.headingDegrees)
	end

	local unitPosTbl = {}
	for i = 1, #units do
		local unit = Unit.getByName(units[i])
		if unit and unit:isExist() then
			unitPosTbl[#unitPosTbl + 1] = unit:getPosition().p
		end
	end
	if #unitPosTbl > 0 then	-- one more more units found.
		-- first, find the unit most in the heading direction
		local maxPos = -math.huge

		local maxPosInd	-- maxPos - the furthest in direction defined by heading; maxPosInd =
		for i = 1, #unitPosTbl do
			local rotatedVec2 = mist.vec.rotateVec2(mist.utils.makeVec2(unitPosTbl[i]), heading)
			if (not maxPos) or maxPos < rotatedVec2.x then
				maxPos = rotatedVec2.x
				maxPosInd = i
			end
		end

		--now, get all the units around this unit...
		local avgPos
		if radius then
			local maxUnitPos = unitPosTbl[maxPosInd]
			local avgx, avgy, avgz, totNum = 0, 0, 0, 0
			for i = 1, #unitPosTbl do
				if mist.utils.get2DDist(maxUnitPos, unitPosTbl[i]) <= radius then
					avgx = avgx + unitPosTbl[i].x
					avgy = avgy + unitPosTbl[i].y
					avgz = avgz + unitPosTbl[i].z
					totNum = totNum + 1
				end
			end
			avgPos = { x = avgx/totNum, y = avgy/totNum, z = avgz/totNum}
		else
			avgPos = unitPosTbl[maxPosInd]
		end

		return avgPos
	end
end

--[[ vars for mist.getLeadingMGRSString:
vars.units - table of unit names
vars.heading - direction
vars.radius - number
vars.headingDegrees - boolean, switches heading to degrees
vars.acc - number, 0 to 5.
]]
function mist.getLeadingMGRSString(vars)
	local pos = mist.getLeadingPos(vars)
	if pos then
		local acc = vars.acc or 5
		return mist.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)), acc)
	end
end

--[[ vars for mist.getLeadingLLString:
vars.units - table of unit names
vars.heading - direction, number
vars.radius - number
vars.headingDegrees - boolean, switches heading to degrees
vars.acc - number of digits after decimal point (can be negative)
vars.DMS -	boolean, true if you want DMS.
]]
function mist.getLeadingLLString(vars)
	local pos = mist.getLeadingPos(vars)
	if pos then
		local acc = vars.acc or 3
		local DMS = vars.DMS
		local lat, lon = coord.LOtoLL(pos)
		return mist.tostringLL(lat, lon, acc, DMS)
	end
end

--[[ vars for mist.getLeadingBRString:
vars.units - table of unit names
vars.heading - direction, number
vars.radius - number
vars.headingDegrees - boolean, switches heading to degrees
vars.metric - boolean, if true, use km instead of NM.
vars.alt - boolean, if true, include altitude.
vars.ref - vec3/vec2 reference point.
]]
function mist.getLeadingBRString(vars)
	local pos = mist.getLeadingPos(vars)
	if pos then
		local ref = vars.ref
		local alt = vars.alt
		local metric = vars.metric

		local vec = {x = pos.x - ref.x, y = pos.y - ref.y, z = pos.z - ref.z}
		local dir = mist.utils.getDir(vec, ref)
		local dist = mist.utils.get2DDist(pos, ref)
		if alt then
			alt = pos.y
		end
		return mist.tostringBR(dir, dist, alt, metric)
	end
end

end

--- Group functions.
-- @section groups
do -- group functions scope

	--- Check table used for group creation.
	-- @tparam table groupData table to check.
	-- @treturn boolean true if a group can be spawned using
	-- this table, false otherwise.
	function mist.groupTableCheck(groupData)
		-- return false if country, category
		-- or units are missing
		if not groupData.country or
			not groupData.category or
			not groupData.units then
			return false
		end
		-- return false if unitData misses
		-- x, y or type
		for unitId, unitData in pairs(groupData.units) do
			if not unitData.x or
				not unitData.y or
				not unitData.type then
					return false
			end
		end
		-- everything we need is here return true
		return true
	end

	--- Returns group data table of give group.
	function mist.getCurrentGroupData(gpName)
		local dbData = mist.getGroupData(gpName)

		if Group.getByName(gpName) and Group.getByName(gpName):isExist() == true then
			local newGroup = Group.getByName(gpName)
			local newData = {}
			newData.name = gpName
			newData.groupId = tonumber(newGroup:getID())
			newData.category = newGroup:getCategory()
			newData.groupName = gpName
			newData.hidden = dbData.hidden

			if newData.category == 2 then
				newData.category = 'vehicle'
			elseif newData.category == 3 then
				newData.category = 'ship'
			end

			newData.units = {}
			local newUnits = newGroup:getUnits()
			for unitNum, unitData in pairs(newGroup:getUnits()) do
				newData.units[unitNum] = {}
                local uName = unitData:getName()

                if mist.DBs.unitsByName[uName] and unitData:getTypeName() ==  mist.DBs.unitsByName[uName].type and mist.DBs.unitsByName[uName].unitId == tonumber(unitData:getID()) then -- If old data matches most of new data
                    newData.units[unitNum] = mist.utils.deepCopy(mist.DBs.unitsByName[uName])
                else
                    newData.units[unitNum].unitId = tonumber(unitData:getID())
                    newData.units[unitNum].type = unitData:getTypeName()
                    newData.units[unitNum].skill = mist.getUnitSkill(uName)
                    newData.country = string.lower(country.name[unitData:getCountry()])
                    newData.units[unitNum].callsign = unitData:getCallsign()
                    newData.units[unitNum].unitName = uName
                end

				newData.units[unitNum].x = unitData:getPosition().p.x
				newData.units[unitNum].y = unitData:getPosition().p.z
                newData.units[unitNum].point = {x = newData.units[unitNum].x, y = newData.units[unitNum].y}
                newData.units[unitNum].heading = mist.getHeading(unitData, true) -- added to DBs
				newData.units[unitNum].alt = unitData:getPosition().p.y
                newData.units[unitNum].speed = mist.vec.mag(unitData:getVelocity())
               
			end

			return newData
		elseif StaticObject.getByName(gpName) and StaticObject.getByName(gpName):isExist() == true then
			local staticObj = StaticObject.getByName(gpName)
			dbData.units[1].x = staticObj:getPosition().p.x
			dbData.units[1].y = staticObj:getPosition().p.z
			dbData.units[1].alt = staticObj:getPosition().p.y
			dbData.units[1].heading = mist.getHeading(staticObj, true)

			return dbData
		end

	end

	function mist.getGroupData(gpName)
		local found = false
		local newData = {}
		if mist.DBs.groupsByName[gpName] then
			newData = mist.utils.deepCopy(mist.DBs.groupsByName[gpName])
			found = true
		end

		if found == false then
			for groupName, groupData in pairs(mist.DBs.groupsByName) do
				if mist.stringMatch(groupName, gpName) == true then
					newData = mist.utils.deepCopy(groupData)
					newData.groupName = groupName
					found = true
					break
				end
			end
		end

		local payloads
		if newData.category == 'plane' or newData.category == 'helicopter' then
			payloads = mist.getGroupPayload(newData.groupName)
		end
		if found == true then
			--newData.hidden = false -- maybe add this to DBs

			for unitNum, unitData in pairs(newData.units) do
				newData.units[unitNum] = {}

				newData.units[unitNum].unitId = unitData.unitId
				--newData.units[unitNum].point = unitData.point
				newData.units[unitNum].x = unitData.point.x
				newData.units[unitNum].y = unitData.point.y
				newData.units[unitNum].alt = unitData.alt
				newData.units[unitNum].alt_type = unitData.alt_type
				newData.units[unitNum].speed = unitData.speed
				newData.units[unitNum].type = unitData.type
				newData.units[unitNum].skill = unitData.skill
				newData.units[unitNum].unitName = unitData.unitName
				newData.units[unitNum].heading = unitData.heading -- added to DBs
				newData.units[unitNum].playerCanDrive = unitData.playerCanDrive -- added to DBs


				if newData.category == 'plane' or newData.category == 'helicopter' then
					newData.units[unitNum].payload = payloads[unitNum]
					newData.units[unitNum].livery_id = unitData.livery_id
					newData.units[unitNum].onboard_num = unitData.onboard_num
					newData.units[unitNum].callsign = unitData.callsign
					newData.units[unitNum].AddPropAircraft = unitData.AddPropAircraft
				end
				if newData.category == 'static' then
					newData.units[unitNum].categoryStatic = unitData.categoryStatic
					newData.units[unitNum].mass = unitData.mass
					newData.units[unitNum].canCargo = unitData.canCargo
					newData.units[unitNum].shape_name = unitData.shape_name
				end
			end
			--log:info(newData)
			return newData
		else
			log:error('$1 not found in MIST database', gpName)
			return
		end
	end

	function mist.getPayload(unitIdent)
		-- refactor to search by groupId and allow groupId and groupName as inputs
		local unitId = unitIdent
		if type(unitIdent) == 'string' and not tonumber(unitIdent) then
			if mist.DBs.MEunitsByName[unitIdent] then
				unitId = mist.DBs.MEunitsByName[unitIdent].unitId
			else
				log:error("Unit not found in mist.DBs.MEunitsByName: $1", unitIdent)
			end
		end
		local gpId = mist.DBs.MEunitsById[unitId].groupId

		if gpId and unitId then
			for coa_name, coa_data in pairs(env.mission.coalition) do
				if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then
					if coa_data.country then --there is a country table
						for cntry_id, cntry_data in pairs(coa_data.country) do
							for obj_type_name, obj_type_data in pairs(cntry_data) do
								if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then	-- only these types have points
									if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then	--there's a group!
										for group_num, group_data in pairs(obj_type_data.group) do
											if group_data and group_data.groupId == gpId then
												for unitIndex, unitData in pairs(group_data.units) do --group index
													if unitData.unitId == unitId then
														return unitData.payload
													end
												end
											end
										end
									end
								end
							end
						end
					end
				end
			end
		else
			log:error('Need string or number. Got: $1', type(unitIdent))
			return false
		end
		log:warn("Couldn't find payload for unit: $1", unitIdent)
		return
	end

	function mist.getGroupPayload(groupIdent)
		local gpId = groupIdent
		if type(groupIdent) == 'string' and not tonumber(groupIdent) then
			if mist.DBs.MEgroupsByName[groupIdent] then
				gpId = mist.DBs.MEgroupsByName[groupIdent].groupId
			else
				log:error('$1 not found in mist.DBs.MEgroupsByName', groupIdent)
			end
		end

		if gpId then
			for coa_name, coa_data in pairs(env.mission.coalition) do
				if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then
					if coa_data.country then --there is a country table
						for cntry_id, cntry_data in pairs(coa_data.country) do
							for obj_type_name, obj_type_data in pairs(cntry_data) do
								if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then	-- only these types have points
									if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then	--there's a group!
										for group_num, group_data in pairs(obj_type_data.group) do
											if group_data and group_data.groupId == gpId then
												local payloads = {}
												for unitIndex, unitData in pairs(group_data.units) do --group index
													payloads[unitIndex] = unitData.payload
												end
												return payloads
											end
										end
									end
								end
							end
						end
					end
				end
			end
		else
			log:error('Need string or number. Got: $1', type(groupIdent))
			return false
		end
		log:warn("Couldn't find payload for group: $1", groupIdent)
		return

	end

	function mist.teleportToPoint(vars) -- main teleport function that all of teleport/respawn functions call
		local point = vars.point

		local gpName
		if vars.gpName then
			gpName = vars.gpName
		elseif vars.groupName then
			gpName = vars.groupName
		else
			log:error('Missing field groupName or gpName in variable table')
		end

		local action = vars.action

		local disperse = vars.disperse or false
		local maxDisp = vars.maxDisp
		if not vars.maxDisp then
			maxDisp = 200
		else
			maxDisp = vars.maxDisp
		end
		local radius = vars.radius or 0
		local innerRadius = vars.innerRadius

		local route = vars.route
		local dbData = false

		local newGroupData
		if gpName and not vars.groupData then
			if string.lower(action) == 'teleport' or string.lower(action) == 'tele' then
				newGroupData = mist.getCurrentGroupData(gpName)
			elseif string.lower(action) == 'respawn' then
				newGroupData = mist.getGroupData(gpName)
				dbData = true
			elseif string.lower(action) == 'clone' then
				newGroupData = mist.getGroupData(gpName)
				newGroupData.clone = 'order66'
				dbData = true
			else
				action = 'tele'
				newGroupData = mist.getCurrentGroupData(gpName)
			end
		else
			action = 'tele'
			newGroupData = vars.groupData
		end
		
		--log:info('get Randomized Point')
		local diff = {x = 0, y = 0}
		local newCoord, origCoord
		if point then
			local valid = false

			local validTerrain
			if string.lower(newGroupData.category) == 'ship' then
				validTerrain = {'SHALLOW_WATER' , 'WATER'}
			elseif string.lower(newGroupData.category) == 'vehicle' then
				validTerrain = {'LAND', 'ROAD'}
			else
				validTerrain = {'LAND', 'ROAD', 'SHALLOW_WATER', 'WATER', 'RUNWAY'}
			end

			for i = 1, 100	do
				newCoord = mist.getRandPointInCircle(point, radius, innerRadius)
				if mist.isTerrainValid(newCoord, validTerrain) then
					origCoord = mist.utils.deepCopy(newCoord)
					diff = {x = (newCoord.x - newGroupData.units[1].x), y = (newCoord.y - newGroupData.units[1].y)}
					valid = true
					break
				end
			end
			if valid == false then
				log:error('Point supplied in variable table is not a valid coordinate. Valid coords: $1', validTerrain)
				return false
			end
		end
		if not newGroupData.country and mist.DBs.groupsByName[newGroupData.groupName].country then
			newGroupData.country = mist.DBs.groupsByName[newGroupData.groupName].country
		end
		if not newGroupData.category and mist.DBs.groupsByName[newGroupData.groupName].category then
			newGroupData.category = mist.DBs.groupsByName[newGroupData.groupName].category
		end

		for unitNum, unitData in pairs(newGroupData.units) do
			if disperse then
				if maxDisp and type(maxDisp) == 'number' and unitNum ~= 1 then
					newCoord = mist.getRandPointInCircle(origCoord, maxDisp)
					--else
					--newCoord = mist.getRandPointInCircle(zone.point, zone.radius)
				end

				newGroupData.units[unitNum].x = newCoord.x
				newGroupData.units[unitNum].y = newCoord.y
			else
				newGroupData.units[unitNum].x = unitData.x + diff.x
				newGroupData.units[unitNum].y = unitData.y + diff.y
			end
			if point then
				if (newGroupData.category == 'plane' or newGroupData.category == 'helicopter')	then
					if point.z and point.y > 0 and point.y > land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + 10 then
						newGroupData.units[unitNum].alt = point.y
					else
						if newGroupData.category == 'plane' then
							newGroupData.units[unitNum].alt = land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + math.random(300, 9000)
						else
							newGroupData.units[unitNum].alt = land.getHeight({newGroupData.units[unitNum].x, newGroupData.units[unitNum].y}) + math.random(200, 3000)
						end
					end
				end
			end
		end

		if newGroupData.start_time then
			newGroupData.startTime = newGroupData.start_time
		end

		if newGroupData.startTime and newGroupData.startTime ~= 0 and dbData == true then
			local timeDif = timer.getAbsTime() - timer.getTime0()
			if timeDif > newGroupData.startTime then
				newGroupData.startTime = 0
			else
				newGroupData.startTime = newGroupData.startTime - timeDif
			end

		end

		if route then
			newGroupData.route = route
		end
		--mist.debug.writeData(mist.utils.serialize,{'teleportToPoint', newGroupData}, 'newGroupData.lua')
		if string.lower(newGroupData.category) == 'static' then
			--log:info(newGroupData)
			return mist.dynAddStatic(newGroupData)
		end
		return mist.dynAdd(newGroupData)

	end

	function mist.respawnInZone(gpName, zone, disperse, maxDisp)

		if type(gpName) == 'table' and gpName:getName() then
			gpName = gpName:getName()
		elseif type(gpName) == 'table' and gpName[1]:getName() then
			gpName = math.random(#gpName)
		else
			gpName = tostring(gpName)
		end

		if type(zone) == 'string' then
			zone = trigger.misc.getZone(zone)
		elseif type(zone) == 'table' and not zone.radius then
			zone = trigger.misc.getZone(zone[math.random(1, #zone)])
		end
		local vars = {}
		vars.gpName = gpName
		vars.action = 'respawn'
		vars.point = zone.point
		vars.radius = zone.radius
		vars.disperse = disperse
		vars.maxDisp = maxDisp
		return mist.teleportToPoint(vars)
	end

	function mist.cloneInZone(gpName, zone, disperse, maxDisp)
		--log:info('cloneInZone')
		if type(gpName) == 'table' then
			gpName = gpName:getName()
		else
			gpName = tostring(gpName)
		end

		if type(zone) == 'string' then
			zone = trigger.misc.getZone(zone)
		elseif type(zone) == 'table' and not zone.radius then
			zone = trigger.misc.getZone(zone[math.random(1, #zone)])
		end
		local vars = {}
		vars.gpName = gpName
		vars.action = 'clone'
		vars.point = zone.point
		vars.radius = zone.radius
		vars.disperse = disperse
		vars.maxDisp = maxDisp
		--log:info('do teleport')
		return mist.teleportToPoint(vars)
	end

	function mist.teleportInZone(gpName, zone, disperse, maxDisp) -- groupName, zoneName or table of Zone Names, keepForm is a boolean
		if type(gpName) == 'table' and gpName:getName() then
			gpName = gpName:getName()
		else
			gpName = tostring(gpName)
		end

		if type(zone) == 'string' then
			zone = trigger.misc.getZone(zone)
		elseif type(zone) == 'table' and not zone.radius then
			zone = trigger.misc.getZone(zone[math.random(1, #zone)])
		end

		local vars = {}
		vars.gpName = gpName
		vars.action = 'tele'
		vars.point = zone.point
		vars.radius = zone.radius
		vars.disperse = disperse
		vars.maxDisp = maxDisp
		return mist.teleportToPoint(vars)
	end

	function mist.respawnGroup(gpName, task)
		local vars = {}
		vars.gpName = gpName
		vars.action = 'respawn'
		if task and type(task) ~= 'number' then
			vars.route = mist.getGroupRoute(gpName, 'task')
		end
		local newGroup = mist.teleportToPoint(vars)
		if task and type(task) == 'number' then
			local newRoute = mist.getGroupRoute(gpName, 'task')
			mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task)
		end
		return newGroup
	end

	function mist.cloneGroup(gpName, task)
		local vars = {}
		vars.gpName = gpName
		vars.action = 'clone'
		if task and type(task) ~= 'number' then
			vars.route = mist.getGroupRoute(gpName, 'task')
		end
		local newGroup = mist.teleportToPoint(vars)
		if task and type(task) == 'number' then
			local newRoute = mist.getGroupRoute(gpName, 'task')
			mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task)
		end
		return newGroup
	end

	function mist.teleportGroup(gpName, task)
		local vars = {}
		vars.gpName = gpName
		vars.action = 'teleport'
		if task and type(task) ~= 'number' then
			vars.route = mist.getGroupRoute(gpName, 'task')
		end
		local newGroup = mist.teleportToPoint(vars)
		if task and type(task) == 'number' then
			local newRoute = mist.getGroupRoute(gpName, 'task')
			mist.scheduleFunction(mist.goRoute, {newGroup, newRoute}, timer.getTime() + task)
		end
		return newGroup
	end

	function mist.spawnRandomizedGroup(groupName, vars) -- need to debug
		if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then
			local gpData = mist.getGroupData(groupName)
			gpData.units = mist.randomizeGroupOrder(gpData.units, vars)
			gpData.route = mist.getGroupRoute(groupName, 'task')

			mist.dynAdd(gpData)
		end

		return true
	end

	function mist.randomizeNumTable(vars)
		local newTable = {}

		local excludeIndex = {}
		local randomTable = {}

		if vars and vars.exclude and type(vars.exclude) == 'table' then
			for index, data in pairs(vars.exclude) do
				excludeIndex[data] = true
			end
		end

		local low, hi, size

		if vars.size then
			size = vars.size
		end

		if vars and vars.lowerLimit and type(vars.lowerLimit) == 'number' then
			low = mist.utils.round(vars.lowerLimit)
		else
			low = 1
		end

		if vars and vars.upperLimit and type(vars.upperLimit) == 'number' then
			hi = mist.utils.round(vars.upperLimit)
		else
			hi = size
		end

		local choices = {}
		-- add to exclude list and create list of what to randomize
		for i = 1, size do
			if not (i >= low and i <= hi) then

				excludeIndex[i] = true
			end
			if not excludeIndex[i] then
				table.insert(choices, i)
			else
				newTable[i] = i
			end
		end

		for ind, num in pairs(choices) do
			local found = false
			local x = 0
			while found == false do
				x = mist.random(size) -- get random number from list
				local addNew = true
				for index, _ in pairs(excludeIndex) do
					if index == x then
						addNew = false
						break
					end
				end
				if addNew == true then
					excludeIndex[x] = true
					found = true
				end
				excludeIndex[x] = true

			end
			newTable[num] = x
		end
		--[[
		for i = 1, #newTable do
			log:info(newTable[i])
		end
		]]
		return newTable
	end

	function mist.randomizeGroupOrder(passedUnits, vars)
		-- figure out what to exclude, and send data to other func
		local units = passedUnits

		if passedUnits.units then
			units = passUnits.units
		end

		local exclude = {}
		local excludeNum = {}
		if vars and vars.excludeType and type(vars.excludeType) == 'table' then
			exclude = vars.excludeType
		end

		if vars and vars.excludeNum and type(vars.excludeNum) == 'table' then
			excludeNum = vars.excludeNum
		end

		local low, hi

		if vars and vars.lowerLimit and type(vars.lowerLimit) == 'number' then
			low = mist.utils.round(vars.lowerLimit)
		else
			low = 1
		end

		if vars and vars.upperLimit and type(vars.upperLimit) == 'number' then
			hi = mist.utils.round(vars.upperLimit)
		else
			hi = #units
		end


		local excludeNum = {}
		for unitIndex, unitData in pairs(units) do
			if unitIndex >= low and unitIndex	<= hi then -- if within range
				local found = false
				if #exclude > 0 then
					for excludeType, index in pairs(exclude) do -- check if excluded
						if mist.stringMatch(excludeType, unitData.type) then -- if excluded
							excludeNum[unitIndex] = unitIndex
							found = true
						end
					end
				end
			else -- unitIndex is either to low, or to high: added to exclude list
				excludeNum[unitIndex] = unitId
			end
		end

		local newGroup = {}
		local newOrder = mist.randomizeNumTable({exclude = excludeNum, size = #units})

		for unitIndex, unitData in pairs(units) do
			for i = 1, #newOrder do
				if newOrder[i] == unitIndex then
					newGroup[i] = mist.utils.deepCopy(units[i]) -- gets all of the unit data
					newGroup[i].type = mist.utils.deepCopy(unitData.type)
					newGroup[i].skill = mist.utils.deepCopy(unitData.skill)
					newGroup[i].unitName = mist.utils.deepCopy(unitData.unitName)
					newGroup[i].unitIndex = mist.utils.deepCopy(unitData.unitIndex) -- replaces the units data with a new type
				end
			end
		end
		return newGroup
	end

	function mist.random(firstNum, secondNum) -- no support for decimals
		local lowNum, highNum
		if not secondNum then
			highNum = firstNum
			lowNum = 1
		else
			lowNum = firstNum
			highNum = secondNum
		end
		local total = 1
		if math.abs(highNum - lowNum + 1) < 50 then -- if total values is less than 50
			total = math.modf(50/math.abs(highNum - lowNum + 1)) -- make x copies required to be above 50
		end
		local choices = {}
		for i = 1, total do -- iterate required number of times
			for x = lowNum, highNum do -- iterate between the range
				choices[#choices +1] = x -- add each entry to a table
			end
		end
		local rtnVal = math.random(#choices) -- will now do a math.random of at least 50 choices
		for i = 1, 10 do
			rtnVal = math.random(#choices) -- iterate a few times for giggles
		end
		return choices[rtnVal]
	end

	function mist.stringMatch(s1, s2, bool)
		local exclude = {'%-', '%(', '%)', '%_', '%[', '%]', '%.', '%#', '% ', '%{', '%}', '%$', '%%', '%?', '%+', '%^'}
		if type(s1) == 'string' and type(s2) == 'string' then
			for i , str in pairs(exclude) do
				s1 = string.gsub(s1, str, '')
				s2 = string.gsub(s2, str, '')
			end
			if not bool then
				s1 = string.lower(s1)
				s2 = string.lower(s2)
			end
			log:info('Comparing: $1 and $2', s1, s2)
			if s1 == s2 then
				return true
			else
				return false
			end
		else
			log:error('Either the first or second variable were not a string')
			return false
		end
	end

	mist.matchString = mist.stringMatch -- both commands work because order out type of I

	--[[ scope:
{
	units = {...},	-- unit names.
	coa = {...}, -- coa names
	countries = {...}, -- country names
	CA = {...}, -- looks just like coa.
	unitTypes = { red = {}, blue = {}, all = {}, Russia = {},}
}


scope examples:

{	units = { 'Hawg11', 'Hawg12' }, CA = {'blue'} }

{ countries = {'Georgia'}, unitTypes = {blue = {'A-10C', 'A-10A'}}}

{ coa = {'all'}}

{unitTypes = { blue = {'A-10C'}}}
]]
end

--- Utility functions.
-- E.g. conversions between units etc.
-- @section mist.utils
do -- mist.util scope
	mist.utils = {}

	--- Converts angle in radians to degrees.
	-- @param angle angle in radians
	-- @return angle in degrees
	function mist.utils.toDegree(angle)
		return angle*180/math.pi
	end

	--- Converts angle in degrees to radians.
	-- @param angle angle in degrees
	-- @return angle in degrees
	function mist.utils.toRadian(angle)
		return angle*math.pi/180
	end

	--- Converts meters to nautical miles.
	-- @param meters distance in meters
	-- @return distance in nautical miles
	function mist.utils.metersToNM(meters)
		return meters/1852
	end

	--- Converts meters to feet.
	-- @param meters distance in meters
	-- @return distance in feet
	function mist.utils.metersToFeet(meters)
		return meters/0.3048
	end

	--- Converts nautical miles to meters.
	-- @param nm distance in nautical miles
	-- @return distance in meters
	function mist.utils.NMToMeters(nm)
		return nm*1852
	end

	--- Converts feet to meters.
	-- @param feet distance in feet
	-- @return distance in meters
	function mist.utils.feetToMeters(feet)
		return feet*0.3048
	end

	--- Converts meters per second to knots.
	-- @param mps speed in m/s
	-- @return speed in knots
	function mist.utils.mpsToKnots(mps)
		return mps*3600/1852
	end

	--- Converts meters per second to kilometers per hour.
	-- @param mps speed in m/s
	-- @return speed in km/h
	function mist.utils.mpsToKmph(mps)
		return mps*3.6
	end

	--- Converts knots to meters per second.
	-- @param knots speed in knots
	-- @return speed in m/s
	function mist.utils.knotsToMps(knots)
		return knots*1852/3600
	end

	--- Converts kilometers per hour to meters per second.
	-- @param kmph speed in km/h
	-- @return speed in m/s
	function mist.utils.kmphToMps(kmph)
		return kmph/3.6
	end

	--- Converts a Vec3 to a Vec2.
	-- @tparam Vec3 vec the 3D vector
	-- @return vector converted to Vec2
	function 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

	--- Converts a Vec2 to a Vec3.
	-- @tparam Vec2 vec the 2D vector
	-- @param y optional new y axis (altitude) value. If omitted it's 0.
	function 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

	--- Converts a Vec2 to a Vec3 using ground level as altitude.
	-- The ground level at the specific point is used as altitude (y-axis)
	-- for the new vector. Optionally a offset can be specified.
	-- @tparam Vec2 vec the 2D vector
	-- @param[opt] offset offset to be applied to the ground level
	-- @return new 3D vector
	function 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

	--- Returns the center of a zone as Vec3.
	-- @tparam string|table zone trigger zone name or table
	-- @treturn Vec3 center of the zone
	function mist.utils.zoneToVec3(zone)
		local new = {}
		if type(zone) == 'table' then
			if zone.point then
				new.x = zone.point.x
				new.y = zone.point.y
				new.z = zone.point.z
			elseif zone.x and zone.y and zone.z then
				return zone
			end
			return new
		elseif type(zone) == 'string' then
			zone = trigger.misc.getZone(zone)
			if zone then
				new.x = zone.point.x
				new.y = zone.point.y
				new.z = zone.point.z
				return new
			end
		end
	end

	--- Returns heading-error corrected direction.
	-- True-north corrected direction from point along vector vec.
	-- @tparam Vec3 vec
	-- @tparam Vec2 point
	-- @return heading-error corrected direction from point.
	function mist.utils.getDir(vec, point)
		local dir = math.atan2(vec.z, vec.x)
		if point then
			dir = dir + 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

	--- Returns distance in meters between two points.
	-- @tparam Vec2|Vec3 point1 first point
	-- @tparam Vec2|Vec3 point2 second point
	-- @treturn number distance between given points.
	function mist.utils.get2DDist(point1, point2)
		point1 = mist.utils.makeVec3(point1)
		point2 = mist.utils.makeVec3(point2)
		return mist.vec.mag({x = point1.x - point2.x, y = 0, z = point1.z - point2.z})
	end

	--- Returns distance in meters between two points in 3D space.
	-- @tparam Vec3 point1 first point
	-- @tparam Vec3 point2 second point
	-- @treturn number distancen between given points in 3D space.
	function mist.utils.get3DDist(point1, point2)
		return mist.vec.mag({x = point1.x - point2.x, y = point1.y - point2.y, z = point1.z - point2.z})
	end

	--- Creates a waypoint from a vector.
	-- @tparam Vec2|Vec3 vec position of the new waypoint
	-- @treturn Waypoint a new waypoint to be used inside paths.
	function mist.utils.vecToWP(vec)
		local newWP = {}
		newWP.x = vec.x
		newWP.y = vec.y
		if vec.z then
			newWP.alt = vec.y
			newWP.y = vec.z
		else
			newWP.alt = land.getHeight({x = vec.x, y = vec.y})
		end
		return newWP
	end

	--- Creates a waypoint from a unit.
	-- This function also considers the units speed.
	-- The alt_type of this waypoint is set to "BARO".
	-- @tparam Unit pUnit Unit whose position and speed will be used.
	-- @treturn Waypoint new waypoint.
	function mist.utils.unitToWP(pUnit)
		local unit = mist.utils.deepCopy(pUnit)
		if type(unit) == 'string' then
			if Unit.getByName(unit) then
				unit = Unit.getByName(unit)
			end
		end
		if unit:isExist() == true then
			local new = mist.utils.vecToWP(unit:getPosition().p)
			new.speed = mist.vec.mag(unit:getVelocity())
			new.alt_type = "BARO"

			return new
		end
		log:error("$1 not found or doesn't exist", pUnit)
		return false
	end

	--- Creates a deep copy of a object.
	-- Usually this object is a table.
	-- See also: from http://lua-users.org/wiki/CopyTable
	-- @param object object to copy
	-- @return copy of object
	function 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

	--- Simple rounding function.
	-- From http://lua-users.org/wiki/SimpleRound
	-- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place
	-- @tparam number num number to round
	-- @param idp
	function mist.utils.round(num, idp)
		local mult = 10^(idp or 0)
		return math.floor(num * mult + 0.5) / mult
	end

	--- Rounds all numbers inside a table.
	-- @tparam table tbl table in which to round numbers
	-- @param idp
	function mist.utils.roundTbl(tbl, idp)
		for id, val in pairs(tbl) do
			if type(val) == 'number' then
				tbl[id] = mist.utils.round(val, idp)
			end
		end
		return tbl
	end

	--- Executes the given string.
	-- borrowed from Slmod
	-- @tparam string s string containing LUA code.
	-- @treturn boolean true if successfully executed, false otherwise
	function mist.utils.dostring(s)
		local f, err = loadstring(s)
		if f then
			return true, f()
		else
			return false, err
		end
	end

	--- Checks a table's types.
	-- This function checks a tables types against a specifically forged type table.
	-- @param fname
	-- @tparam table type_tbl
	-- @tparam table var_tbl
	-- @usage -- specifically forged type table
	-- type_tbl = {
	--						 {'table', 'number'},
	--						 'string',
	--						 'number',
	--						 'number',
	--						 {'string','nil'},
	--						 {'number', 'nil'}
	--					 }
	-- -- my_tbl index 1 must be a table or a number;
	-- -- index 2, a string; index 3, a number;
	-- -- index 4, a number; index 5, either a string or nil;
	-- -- and index 6, either a number or nil.
	-- mist.utils.typeCheck(type_tbl, my_tb)
	-- @return true if table passes the check, false otherwise.
	function mist.utils.typeCheck(fname, type_tbl, var_tbl)
		-- log:info('type check')
		for type_key, type_val in pairs(type_tbl) do
			-- log:info('type_key: $1 type_val: $2', type_key, type_val)

			--type_key can be a table of accepted keys- so try to find one that is not nil
			local type_key_str = ''
			local act_key = type_key -- actual key within var_tbl - necessary to use for multiple possible key variables.	Initialize to type_key
			if type(type_key) == 'table' then

				for i = 1, #type_key do
					if i ~= 1 then
						type_key_str = type_key_str .. '/'
					end
					type_key_str = type_key_str .. tostring(type_key[i])
					if var_tbl[type_key[i]] ~= nil then
						act_key = type_key[i]	-- found a non-nil entry, make act_key now this val.
					end
				end
			else
				type_key_str = tostring(type_key)
			end

			local err_msg = 'Error in function ' .. fname .. ', parameter "' .. type_key_str .. '", expected: '
			local passed_check = false

			if type(type_tbl[type_key]) == 'table' then
				-- log:info('err_msg, before: $1', err_msg)
				for j = 1, #type_tbl[type_key] do

					if j == 1 then
						err_msg = err_msg .. type_tbl[type_key][j]
					else
						err_msg = err_msg .. ' or ' .. type_tbl[type_key][j]
					end

					if type(var_tbl[act_key]) == type_tbl[type_key][j] then
						passed_check = true
					end
				end
				-- log:info('err_msg, after: $1', err_msg)
			else
				-- log:info('err_msg, before: $1', err_msg)
				err_msg = err_msg .. type_tbl[type_key]
				-- log:info('err_msg, after: $1', err_msg)
				if type(var_tbl[act_key]) == type_tbl[type_key] then
					passed_check = true
				end

			end

			if not passed_check then
				err_msg = err_msg .. ', got ' .. type(var_tbl[act_key])
				return false, err_msg
			end
		end
		return true
	end

	--- Serializes the give variable to a string.
	-- borrowed from slmod
	-- @param var variable to serialize
	-- @treturn string variable serialized to string
	function 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

--- Serialize value
-- borrowed from slmod (serialize_slmod)
-- @param name
-- @param value value to serialize
-- @param level
function mist.utils.serialize(name, value, level)
	--Based on ED's serialize_simple2
	local function basicSerialize(o)
		if type(o) == "number" then
			return tostring(o)
		elseif type(o) == "boolean" then
			return tostring(o)
		else -- assume it is a string
			return mist.utils.basicSerialize(o)
		end
	end

	local function serializeToTbl(name, value, level)
		local var_str_tbl = {}
		if level == nil then level = "" end
		if level ~= "" then level = level.."	" end

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

		if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then
			table.insert(var_str_tbl, basicSerialize(value) ..	",\n")
		elseif type(value) == "table" then
			table.insert(var_str_tbl, "\n"..level.."{\n")

			for k,v in pairs(value) do -- serialize its fields
				local key
				if type(k) == "number" then
					key = string.format("[%s]", k)
				else
					key = string.format("[%q]", k)
				end

				table.insert(var_str_tbl, mist.utils.serialize(key, v, level.."	"))

			end
			if level == "" then
				table.insert(var_str_tbl, level.."} -- end of "..name.."\n")

			else
				table.insert(var_str_tbl, level.."}, -- end of "..name.."\n")

			end
		else
			log:error('Cannot serialize a $1', type(value))
		end
		return var_str_tbl
	end

	local t_str = serializeToTbl(name, value, level)

	return table.concat(t_str)
end

--- Serialize value supporting cycles.
-- borrowed from slmod (serialize_wcycles)
-- @param name
-- @param value value to serialize
-- @param saved
function mist.utils.serializeWithCycles(name, value, saved)
	--mostly straight out of Programming in Lua
	local function basicSerialize(o)
		if type(o) == "number" then
			return tostring(o)
		elseif type(o) == "boolean" then
			return tostring(o)
		else -- assume it is a string
			return mist.utils.basicSerialize(o)
		end
	end

	local t_str = {}
	saved = saved or {}			 -- initial value
	if ((type(value) == 'string') or (type(value) == 'number') or (type(value) == 'table') or (type(value) == 'boolean')) then
		table.insert(t_str, name .. " = ")
		if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then
			table.insert(t_str, basicSerialize(value) ..	"\n")
		else

			if saved[value] then		-- value already saved?
				table.insert(t_str, saved[value] .. "\n")
			else
				saved[value] = name	 -- save name for next time
				table.insert(t_str, "{}\n")
				for k,v in pairs(value) do			-- save its fields
					local fieldname = string.format("%s[%s]", name, basicSerialize(k))
					table.insert(t_str, mist.utils.serializeWithCycles(fieldname, v, saved))
				end
			end
		end
		return table.concat(t_str)
	else
		return ""
	end
end

--- Serialize a table to a single line string.
-- serialization of a table all on a single line, no comments, made to replace old get_table_string function
-- borrowed from slmod
-- @tparam table tbl table to serialize.
-- @treturn string string containing serialized table
function mist.utils.oneLineSerialize(tbl)
	if type(tbl) == 'table' then --function only works for tables!

		local tbl_str = {}

		tbl_str[#tbl_str + 1] = '{ '

		for ind,val in pairs(tbl) do -- serialize its fields
			if type(ind) == "number" then
				tbl_str[#tbl_str + 1] = '['
				tbl_str[#tbl_str + 1] = tostring(ind)
				tbl_str[#tbl_str + 1] = '] = '
			else --must be a string
				tbl_str[#tbl_str + 1] = '['
				tbl_str[#tbl_str + 1] = 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] = ', '
			elseif type(val) == 'string' then
				tbl_str[#tbl_str + 1] = mist.utils.basicSerialize(val)
				tbl_str[#tbl_str + 1] = ', '
			elseif type(val) == 'nil' then -- won't ever happen, right?
				tbl_str[#tbl_str + 1] = 'nil, '
			elseif type(val) == 'table' then
				tbl_str[#tbl_str + 1] = mist.utils.oneLineSerialize(val)
				tbl_str[#tbl_str + 1] = ', '	 --I think this is right, I just added it
			else
				log:war('Unable to serialize value type $1 at index $2', mist.utils.basicSerialize(type(val)), tostring(ind))
			end

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

--- Returns table in a easy readable string representation.
-- this function is not meant for serialization because it uses
-- newlines for better readability.
-- @param tbl table to show
-- @param loc
-- @param indent
-- @param tableshow_tbls
-- @return human readable string representation of given table
function 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 -- serialize its fields
			if type(ind) == "number" then
				tbl_str[#tbl_str + 1] = indent
				tbl_str[#tbl_str + 1] = loc .. '['
				tbl_str[#tbl_str + 1] = tostring(ind)
				tbl_str[#tbl_str + 1] = '] = '
			else
				tbl_str[#tbl_str + 1] = indent
				tbl_str[#tbl_str + 1] = loc .. '['
				tbl_str[#tbl_str + 1] = 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] = 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 ..	'[' .. mist.utils.basicSerialize(ind) .. ']'
					tbl_str[#tbl_str + 1] = tostring(val) .. ' '
					tbl_str[#tbl_str + 1] = mist.utils.tableShow(val,	loc .. '[' .. 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 ' .. mist.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)
			end
		end

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

--- Debug functions
-- @section mist.debug
do -- mist.debug scope
	mist.debug = {}

	--- Dumps the global table _G.
	-- This dumps the global table _G to a file in
	-- the DCS\Logs directory.
	-- This function requires you to disable script sanitization
	-- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io
	-- libraries.
	-- @param fname
	function mist.debug.dump_G(fname)
		if lfs and io then
			local fdir = lfs.writedir() .. [[Logs\]] .. fname
			local f = io.open(fdir, 'w')
			f:write(mist.utils.tableShow(_G))
			f:close()
			log:info('Wrote debug data to $1', fdir)
			--trigger.action.outText(errmsg, 10)
		else
			log:alert('insufficient libraries to run mist.debug.dump_G, you must disable the sanitization of the io and lfs libraries in ./Scripts/MissionScripting.lua')
			--trigger.action.outText(errmsg, 10)
		end
	end

	--- Write debug data to file.
	-- This function requires you to disable script sanitization
	-- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io
	-- libraries.
	-- @param fcn
	-- @param fcnVars
	-- @param fname
	function mist.debug.writeData(fcn, fcnVars, fname)
		if lfs and io then
			local fdir = lfs.writedir() .. [[Logs\]] .. fname
			local f = io.open(fdir, 'w')
			f:write(fcn(unpack(fcnVars, 1, table.maxn(fcnVars))))
			f:close()
			log:info('Wrote debug data to $1', fdir)
			local errmsg = 'mist.debug.writeData wrote data to ' .. fdir
			trigger.action.outText(errmsg, 10)
		else
			local errmsg = 'Error: insufficient libraries to run mist.debug.writeData, you must disable the sanitization of the io and lfs libraries in ./Scripts/MissionScripting.lua'
			log:alert(errmsg)
			trigger.action.outText(errmsg, 10)
		end
	end

	--- Write mist databases to file.
	-- This function requires you to disable script sanitization
	-- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io
	-- libraries.
	function mist.debug.dumpDBs()
		for DBname, DB in pairs(mist.DBs) do
			if type(DB) == 'table' and type(DBname) == 'string' then
				mist.debug.writeData(mist.utils.serialize, {DBname, DB}, 'mist_DBs_' .. DBname .. '.lua')
			end
		end
	end
end

--- 3D Vector functions
-- @section mist.vec
do -- mist.vec scope
	mist.vec = {}

	--- Vector addition.
	-- @tparam Vec3 vec1 first vector
	-- @tparam Vec3 vec2 second vector
	-- @treturn Vec3 new vector, sum of vec1 and vec2.
	function mist.vec.add(vec1, vec2)
		return {x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z}
	end

	--- Vector substraction.
	-- @tparam Vec3 vec1 first vector
	-- @tparam Vec3 vec2 second vector
	-- @treturn Vec3 new vector, vec2 substracted from vec1.
	function mist.vec.sub(vec1, vec2)
		return {x = vec1.x - vec2.x, y = vec1.y - vec2.y, z = vec1.z - vec2.z}
	end

	--- Vector scalar multiplication.
	-- @tparam Vec3 vec vector to multiply
	-- @tparam number mult scalar multiplicator
	-- @treturn Vec3 new vector multiplied with the given scalar
	function mist.vec.scalarMult(vec, mult)
		return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult}
	end

	mist.vec.scalar_mult = mist.vec.scalarMult

	--- Vector dot product.
	-- @tparam Vec3 vec1 first vector
	-- @tparam Vec3 vec2 second vector
	-- @treturn number dot product of given vectors
	function mist.vec.dp (vec1, vec2)
		return vec1.x*vec2.x + vec1.y*vec2.y + vec1.z*vec2.z
	end

	--- Vector cross product.
	-- @tparam Vec3 vec1 first vector
	-- @tparam Vec3 vec2 second vector
	-- @treturn Vec3 new vector, cross product of vec1 and vec2.
	function 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

	--- Vector magnitude
	-- @tparam Vec3 vec vector
	-- @treturn number magnitude of vector vec
	function mist.vec.mag(vec)
		return (vec.x^2 + vec.y^2 + vec.z^2)^0.5
	end

	--- Unit vector
	-- @tparam Vec3 vec
	-- @treturn Vec3 unit vector of vec
	function mist.vec.getUnitVec(vec)
		local mag = mist.vec.mag(vec)
		return { x = vec.x/mag, y = vec.y/mag, z = vec.z/mag }
	end

	--- Rotate vector.
	-- @tparam Vec2 vec2 to rotoate
	-- @tparam number theta
	-- @return Vec2 rotated vector.
	function 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
end

--- Flag functions.
-- The mist "Flag functions" are functions that are similar to Slmod functions
-- that detect a game condition and set a flag when that game condition is met.
--
-- They are intended to be used by persons with little or no experience in Lua
-- programming, but with a good knowledge of the DCS mission editor.
-- @section mist.flagFunc
do -- mist.flagFunc scope
	mist.flagFunc = {}

	--- Sets a flag if map objects are destroyed inside a zone.
	-- Once this function is run, it will start a continuously evaluated process
	-- that will set a flag true if map objects (such as bridges, buildings in
	-- town, etc.) die (or have died) in a mission editor zone (or set of zones).
	-- This will only happen once; once the flag is set true, the process ends.
	-- @usage
	-- -- Example vars table
	-- vars = {
	--	 zones = { "zone1", "zone2" }, -- can also be a single string
	--	 flag = 3, -- number of the flag
	--	 stopflag = 4, -- optional number of the stop flag
	--	 req_num = 10, -- optional minimum amount of map objects needed to die
	-- }
	-- mist.flagFuncs.mapobjs_dead_zones(vars)
	-- @tparam table vars table containing parameters.
	function mist.flagFunc.mapobjs_dead_zones(vars)
		--[[vars needs to be:
zones = table or string,
flag = number,
stopflag = number or nil,
req_num = number or nil

AND used by function,
initial_number

]]
		-- type_tbl
		local type_tbl = {
			[{'zones', 'zone'}] = {'table', 'string'},
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			[{'req_num', 'reqnum'}] = {'number', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.mapobjs_dead_zones', type_tbl, vars)
		assert(err, errmsg)
		local zones = vars.zones or vars.zone
		local flag = vars.flag
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local req_num = vars.req_num or vars.reqnum or 1
		local initial_number = vars.initial_number

		if type(zones) == 'string' then
			zones = {zones}
		end

		if not initial_number then
			initial_number = #mist.getDeadMapObjsInZones(zones)
		end

		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			if (#mist.getDeadMapObjsInZones(zones) - initial_number) >= req_num and trigger.misc.getUserFlag(flag) == 0 then
				trigger.action.setUserFlag(flag, true)
				return
			else
				mist.scheduleFunction(mist.flagFunc.mapobjs_dead_zones, {{zones = zones, flag = flag, stopflag = stopflag, req_num = req_num, initial_number = initial_number}}, timer.getTime() + 1)
			end
		end
	end

	--- Sets a flag if map objects are destroyed inside a polygon.
	-- Once this function is run, it will start a continuously evaluated process
	-- that will set a flag true if map objects (such as bridges, buildings in
	-- town, etc.) die (or have died) in a polygon.
	-- This will only happen once; once the flag is set true, the process ends.
	-- @usage
	-- -- Example vars table
	-- vars = {
	--	 zone = {
	--		 [1] = mist.DBs.unitsByName['NE corner'].point,
	--		 [2] = mist.DBs.unitsByName['SE corner'].point,
	--		 [3] = mist.DBs.unitsByName['SW corner'].point,
	--		 [4] = mist.DBs.unitsByName['NW corner'].point
	--	 }
	--	 flag = 3, -- number of the flag
	--	 stopflag = 4, -- optional number of the stop flag
	--	 req_num = 10, -- optional minimum amount of map objects needed to die
	-- }
	-- mist.flagFuncs.mapobjs_dead_zones(vars)
	-- @tparam table vars table containing parameters.
	function mist.flagFunc.mapobjs_dead_polygon(vars)
		--[[vars needs to be:
zone = table,
flag = number,
stopflag = number or nil,
req_num = number or nil

AND used by function,
initial_number

]]
		-- type_tbl
		local type_tbl = {
			[{'zone', 'polyzone'}] = 'table',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			[{'req_num', 'reqnum'}] = {'number', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.mapobjs_dead_polygon', type_tbl, vars)
		assert(err, errmsg)
		local zone = vars.zone or vars.polyzone
		local flag = vars.flag
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local req_num = vars.req_num or vars.reqnum or 1
		local initial_number = vars.initial_number

		if not initial_number then
			initial_number = #mist.getDeadMapObjsInPolygonZone(zone)
		end

		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			if (#mist.getDeadMapObjsInPolygonZone(zone) - initial_number) >= req_num and trigger.misc.getUserFlag(flag) == 0 then
				trigger.action.setUserFlag(flag, true)
				return
			else
				mist.scheduleFunction(mist.flagFunc.mapobjs_dead_polygon, {{zone = zone, flag = flag, stopflag = stopflag, req_num = req_num, initial_number = initial_number}}, timer.getTime() + 1)
			end
		end
	end

	--- Sets a flag if unit(s) is/are inside a polygon.
	-- @tparam table vars @{unitsInPolygonVars}
	-- @usage -- set flag 11 to true as soon as any blue vehicles
	-- -- are inside the polygon shape created off of the waypoints
	-- -- of the group forest1
	-- mist.flagFunc.units_in_polygon {
	--		units = {'[blue][vehicle]'},
	--		zone = mist.getGroupPoints('forest1'),
	--		flag = 11
	-- }
	function mist.flagFunc.units_in_polygon(vars)
		--[[vars needs to be:
units = table,
zone = table,
flag = number,
stopflag = number or nil,
maxalt = number or nil,
interval	= number or nil,
req_num = number or nil
toggle = boolean or nil
unitTableDef = table or nil
]]
		-- type_tbl
		local type_tbl = {
			[{'units', 'unit'}] = 'table',
			[{'zone', 'polyzone'}] = 'table',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			[{'maxalt', 'alt'}] = {'number', 'nil'},
			interval = {'number', 'nil'},
			[{'req_num', 'reqnum'}] = {'number', 'nil'},
			toggle = {'boolean', 'nil'},
			unitTableDef = {'table', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_polygon', type_tbl, vars)
		assert(err, errmsg)
		local units = vars.units or vars.unit
		local zone = vars.zone or vars.polyzone
		local flag = vars.flag
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local interval = vars.interval or 1
		local maxalt = vars.maxalt or vars.alt
		local req_num = vars.req_num or vars.reqnum or 1
		local toggle = vars.toggle or nil
		local unitTableDef = vars.unitTableDef

		if not units.processed then
			unitTableDef = mist.utils.deepCopy(units)
		end

		if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts
			if unitTableDef then
				units = mist.makeUnitTable(unitTableDef)
			end
		end

		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == 0) then
			local num_in_zone = 0
			for i = 1, #units do
				local unit = Unit.getByName(units[i])
				if unit then
					local pos = unit:getPosition().p
					if mist.pointInPolygon(pos, zone, maxalt) then
						num_in_zone = num_in_zone + 1
						if num_in_zone >= req_num and trigger.misc.getUserFlag(flag) == 0 then
							trigger.action.setUserFlag(flag, true)
							break
						end
					end
				end
			end
			if toggle and (num_in_zone < req_num) and trigger.misc.getUserFlag(flag) > 0 then
				trigger.action.setUserFlag(flag, false)
			end
			-- do another check in case stopflag was set true by this function
			if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == 0) then
				mist.scheduleFunction(mist.flagFunc.units_in_polygon, {{units = units, zone = zone, flag = flag, stopflag = stopflag, interval = interval, req_num = req_num, maxalt = maxalt, toggle = toggle, unitTableDef = unitTableDef}}, timer.getTime() + interval)
			end
		end

	end

	--- Sets a flag if unit(s) is/are inside a trigger zone.
	-- @todo document
	function mist.flagFunc.units_in_zones(vars)
		--[[vars needs to be:
	units = table,
	zones = table,
	flag = number,
	stopflag = number or nil,
	zone_type = string or nil,
	req_num = number or nil,
	interval	= number or nil
	toggle = boolean or nil
	]]
		-- type_tbl
		local type_tbl = {
			units = 'table',
			zones = 'table',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			[{'zone_type', 'zonetype'}] = {'string', 'nil'},
			[{'req_num', 'reqnum'}] = {'number', 'nil'},
			interval = {'number', 'nil'},
			toggle = {'boolean', 'nil'},
			unitTableDef = {'table', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_zones', type_tbl, vars)
		assert(err, errmsg)
		local units = vars.units
		local zones = vars.zones
		local flag = vars.flag
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local zone_type = vars.zone_type or vars.zonetype or 'cylinder'
		local req_num = vars.req_num or vars.reqnum or 1
		local interval = vars.interval or 1
		local toggle = vars.toggle or nil
		local unitTableDef = vars.unitTableDef

		if not units.processed then
			unitTableDef = mist.utils.deepCopy(units)
		end
		
		if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts
			if unitTableDef then
				units = mist.makeUnitTable(unitTableDef)
			end
		end

		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then

			local in_zone_units = mist.getUnitsInZones(units, zones, zone_type)

			if #in_zone_units >= req_num and trigger.misc.getUserFlag(flag) == 0 then
				trigger.action.setUserFlag(flag, true)
			elseif #in_zone_units < req_num and toggle then
				trigger.action.setUserFlag(flag, false)
			end
			-- do another check in case stopflag was set true by this function
			if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
				mist.scheduleFunction(mist.flagFunc.units_in_zones, {{units = units, zones = zones, flag = flag, stopflag = stopflag, zone_type = zone_type, req_num = req_num, interval = interval, toggle = toggle, unitTableDef = unitTableDef}}, timer.getTime() + interval)
			end
		end

	end

	--- Sets a flag if unit(s) is/are inside a moving zone.
	-- @todo document
	function mist.flagFunc.units_in_moving_zones(vars)
		--[[vars needs to be:
	units = table,
	zone_units = table,
	radius = number,
	flag = number,
	stopflag = number or nil,
	zone_type = string or nil,
	req_num = number or nil,
	interval	= number or nil
	toggle = boolean or nil
	]]
		-- type_tbl
		local type_tbl = {
			units = 'table',
			[{'zone_units', 'zoneunits'}]	= 'table',
			radius = 'number',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			[{'zone_type', 'zonetype'}] = {'string', 'nil'},
			[{'req_num', 'reqnum'}] = {'number', 'nil'},
			interval = {'number', 'nil'},
			toggle = {'boolean', 'nil'},
			unitTableDef = {'table', 'nil'},
			zUnitTableDef = {'table', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_in_moving_zones', type_tbl, vars)
		assert(err, errmsg)
		local units = vars.units
		local zone_units = vars.zone_units or vars.zoneunits
		local radius = vars.radius
		local flag = vars.flag
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local zone_type = vars.zone_type or vars.zonetype or 'cylinder'
		local req_num = vars.req_num or vars.reqnum or 1
		local interval = vars.interval or 1
		local toggle = vars.toggle or nil
		local unitTableDef = vars.unitTableDef
		local zUnitTableDef = vars.zUnitTableDef

		if not units.processed then
			unitTableDef = mist.utils.deepCopy(units)
		end

		if not zone_units.processed then
			zUnitTableDef = mist.utils.deepCopy(zone_units)
		end

		if (units.processed and units.processed < mist.getLastDBUpdateTime()) or not units.processed then -- run unit table short cuts
			if unitTableDef then
				units = mist.makeUnitTable(unitTableDef)
			end
		end

		if (zone_units.processed and zone_units.processed < mist.getLastDBUpdateTime()) or not zone_units.processed then -- run unit table short cuts
			if zUnitTableDef then
				zone_units = mist.makeUnitTable(zUnitTableDef)
			end
			
		end

		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then

			local in_zone_units = mist.getUnitsInMovingZones(units, zone_units, radius, zone_type)

			if #in_zone_units >= req_num and trigger.misc.getUserFlag(flag) == 0 then
				trigger.action.setUserFlag(flag, true)
			elseif #in_zone_units < req_num and toggle then
				trigger.action.setUserFlag(flag, false)
			end
			-- do another check in case stopflag was set true by this function
			if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
				mist.scheduleFunction(mist.flagFunc.units_in_moving_zones, {{units = units, zone_units = zone_units, radius = radius, flag = flag, stopflag = stopflag, zone_type = zone_type, req_num = req_num, interval = interval, toggle = toggle, unitTableDef = unitTableDef, zUnitTableDef = zUnitTableDef}}, timer.getTime() + interval)
			end
		end

	end

	--- Sets a flag if units have line of sight to each other.
	-- @todo document
	function mist.flagFunc.units_LOS(vars)
		--[[vars needs to be:
unitset1 = table,
altoffset1 = number,
unitset2 = table,
altoffset2 = number,
flag = number,
stopflag = number or nil,
radius = number or nil,
interval	= number or nil,
req_num = number or nil
toggle = boolean or nil
]]
		-- type_tbl
		local type_tbl = {
			[{'unitset1', 'units1'}] = 'table',
			[{'altoffset1', 'alt1'}] = 'number',
			[{'unitset2', 'units2'}] = 'table',
			[{'altoffset2', 'alt2'}] = 'number',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			[{'req_num', 'reqnum'}] = {'number', 'nil'},
			interval = {'number', 'nil'},
			radius = {'number', 'nil'},
			toggle = {'boolean', 'nil'},
			unitTableDef1 = {'table', 'nil'},
			unitTableDef2 = {'table', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.units_LOS', type_tbl, vars)
		assert(err, errmsg)
		local unitset1 = vars.unitset1 or vars.units1
		local altoffset1 = vars.altoffset1 or vars.alt1
		local unitset2 = vars.unitset2 or vars.units2
		local altoffset2 = vars.altoffset2 or vars.alt2
		local flag = vars.flag
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local interval = vars.interval or 1
		local radius = vars.radius or math.huge
		local req_num = vars.req_num or vars.reqnum or 1
		local toggle = vars.toggle or nil
		local unitTableDef1 = vars.unitTableDef1
		local unitTableDef2 = vars.unitTableDef2

		if not unitset1.processed then
			unitTableDef1 = mist.utils.deepCopy(unitset1)
		end

		if not unitset2.processed then
			unitTableDef2 = mist.utils.deepCopy(unitset2)
		end

		if (unitset1.processed and unitset1.processed < mist.getLastDBUpdateTime()) or not unitset1.processed then -- run unit table short cuts
			if unitTableDef1 then
				unitset1 = mist.makeUnitTable(unitTableDef1)
			end
		end

		if (unitset2.processed and unitset2.processed < mist.getLastDBUpdateTime()) or not unitset2.processed then -- run unit table short cuts
			if unitTableDef2 then
				unitset2 = mist.makeUnitTable(unitTableDef2)
			end
		end


		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then

			local unitLOSdata = mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius)

			if #unitLOSdata >= req_num and trigger.misc.getUserFlag(flag) == 0 then
				trigger.action.setUserFlag(flag, true)
			elseif #unitLOSdata < req_num and toggle then
				trigger.action.setUserFlag(flag, false)
			end
			-- do another check in case stopflag was set true by this function
			if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
				mist.scheduleFunction(mist.flagFunc.units_LOS, {{unitset1 = unitset1, altoffset1 = altoffset1, unitset2 = unitset2, altoffset2 = altoffset2, flag = flag, stopflag = stopflag, radius = radius, req_num = req_num, interval = interval, toggle = toggle, unitTableDef1 = unitTableDef1, unitTableDef2 = unitTableDef2}}, timer.getTime() + interval)
			end
		end
	end

	--- Sets a flag if group is alive.
	-- @todo document
	function mist.flagFunc.group_alive(vars)
		--[[vars
groupName
flag
toggle
interval
stopFlag

]]
		local type_tbl = {
			[{'group', 'groupname', 'gp', 'groupName'}] = 'string',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			interval = {'number', 'nil'},
			toggle = {'boolean', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive', type_tbl, vars)
		assert(err, errmsg)

		local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname
		local flag = vars.flag
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local interval = vars.interval or 1
		local toggle = vars.toggle or nil


		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true and #Group.getByName(groupName):getUnits() > 0 then
				if trigger.misc.getUserFlag(flag) == 0 then
					trigger.action.setUserFlag(flag, true)
				end
			else
				if toggle then
					trigger.action.setUserFlag(flag, false)
				end
			end
		end

		if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			mist.scheduleFunction(mist.flagFunc.group_alive, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle}}, timer.getTime() + interval)
		end

	end

	--- Sets a flag if group is dead.
	-- @todo document
	function mist.flagFunc.group_dead(vars)
		local type_tbl = {
			[{'group', 'groupname', 'gp', 'groupName'}] = 'string',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			interval = {'number', 'nil'},
			toggle = {'boolean', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_dead', type_tbl, vars)
		assert(err, errmsg)

		local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname
		local flag = vars.flag
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local interval = vars.interval or 1
		local toggle = vars.toggle or nil


		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			if (Group.getByName(groupName) and Group.getByName(groupName):isExist() == false) or (Group.getByName(groupName) and #Group.getByName(groupName):getUnits() < 1) or not Group.getByName(groupName) then
				if trigger.misc.getUserFlag(flag) == 0 then
					trigger.action.setUserFlag(flag, true)
				end
			else
				if toggle then
					trigger.action.setUserFlag(flag, false)
				end
			end
		end

		if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			mist.scheduleFunction(mist.flagFunc.group_dead, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle}}, timer.getTime() + interval)
		end
	end

	--- Sets a flag if less than given percent of group is alive.
	-- @todo document
	function mist.flagFunc.group_alive_less_than(vars)
		local type_tbl = {
			[{'group', 'groupname', 'gp', 'groupName'}] = 'string',
			percent = 'number',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			interval = {'number', 'nil'},
			toggle = {'boolean', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive_less_than', type_tbl, vars)
		assert(err, errmsg)

		local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname
		local flag = vars.flag
		local percent = vars.percent
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local interval = vars.interval or 1
		local toggle = vars.toggle or nil


		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then
				if Group.getByName(groupName):getSize()/Group.getByName(groupName):getInitialSize() < percent/100 then
					if trigger.misc.getUserFlag(flag) == 0 then
						trigger.action.setUserFlag(flag, true)
					end
				else
					if toggle then
						trigger.action.setUserFlag(flag, false)
					end
				end
			else
				if trigger.misc.getUserFlag(flag) == 0 then
					trigger.action.setUserFlag(flag, true)
				end
			end
		end

		if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			mist.scheduleFunction(mist.flagFunc.group_alive_less_than, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle, percent = percent}}, timer.getTime() + interval)
		end
	end

	--- Sets a flag if more than given percent of group is alive.
	-- @todo document
	function mist.flagFunc.group_alive_more_than(vars)
		local type_tbl = {
			[{'group', 'groupname', 'gp', 'groupName'}] = 'string',
			percent = 'number',
			flag = {'number', 'string'},
			[{'stopflag', 'stopFlag'}] = {'number', 'string', 'nil'},
			interval = {'number', 'nil'},
			toggle = {'boolean', 'nil'},
		}

		local err, errmsg = mist.utils.typeCheck('mist.flagFunc.group_alive_more_than', type_tbl, vars)
		assert(err, errmsg)

		local groupName = vars.groupName or vars.group or vars.gp or vars.Groupname
		local flag = vars.flag
		local percent = vars.percent
		local stopflag = vars.stopflag or vars.stopFlag or -1
		local interval = vars.interval or 1
		local toggle = vars.toggle or nil


		if stopflag == -1 or (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			if Group.getByName(groupName) and Group.getByName(groupName):isExist() == true then
				if Group.getByName(groupName):getSize()/Group.getByName(groupName):getInitialSize() > percent/100 then
					if trigger.misc.getUserFlag(flag) == 0 then
						trigger.action.setUserFlag(flag, true)
					end
				else
					if toggle and trigger.misc.getUserFlag(flag) == 1 then
						trigger.action.setUserFlag(flag, false)
					end
				end
			else --- just in case
				if toggle and trigger.misc.getUserFlag(flag) == 1 then
					trigger.action.setUserFlag(flag, false)
				end
			end
		end

		if (type(trigger.misc.getUserFlag(stopflag)) == 'number' and trigger.misc.getUserFlag(stopflag) == 0) or (type(trigger.misc.getUserFlag(stopflag)) == 'boolean' and trigger.misc.getUserFlag(stopflag) == false) then
			mist.scheduleFunction(mist.flagFunc.group_alive_more_than, {{groupName = groupName, flag = flag, stopflag = stopflag, interval = interval, toggle = toggle, percent = percent}}, timer.getTime() + interval)
		end
	end

	mist.flagFunc.mapobjsDeadPolygon = mist.flagFunc.mapobjs_dead_polygon
	mist.flagFunc.mapobjsDeadZones = mist.flagFunc.Mapobjs_dead_zones
	mist.flagFunc.unitsInZones = mist.flagFunc.units_in_zones
	mist.flagFunc.unitsInMovingZones = mist.flagFunc.units_in_moving_zones
	mist.flagFunc.unitsInPolygon = mist.flagFunc.units_in_polygon
	mist.flagFunc.unitsLOS = mist.flagFunc.units_LOS
	mist.flagFunc.groupAlive = mist.flagFunc.group_alive
	mist.flagFunc.groupDead = mist.flagFunc.group_dead
	mist.flagFunc.groupAliveMoreThan = mist.flagFunc.group_alive_more_than
	mist.flagFunc.groupAliveLessThan = mist.flagFunc.group_alive_less_than

end

--- Message functions.
-- Messaging system
-- @section mist.msg
do -- mist.msg scope
	local messageList = {}
	-- this defines the max refresh rate of the message box it honestly only needs to
	-- go faster than this for precision timing stuff (which could be its own function)
	local messageDisplayRate = 0.1
	local messageID = 0
	local displayActive = false
	local displayFuncId = 0

	local caSlots = false
	local caMSGtoGroup = false

	if env.mission.groundControl then -- just to be sure?
		for index, value in pairs(env.mission.groundControl) do
			if type(value) == 'table' then
				for roleName, roleVal in pairs(value) do
					for rIndex, rVal in pairs(roleVal) do
						if rIndex == 'red' or rIndex == 'blue' then
							if env.mission.groundControl[index][roleName][rIndex] > 0 then
								caSlots = true
								break
							end
						end
					end
				end
			elseif type(value) == 'boolean' and value == true then
				caSlots = true
				break
			end
		end
	end

	local function mistdisplayV5()
		--[[thoughts to improve upon
		event handler based activeClients table.
		display messages only when there is an update
		possibly co-routine it.
		]]
	end

	local function mistdisplayV4()
		local activeClients = {}

		for clientId, clientData in pairs(mist.DBs.humansById) do
			if Unit.getByName(clientData.unitName) and Unit.getByName(clientData.unitName):isExist() == true then
				activeClients[clientData.groupId] = clientData.groupName
			end
		end

		--[[if caSlots == true and caMSGtoGroup == true then

		end]]


		if #messageList > 0 then
			if displayActive == false then
				displayActive = true
			end
			--mist.debug.writeData(mist.utils.serialize,{'msg', messageList}, 'messageList.lua')
			local msgTableText = {}
			local msgTableSound = {}

			for messageId, messageData in pairs(messageList) do
				if messageData.displayedFor > messageData.displayTime then
					messageData:remove()	-- now using the remove/destroy function.
				else
					if messageData.displayedFor then
						messageData.displayedFor = messageData.displayedFor + messageDisplayRate
					end
					local nextSound = 1000
					local soundIndex = 0

					if messageData.multSound and #messageData.multSound > 0 then
						for index, sData in pairs(messageData.multSound) do
							if sData.time <= messageData.displayedFor and sData.played == false and sData.time < nextSound then -- find index of the next sound to be played
								nextSound = sData.time
								soundIndex = index
							end
						end
						if soundIndex ~= 0 then
							messageData.multSound[soundIndex].played = true
						end
					end

					for recIndex, recData in pairs(messageData.msgFor) do -- iterate recipiants
						if recData == 'RED' or recData == 'BLUE' or activeClients[recData] then -- rec exists
							if messageData.text then -- text
								if not msgTableText[recData] then -- create table entry for text
									msgTableText[recData] = {}
									msgTableText[recData].text = {}
									if recData == 'RED' or recData == 'BLUE' then
										msgTableText[recData].text[1] = '-------Combined Arms Message-------- \n'
									end
									msgTableText[recData].text[#msgTableText[recData].text + 1] = messageData.text
									msgTableText[recData].displayTime = messageData.displayTime - messageData.displayedFor
								else -- add to table entry and adjust display time if needed
									if recData == 'RED' or recData == 'BLUE' then
										msgTableText[recData].text[#msgTableText[recData].text + 1] = '\n ---------------- Combined Arms Message: \n'
									else
										msgTableText[recData].text[#msgTableText[recData].text + 1] = '\n ---------------- \n'
									end
									msgTableText[recData].text[#msgTableText[recData].text + 1] = messageData.text
									if msgTableText[recData].displayTime < messageData.displayTime - messageData.displayedFor then
										msgTableText[recData].displayTime = messageData.displayTime - messageData.displayedFor
									else
										msgTableText[recData].displayTime = 1
									end
								end
							end
							if soundIndex ~= 0 then
								msgTableSound[recData] = messageData.multSound[soundIndex].file
							end
						end
					end


				end
			end
			------- new display

			if caSlots == true and caMSGtoGroup == false then
				if msgTableText.RED then
					trigger.action.outTextForCoalition(coalition.side.RED, table.concat(msgTableText.RED.text), msgTableText.RED.displayTime, true)

				end
				if msgTableText.BLUE then
					trigger.action.outTextForCoalition(coalition.side.BLUE, table.concat(msgTableText.BLUE.text), msgTableText.BLUE.displayTime, true)
				end
			end

			for index, msgData in pairs(msgTableText) do
				if type(index) == 'number' then -- its a groupNumber
					trigger.action.outTextForGroup(index, table.concat(msgData.text), msgData.displayTime, true)
				end
			end
			--- new audio
			if msgTableSound.RED then
				trigger.action.outSoundForCoalition(coalition.side.RED, msgTableSound.RED)
			end
			if msgTableSound.BLUE then
				trigger.action.outSoundForCoalition(coalition.side.BLUE, msgTableSound.BLUE)
			end


			for index, file in pairs(msgTableSound) do
				if type(index) == 'number' then -- its a groupNumber
					trigger.action.outSoundForGroup(index, file)
				end
			end
		else
			mist.removeFunction(displayFuncId)
			displayActive = false
		end

	end

	local typeBase = {
		['Mi-8MT'] = {'Mi-8MTV2', 'Mi-8MTV', 'Mi-8'},
		['MiG-21Bis'] = {'Mig-21'},
		['MiG-15bis'] = {'Mig-15'},
		['FW-190D9'] = {'FW-190'},
		['Bf-109K-4'] = {'Bf-109'},
	}

	--[[function mist.setCAGroupMSG(val)
	if type(val) == 'boolean' then
		caMSGtoGroup = val
		return true
	end
	return false
end]]

	mist.message = {

		add = function(vars)
			local function msgSpamFilter(recList, spamBlockOn)
				for id, name in pairs(recList) do
					if name == spamBlockOn then
						--	log:info('already on recList')
						return recList
					end
				end
				--log:info('add to recList')
				table.insert(recList, spamBlockOn)
				return recList
			end

			--[[
			local vars = {}
			vars.text = 'Hello World'
			vars.displayTime = 20
			vars.msgFor = {coa = {'red'}, countries = {'Ukraine', 'Georgia'}, unitTypes = {'A-10C'}}
			mist.message.add(vars)

			Displays the message for all red coalition players. Players belonging to Ukraine and Georgia, and all A-10Cs on the map

			]]


			local new = {}
			new.text = vars.text -- The actual message
			new.displayTime = vars.displayTime -- How long will the message appear for
			new.displayedFor = 0 -- how long the message has been displayed so far
			new.name = vars.name	 -- ID to overwrite the older message (if it exists) Basically it replaces a message that is displayed with new text.
			new.addedAt = timer.getTime()
			new.update = true

			if vars.multSound and vars.multSound[1] then
				new.multSound = vars.multSound
			else
				new.multSound = {}
			end

			if vars.sound or vars.fileName then -- converts old sound file system into new multSound format
				local sound = vars.sound
				if vars.fileName then
					sound = vars.fileName
				end
				new.multSound[#new.multSound+1] = {time = 0.1, file = sound}
			end

			if #new.multSound > 0 then
				for i, data in pairs(new.multSound) do
					data.played = false
				end
			end

			local newMsgFor = {} -- list of all groups message displays for
			for forIndex, forData in pairs(vars.msgFor) do
				for list, listData in pairs(forData) do
					for clientId, clientData in pairs(mist.DBs.humansById) do
						forIndex = string.lower(forIndex)
						if type(listData) == 'string' then
							listData = string.lower(listData)
						end
						if (forIndex == 'coa' and (listData == string.lower(clientData.coalition) or listData == 'all')) or (forIndex == 'countries' and string.lower(clientData.country) == listData) or (forIndex == 'units' and string.lower(clientData.unitName) == listData) then --
							newMsgFor = msgSpamFilter(newMsgFor, clientData.groupId) -- so units dont get the same message twice if complex rules are given
							--table.insert(newMsgFor, clientId)
						elseif forIndex == 'unittypes' then
							for typeId, typeData in pairs(listData) do
								local found = false
								for clientDataEntry, clientDataVal in pairs(clientData) do
									if type(clientDataVal) == 'string' then
										if mist.matchString(list, clientDataVal) == true or list == 'all' then
											local sString = typeData
											for rName, pTbl in pairs(typeBase) do -- just a quick check to see if the user may have meant something and got the specific type of the unit wrong
												for pIndex, pName in pairs(pTbl) do
													if mist.stringMatch(sString, pName) then
														sString = rName
													end
												end
											end
											if sString == clientData.type then
												found = true
												newMsgFor = msgSpamFilter(newMsgFor, clientData.groupId) -- sends info oto other function to see if client is already recieving the current message.
												--table.insert(newMsgFor, clientId)
											end
										end
									end
									if found == true then	-- shouldn't this be elsewhere too?
										break
									end
								end
							end

						end
					end
					for coaData, coaId in pairs(coalition.side) do
						if string.lower(forIndex) == 'coa' or string.lower(forIndex) == 'ca' then
							if listData == string.lower(coaData) or listData == 'all' then
								newMsgFor = msgSpamFilter(newMsgFor, coaData)
							end
						end
					end
				end
			end

			if #newMsgFor > 0 then
				new.msgFor = newMsgFor -- I swear its not confusing

			else
				return false
			end


			if vars.name and type(vars.name) == 'string' then
				for i = 1, #messageList do
					if messageList[i].name then
						if messageList[i].name == vars.name then
							--log:info('updateMessage')
							messageList[i].displayedFor = 0
							messageList[i].addedAt = timer.getTime()
							messageList[i].sound = new.sound
							messageList[i].text = new.text
							messageList[i].msgFor = new.msgFor
							messageList[i].multSound = new.multSound
							messageList[i].update = true
							return messageList[i].messageID
						end
					end
				end
			end

			messageID = messageID + 1
			new.messageID = messageID

			--mist.debug.writeData(mist.utils.serialize,{'msg', new}, 'newMsg.lua')


			messageList[#messageList + 1] = new

			local mt = { __index =	mist.message}
			setmetatable(new, mt)

			if displayActive == false then
				displayActive = true
				displayFuncId = mist.scheduleFunction(mistdisplayV4, {}, timer.getTime() + messageDisplayRate, messageDisplayRate)
			end

			return messageID

		end,

		remove = function(self)	-- Now a self variable; the former functionality taken up by mist.message.removeById.
			for i, msgData in pairs(messageList) do
				if messageList[i] == self then
					table.remove(messageList, i)
					return true --removal successful
				end
			end
			return false -- removal not successful this script fails at life!
		end,

		removeById = function(id)	-- This function is NOT passed a self variable; it is the remove by id function.
			for i, msgData in pairs(messageList) do
				if messageList[i].messageID == id then
					table.remove(messageList, i)
					return true --removal successful
				end
			end
			return false -- removal not successful this script fails at life!
		end,
	}

	--[[ vars for mist.msgMGRS
vars.units - table of unit names (NOT unitNameTable- maybe this should change).
vars.acc - integer between 0 and 5, inclusive
vars.text - text in the message
vars.displayTime - self explanatory
vars.msgFor - scope
]]
	function mist.msgMGRS(vars)
		local units = vars.units
		local acc = vars.acc
		local text = vars.text
		local displayTime = vars.displayTime
		local msgFor = vars.msgFor

		local s = mist.getMGRSString{units = units, acc = acc}
		local newText
		if text then
			if string.find(text, '%%s') then	-- look for %s
				newText = string.format(text, s)	-- insert the coordinates into the message
			else
				-- just append to the end.
				newText = text .. s
			end
		else
			newText = s
		end
		mist.message.add{
			text = newText,
			displayTime = displayTime,
			msgFor = msgFor
		}
	end

	--[[ vars for mist.msgLL
vars.units - table of unit names (NOT unitNameTable- maybe this should change) (Yes).
vars.acc - integer, number of numbers after decimal place
vars.DMS - if true, output in degrees, minutes, seconds.	Otherwise, output in degrees, minutes.
vars.text - text in the message
vars.displayTime - self explanatory
vars.msgFor - scope
]]
	function mist.msgLL(vars)
		local units = vars.units	-- technically, I don't really need to do this, but it helps readability.
		local acc = vars.acc
		local DMS = vars.DMS
		local text = vars.text
		local displayTime = vars.displayTime
		local msgFor = vars.msgFor

		local s = mist.getLLString{units = units, acc = acc, DMS = DMS}
		local newText
		if text then
			if string.find(text, '%%s') then	-- look for %s
				newText = string.format(text, s)	-- insert the coordinates into the message
			else
				-- just append to the end.
				newText = text .. s
			end
		else
			newText = s
		end

		mist.message.add{
			text = newText,
			displayTime = displayTime,
			msgFor = msgFor
		}

	end

	--[[
vars.units- table of unit names (NOT unitNameTable- maybe this should change).
vars.ref -	vec3 ref point, maybe overload for vec2 as well?
vars.alt - boolean, if used, includes altitude in string
vars.metric - boolean, gives distance in km instead of NM.
vars.text - text of the message
vars.displayTime
vars.msgFor - scope
]]
	function mist.msgBR(vars)
		local units = vars.units	-- technically, I don't really need to do this, but it helps readability.
		local ref = vars.ref -- vec2/vec3 will be handled in mist.getBRString
		local alt = vars.alt
		local metric = vars.metric
		local text = vars.text
		local displayTime = vars.displayTime
		local msgFor = vars.msgFor

		local s = mist.getBRString{units = units, ref = ref, alt = alt, metric = metric}
		local newText
		if text then
			if string.find(text, '%%s') then	-- look for %s
				newText = string.format(text, s)	-- insert the coordinates into the message
			else
				-- just append to the end.
				newText = text .. s
			end
		else
			newText = s
		end

		mist.message.add{
			text = newText,
			displayTime = displayTime,
			msgFor = msgFor
		}

	end

	-- basically, just sub-types of mist.msgBR... saves folks the work of getting the ref point.
	--[[
vars.units- table of unit names (NOT unitNameTable- maybe this should change).
vars.ref -	string red, blue
vars.alt - boolean, if used, includes altitude in string
vars.metric - boolean, gives distance in km instead of NM.
vars.text - text of the message
vars.displayTime
vars.msgFor - scope
]]
	function mist.msgBullseye(vars)
		if string.lower(vars.ref) == 'red' then
			vars.ref = mist.DBs.missionData.bullseye.red
			mist.msgBR(vars)
		elseif string.lower(vars.ref) == 'blue' then
			vars.ref = mist.DBs.missionData.bullseye.blue
			mist.msgBR(vars)
		end
	end

	--[[
vars.units- table of unit names (NOT unitNameTable- maybe this should change).
vars.ref -	unit name of reference point
vars.alt - boolean, if used, includes altitude in string
vars.metric - boolean, gives distance in km instead of NM.
vars.text - text of the message
vars.displayTime
vars.msgFor - scope
]]
	function mist.msgBRA(vars)
		if Unit.getByName(vars.ref) and Unit.getByName(vars.ref):isExist() == true then
			vars.ref = Unit.getByName(vars.ref):getPosition().p
			if not vars.alt then
				vars.alt = true
			end
			mist.msgBR(vars)
		end
	end

	--[[ vars for mist.msgLeadingMGRS:
vars.units - table of unit names
vars.heading - direction
vars.radius - number
vars.headingDegrees - boolean, switches heading to degrees (optional)
vars.acc - number, 0 to 5.
vars.text - text of the message
vars.displayTime
vars.msgFor - scope
]]
	function mist.msgLeadingMGRS(vars)
		local units = vars.units	-- technically, I don't really need to do this, but it helps readability.
		local heading = vars.heading
		local radius = vars.radius
		local headingDegrees = vars.headingDegrees
		local acc = vars.acc
		local text = vars.text
		local displayTime = vars.displayTime
		local msgFor = vars.msgFor

		local s = mist.getLeadingMGRSString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc}
		local newText
		if text then
			if string.find(text, '%%s') then	-- look for %s
				newText = string.format(text, s)	-- insert the coordinates into the message
			else
				-- just append to the end.
				newText = text .. s
			end
		else
			newText = s
		end

		mist.message.add{
			text = newText,
			displayTime = displayTime,
			msgFor = msgFor
		}


	end

	--[[ vars for mist.msgLeadingLL:
vars.units - table of unit names
vars.heading - direction, number
vars.radius - number
vars.headingDegrees - boolean, switches heading to degrees (optional)
vars.acc - number of digits after decimal point (can be negative)
vars.DMS -	boolean, true if you want DMS. (optional)
vars.text - text of the message
vars.displayTime
vars.msgFor - scope
]]
	function mist.msgLeadingLL(vars)
		local units = vars.units	-- technically, I don't really need to do this, but it helps readability.
		local heading = vars.heading
		local radius = vars.radius
		local headingDegrees = vars.headingDegrees
		local acc = vars.acc
		local DMS = vars.DMS
		local text = vars.text
		local displayTime = vars.displayTime
		local msgFor = vars.msgFor

		local s = mist.getLeadingLLString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc, DMS = DMS}
		local newText

		if text then
			if string.find(text, '%%s') then	-- look for %s
				newText = string.format(text, s)	-- insert the coordinates into the message
			else
				-- just append to the end.
				newText = text .. s
			end
		else
			newText = s
		end

		mist.message.add{
			text = newText,
			displayTime = displayTime,
			msgFor = msgFor
		}

	end

	--[[
vars.units - table of unit names
vars.heading - direction, number
vars.radius - number
vars.headingDegrees - boolean, switches heading to degrees	(optional)
vars.metric - boolean, if true, use km instead of NM. (optional)
vars.alt - boolean, if true, include altitude. (optional)
vars.ref - vec3/vec2 reference point.
vars.text - text of the message
vars.displayTime
vars.msgFor - scope
]]
	function mist.msgLeadingBR(vars)
		local units = vars.units	-- technically, I don't really need to do this, but it helps readability.
		local heading = vars.heading
		local radius = vars.radius
		local headingDegrees = vars.headingDegrees
		local metric = vars.metric
		local alt = vars.alt
		local ref = vars.ref -- vec2/vec3 will be handled in mist.getBRString
		local text = vars.text
		local displayTime = vars.displayTime
		local msgFor = vars.msgFor

		local s = mist.getLeadingBRString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, metric = metric, alt = alt, ref = ref}
		local newText

		if text then
			if string.find(text, '%%s') then	-- look for %s
				newText = string.format(text, s)	-- insert the coordinates into the message
			else
				-- just append to the end.
				newText = text .. s
			end
		else
			newText = s
		end

		mist.message.add{
			text = newText,
			displayTime = displayTime,
			msgFor = msgFor
		}
	end
end

--- Demo functions.
-- @section mist.demos
do -- mist.demos scope
	mist.demos = {}

	function mist.demos.printFlightData(unit)
		if unit:isExist() then
			local function printData(unit, prevVel, prevE, prevTime)
				local angles = mist.getAttitude(unit)
				if angles then
					local Heading = angles.Heading
					local Pitch = angles.Pitch
					local Roll = angles.Roll
					local Yaw = angles.Yaw
					local AoA = angles.AoA
					local ClimbAngle = angles.ClimbAngle

					if not Heading then
						Heading = 'NA'
					else
						Heading = string.format('%12.2f', mist.utils.toDegree(Heading))
					end

					if not Pitch then
						Pitch = 'NA'
					else
						Pitch = string.format('%12.2f', mist.utils.toDegree(Pitch))
					end

					if not Roll then
						Roll = 'NA'
					else
						Roll = string.format('%12.2f', mist.utils.toDegree(Roll))
					end

					local AoAplusYaw = 'NA'
					if AoA and Yaw then
						AoAplusYaw = string.format('%12.2f', mist.utils.toDegree((AoA^2 + Yaw^2)^0.5))
					end

					if not Yaw then
						Yaw = 'NA'
					else
						Yaw = string.format('%12.2f', mist.utils.toDegree(Yaw))
					end

					if not AoA then
						AoA = 'NA'
					else
						AoA = string.format('%12.2f', mist.utils.toDegree(AoA))
					end

					if not ClimbAngle then
						ClimbAngle = 'NA'
					else
						ClimbAngle = string.format('%12.2f', mist.utils.toDegree(ClimbAngle))
					end
					local unitPos = unit:getPosition()
					local unitVel = unit:getVelocity()
					local curTime = timer.getTime()
					local absVel = string.format('%12.2f', mist.vec.mag(unitVel))


					local unitAcc = 'NA'
					local Gs = 'NA'
					local axialGs = 'NA'
					local transGs = 'NA'
					if prevVel and prevTime then
						local xAcc = (unitVel.x - prevVel.x)/(curTime - prevTime)
						local yAcc = (unitVel.y - prevVel.y)/(curTime - prevTime)
						local zAcc = (unitVel.z - prevVel.z)/(curTime - prevTime)

						unitAcc = string.format('%12.2f', mist.vec.mag({x = xAcc, y = yAcc, z = zAcc}))
						Gs = string.format('%12.2f', mist.vec.mag({x = xAcc, y = yAcc + 9.81, z = zAcc})/9.81)
						axialGs = string.format('%12.2f', mist.vec.dp({x = xAcc, y = yAcc + 9.81, z = zAcc}, unitPos.x)/9.81)
						transGs = string.format('%12.2f', mist.vec.mag(mist.vec.cp({x = xAcc, y = yAcc + 9.81, z = zAcc}, unitPos.x))/9.81)
					end

					local E = 0.5*mist.vec.mag(unitVel)^2 + 9.81*unitPos.p.y

					local energy = string.format('%12.2e', E)

					local dEdt = 'NA'
					if prevE and prevTime then
						dEdt = string.format('%12.2e', (E - prevE)/(curTime - prevTime))
					end

					trigger.action.outText(string.format('%-25s', 'Heading: ') .. Heading .. ' degrees\n' .. string.format('%-25s', 'Roll: ') .. Roll .. ' degrees\n' .. string.format('%-25s', 'Pitch: ') .. Pitch
							.. ' degrees\n' .. string.format('%-25s', 'Yaw: ') .. Yaw .. ' degrees\n' .. string.format('%-25s', 'AoA: ') .. AoA .. ' degrees\n' .. string.format('%-25s', 'AoA plus Yaw: ') .. AoAplusYaw .. ' degrees\n' .. string.format('%-25s', 'Climb Angle: ') ..
							ClimbAngle .. ' degrees\n' .. string.format('%-25s', 'Absolute Velocity: ') .. absVel .. ' m/s\n' .. string.format('%-25s', 'Absolute Acceleration: ') .. unitAcc ..' m/s^2\n'
							.. string.format('%-25s', 'Axial G loading: ') .. axialGs .. ' g\n' .. string.format('%-25s', 'Transverse G loading: ') .. transGs .. ' g\n' .. string.format('%-25s', 'Absolute G loading: ') .. Gs .. ' g\n' .. string.format('%-25s', 'Energy: ') .. energy .. ' J/kg\n' .. string.format('%-25s', 'dE/dt: ') .. dEdt ..' J/(kg*s)', 1)
					return unitVel, E, curTime
				end
			end

			local function frameFinder(unit, prevVel, prevE, prevTime)
				if unit:isExist() then
					local currVel = unit:getVelocity()
					if prevVel and (prevVel.x ~= currVel.x or prevVel.y ~= currVel.y or prevVel.z ~= currVel.z) or (prevTime and (timer.getTime() - prevTime) > 0.25) then
						prevVel, prevE, prevTime = printData(unit, prevVel, prevE, prevTime)
					end
					mist.scheduleFunction(frameFinder, {unit, prevVel, prevE, prevTime}, timer.getTime() + 0.005)	-- it can't go this fast, limited to the 100 times a sec check right now.
				end
			end


			local curVel = unit:getVelocity()
			local curTime = timer.getTime()
			local curE = 0.5*mist.vec.mag(curVel)^2 + 9.81*unit:getPosition().p.y
			frameFinder(unit, curVel, curE, curTime)

		end

	end

end

--- Time conversion functions.
-- @section mist.time
do -- mist.time scope
	mist.time = {}
	-- returns a string for specified military time
	-- theTime is optional
	-- if present current time in mil time is returned
	-- if number or table the time is converted into mil tim
	function mist.time.convertToSec(timeTable)

		timeInSec = 0
		if timeTable and type(timeTable) == 'number' then
			timeInSec = timeTable
		elseif timeTable and type(timeTable) == 'table' and (timeTable.d or timeTable.h or timeTable.m or timeTable.s) then
			if timeTable.d and type(timeTable.d) == 'number' then
				timeInSec = timeInSec + (timeTable.d*86400)
			end
			if timeTable.h and type(timeTable.h) == 'number' then
				timeInSec = timeInSec + (timeTable.h*3600)
			end
			if timeTable.m and type(timeTable.m) == 'number' then
				timeInSec = timeInSec + (timeTable.m*60)
			end
			if timeTable.s and type(timeTable.s) == 'number' then
				timeInSec = timeInSec + timeTable.s
			end

		end
		return timeInSec
	end

	function 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

	function mist.getMilString(theTime)
		local timeInSec = 0
		if theTime then
			timeInSec = mist.time.convertToSec(theTime)
		else
			timeInSec = mist.utils.round(timer.getAbsTime(), 0)
		end

		local DHMS = mist.time.getDHMS(timeInSec)

		return tostring(string.format('%02d', DHMS.h) .. string.format('%02d',DHMS.m))
	end

	function mist.getClockString(theTime, hour)
		local timeInSec = 0
		if theTime then
			timeInSec = mist.time.convertToSec(theTime)
		else
			timeInSec = mist.utils.round(timer.getAbsTime(), 0)
		end
		local DHMS = mist.time.getDHMS(timeInSec)
		if hour then
			if DHMS.h > 12 then
				DHMS.h = DHMS.h - 12
				return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m)	.. ':' .. string.format('%02d',DHMS.s) .. ' PM')
			else
				return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m)	.. ':' .. string.format('%02d',DHMS.s) .. ' AM')
			end
		else
			return tostring(string.format('%02d', DHMS.h) .. ':' .. string.format('%02d',DHMS.m)	.. ':' .. string.format('%02d',DHMS.s))
		end
	end

	-- returns the date in string format
	-- both variables optional
	-- first val returns with the month as a string
	-- 2nd val defins if it should be written the American way or the wrong way.
	function mist.time.getDate(convert)
		local cal = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} -- 
		local date = {}
		
		if not env.mission.date then -- Not likely to happen. Resaving mission auto updates this to remove it.
			date.d = 0
			date.m = 6
			date.y = 2011
		else 
			date.d = env.mission.date.Day
			date.m = env.mission.date.Month
			date.y = env.mission.date.Year
		end
		local start = 86400
		local timeInSec = mist.utils.round(timer.getAbsTime())
		if convert and type(convert) == 'number' then
			timeInSec = convert
		end
		if timeInSec > 86400 then
			while start < timeInSec do
				if date.d >= cal[date.m] then
					if date.m == 2 and date.d == 28 then -- HOLY COW we can edit years now. Gotta re-add this!
						if date.y % 4 == 0 and date.y % 100 == 0 and date.y % 400 ~= 0 or date.y % 4 > 0 then
							date.m = date.m + 1
							date.d = 0
						end
						--date.d = 29
					else
						date.m = date.m + 1
						date.d = 0
					end
				end
				if date.m == 13 then
					date.m = 1
					date.y = date.y + 1
				end
				date.d = date.d + 1
				start = start + 86400
				
			end
		end
		return date
	end

	function mist.time.relativeToStart(time)
		if type(time) == 'number' then
			return time - timer.getTime0()
		end
	end

	function mist.getDateString(rtnType, murica, oTime) -- returns date based on time
		local word = {'January', 'Feburary', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' } -- 'etc
		local curTime = 0
		if oTime then
			curTime = oTime
		else
			curTime = mist.utils.round(timer.getAbsTime())
		end
		local tbl = mist.time.getDate(curTime)

		if rtnType then
			if murica then
				return tostring(word[tbl.m] .. ' ' .. tbl.d .. ' ' .. tbl.y)
			else
				return tostring(tbl.d .. ' ' .. word[tbl.m] .. ' ' .. tbl.y)
			end
		else
			if murica then
				return tostring(tbl.m .. '.' .. tbl.d .. '.' .. tbl.y)
			else
				return tostring(tbl.d .. '.' .. tbl.m .. '.' .. tbl.y)
			end
		end
	end
	--WIP
	function mist.time.milToGame(milString, rtnType) --converts a military time. By default returns the abosolute time that event would occur. With optional value it returns how many seconds from time of call till that time.
		local curTime = mist.utils.round(timer.getAbsTime())
		local milTimeInSec = 0

		if milString and type(milString) == 'string' and string.len(milString) >= 4 then
			local hr = tonumber(string.sub(milString, 1, 2))
			local mi = tonumber(string.sub(milString, 3))
			milTimeInSec = milTimeInSec + (mi*60) + (hr*3600)
		elseif milString and type(milString) == 'table' and (milString.d or milString.h or milString.m or milString.s) then
			milTimeInSec = mist.time.convertToSec(milString)
		end

		local startTime = timer.getTime0()
		local daysOffset = 0
		if startTime > 86400 then
			daysOffset = mist.utils.round(startTime/86400)
			if daysOffset > 0 then
				milTimeInSec = milTimeInSec *daysOffset
			end
		end

		if curTime > milTimeInSec then
			milTimeInSec = milTimeInSec + 86400
		end
		if rtnType then
			milTimeInSec = milTimeInSec - startTime
		end
		return milTimeInSec
	end


end

--- Group task functions.
-- @section tasks
do -- group tasks scope
	mist.ground = {}
	mist.fixedWing = {}
	mist.heli = {}
	mist.air = {}
	mist.air.fixedWing = {}
	mist.air.heli = {}

	--- Tasks group to follow a route.
	-- This sets the mission task for the given group.
	-- Any wrapped actions inside the path (like enroute
	-- tasks) will be executed.
	-- @tparam Group group group to task.
	-- @tparam table path containing
	-- points defining a route.
	function mist.goRoute(group, path)
		local misTask = {
			id = 'Mission',
			params = {
				route = {
					points = mist.utils.deepCopy(path),
				},
			},
		}
		if type(group) == 'string' then
			group = Group.getByName(group)
		end
		if group then
			local groupCon = group:getController()
			if groupCon then
				groupCon:setTask(misTask)
				return true
			end
		end
		return false
	end

	-- same as getGroupPoints but returns speed and formation type along with vec2 of point}
	function mist.getGroupRoute(groupIdent, task)
		-- refactor to search by groupId and allow groupId and groupName as inputs
		local gpId = groupIdent
			if mist.DBs.MEgroupsByName[groupIdent] then
				gpId = mist.DBs.MEgroupsByName[groupIdent].groupId
			else
				log:error('$1 not found in mist.DBs.MEgroupsByName', groupIdent)
			end

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

												for point_num, point in pairs(group_data.route.points) do
													local routeData = {}
													if not point.point then
														routeData.x = point.x
														routeData.y = point.y
													else
														routeData.point = point.point	--it's possible that the ME could move to the point = Vec2 notation.
													end
													routeData.form = point.action
													routeData.speed = point.speed
													routeData.alt = point.alt
													routeData.alt_type = point.alt_type
													routeData.airdromeId = point.airdromeId
													routeData.helipadId = point.helipadId
													routeData.type = point.type
													routeData.action = point.action
													if task then
														routeData.task = point.task
													end
													points[point_num] = routeData
												end

												return points
											end
											log:error('Group route not defined in mission editor for groupId: $1', gpId)
											return
										end	--if group_data and group_data.name and group_data.name == 'groupname'
									end --for group_num, group_data in pairs(obj_type_data.group) do
								end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then
							end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then
						end --for obj_type_name, obj_type_data in pairs(cntry_data) do
					end --for cntry_id, cntry_data in pairs(coa_data.country) do
				end --if coa_data.country then --there is a country table
			end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then
		end --for coa_name, coa_data in pairs(mission.coalition) do
	end

	-- function mist.ground.buildPath() end -- ????

	function mist.ground.patrolRoute(vars)
		log:info('patrol')
		local tempRoute = {}
		local useRoute = {}
		local gpData = vars.gpData
		if type(gpData) == 'string' then
			gpData = Group.getByName(gpData)
		end

		local useGroupRoute
		if not vars.useGroupRoute then
			useGroupRoute = vars.gpData
		else
			useGroupRoute = vars.useGroupRoute
		end
		local routeProvided = false
		if not vars.route then
			if useGroupRoute then
				tempRoute = mist.getGroupRoute(useGroupRoute)
			end
		else
			useRoute = vars.route
			local posStart = mist.getLeadPos(gpData)
			useRoute[1] = mist.ground.buildWP(posStart, useRoute[1].action, useRoute[1].speed)
			routeProvided = true
		end


		local overRideSpeed = vars.speed or 'default'
		local pType = vars.pType
		local offRoadForm = vars.offRoadForm or 'default'
		local onRoadForm = vars.onRoadForm or 'default'

		if routeProvided == false and #tempRoute > 0 then
			local posStart = mist.getLeadPos(gpData)


			useRoute[#useRoute + 1] = mist.ground.buildWP(posStart, offRoadForm, overRideSpeed)
			for i = 1, #tempRoute do
				local tempForm = tempRoute[i].action
				local tempSpeed = tempRoute[i].speed

				if offRoadForm == 'default' then
					tempForm = tempRoute[i].action
				end
				if onRoadForm == 'default' then
					onRoadForm = 'On Road'
				end
				if (string.lower(tempRoute[i].action) == 'on road' or	string.lower(tempRoute[i].action) == 'onroad' or string.lower(tempRoute[i].action) == 'on_road') then
					tempForm = onRoadForm
				else
					tempForm = offRoadForm
				end

				if type(overRideSpeed) == 'number' then
					tempSpeed = overRideSpeed
				end


				useRoute[#useRoute + 1] = mist.ground.buildWP(tempRoute[i], tempForm, tempSpeed)
			end

			if pType and string.lower(pType) == 'doubleback' then
				local curRoute = mist.utils.deepCopy(useRoute)
				for i = #curRoute, 2, -1 do
					useRoute[#useRoute + 1] = mist.ground.buildWP(curRoute[i], curRoute[i].action, curRoute[i].speed)
				end
			end

			useRoute[1].action = useRoute[#useRoute].action -- make it so the first WP matches the last WP
		end

		local cTask3 = {}
		local newPatrol = {}
		newPatrol.route = useRoute
		newPatrol.gpData = gpData:getName()
		cTask3[#cTask3 + 1] = 'mist.ground.patrolRoute('
		cTask3[#cTask3 + 1] = mist.utils.oneLineSerialize(newPatrol)
		cTask3[#cTask3 + 1] = ')'
		cTask3 = table.concat(cTask3)
		local tempTask = {
			id = 'WrappedAction',
			params = {
				action = {
					id = 'Script',
					params = {
						command = cTask3,

					},
				},
			},
		}
		
		useRoute[#useRoute].task = tempTask
		log:info(useRoute)
		mist.goRoute(gpData, useRoute)

		return
	end

	function mist.ground.patrol(gpData, pType, form, speed)
		local vars = {}

		if type(gpData) == 'table' and gpData:getName() then
			gpData = gpData:getName()
		end

		vars.useGroupRoute = gpData
		vars.gpData = gpData
		vars.pType = pType
		vars.offRoadForm = form
		vars.speed = speed

		mist.ground.patrolRoute(vars)

		return
	end

	-- No longer accepts path
	function mist.ground.buildWP(point, overRideForm, overRideSpeed)

		local wp = {}
		wp.x = point.x

		if point.z then
			wp.y = point.z
		else
			wp.y = point.y
		end
		local form, speed

		if point.speed and not overRideSpeed then
			wp.speed = point.speed
		elseif type(overRideSpeed) == 'number' then
			wp.speed = overRideSpeed
		else
			wp.speed = mist.utils.kmphToMps(20)
		end

		if point.form and not overRideForm then
			form = point.form
		else
			form = overRideForm
		end

		if not form then
			wp.action = 'Cone'
		else
			form = string.lower(form)
			if form == 'off_road' or form == 'off road' then
				wp.action = 'Off Road'
			elseif form == 'on_road' or form == 'on road' then
				wp.action = 'On Road'
			elseif form == 'rank' or form == 'line_abrest' or form == 'line abrest' or form == 'lineabrest'then
				wp.action = 'Rank'
			elseif form == 'cone' then
				wp.action = 'Cone'
			elseif form == 'diamond' then
				wp.action = 'Diamond'
			elseif form == 'vee' then
				wp.action = 'Vee'
			elseif form == 'echelon_left' or form == 'echelon left' or form == 'echelonl' then
				wp.action = 'EchelonL'
			elseif form == 'echelon_right' or form == 'echelon right' or form == 'echelonr' then
				wp.action = 'EchelonR'
			else
				wp.action = 'Cone' -- if nothing matched
			end
		end

		wp.type = 'Turning Point'

		return wp

	end

	function mist.fixedWing.buildWP(point, WPtype, speed, alt, altType)

		local wp = {}
		wp.x = point.x

		if point.z then
			wp.y = point.z
		else
			wp.y = point.y
		end

		if alt and type(alt) == 'number' then
			wp.alt = alt
		else
			wp.alt = 2000
		end

		if altType then
			altType = string.lower(altType)
			if altType == 'radio' or altType == 'agl' then
				wp.alt_type = 'RADIO'
			elseif altType == 'baro' or altType == 'asl' then
				wp.alt_type = 'BARO'
			end
		else
			wp.alt_type = 'RADIO'
		end

		if point.speed then
			speed = point.speed
		end

		if point.type then
			WPtype = point.type
		end

		if not speed then
			wp.speed = mist.utils.kmphToMps(500)
		else
			wp.speed = speed
		end

		if not WPtype then
			wp.action =	'Turning Point'
		else
			WPtype = string.lower(WPtype)
			if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then
				wp.action =	'Fly Over Point'
			elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then
				wp.action =	'Turning Point'
			else
				wp.action = 'Turning Point'
			end
		end

		wp.type = 'Turning Point'
		return wp
	end

	function mist.heli.buildWP(point, WPtype, speed, alt, altType)

		local wp = {}
		wp.x = point.x

		if point.z then
			wp.y = point.z
		else
			wp.y = point.y
		end

		if alt and type(alt) == 'number' then
			wp.alt = alt
		else
			wp.alt = 500
		end

		if altType then
			altType = string.lower(altType)
			if altType == 'radio' or altType == 'agl' then
				wp.alt_type = 'RADIO'
			elseif altType == 'baro' or altType == 'asl' then
				wp.alt_type = 'BARO'
			end
		else
			wp.alt_type = 'RADIO'
		end

		if point.speed then
			speed = point.speed
		end

		if point.type then
			WPtype = point.type
		end

		if not speed then
			wp.speed = mist.utils.kmphToMps(200)
		else
			wp.speed = speed
		end

		if not WPtype then
			wp.action =	'Turning Point'
		else
			WPtype = string.lower(WPtype)
			if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then
				wp.action =	'Fly Over Point'
			elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then
				wp.action = 'Turning Point'
			else
				wp.action =	'Turning Point'
			end
		end

		wp.type = 'Turning Point'
		return wp
	end

	-- need to return a Vec3 or Vec2?
	function mist.getRandPointInCircle(point, radius, innerRadius)
		local theta = 2*math.pi*math.random()
		local rad = math.random() + math.random()
		if rad > 1 then
			rad = 2 - rad
		end

		local radMult
		if innerRadius and innerRadius <= radius then
			radMult = (radius - innerRadius)*rad + innerRadius
		else
			radMult = radius*rad
		end

		if not point.z then --might as well work with vec2/3
			point.z = point.y
		end

		local rndCoord
		if radius > 0 then
			rndCoord = {x = math.cos(theta)*radMult + point.x, y = math.sin(theta)*radMult + point.z}
		else
			rndCoord = {x = point.x, y = point.z}
		end
		return rndCoord
	end

	function mist.getRandomPointInZone(zoneName, innerRadius)
		if type(zoneName) == 'string' and type(trigger.misc.getZone(zoneName)) == 'table' then
			return mist.getRandPointInCircle(trigger.misc.getZone(zoneName).point, trigger.misc.getZone(zoneName).radius, innerRadius)
		end
		return false
	end

	function mist.groupToRandomPoint(vars)
		local group = vars.group --Required
		local point = vars.point --required
		local radius = vars.radius or 0
		local innerRadius = vars.innerRadius
		local form = vars.form or 'Cone'
		local heading = vars.heading or math.random()*2*math.pi
		local headingDegrees = vars.headingDegrees
		local speed = vars.speed or mist.utils.kmphToMps(20)


		local useRoads
		if not vars.disableRoads then
			useRoads = true
		else
			useRoads = false
		end

		local path = {}

		if headingDegrees then
			heading = headingDegrees*math.pi/180
		end

		if heading >= 2*math.pi then
			heading = heading - 2*math.pi
		end

		local rndCoord = mist.getRandPointInCircle(point, radius, innerRadius)

		local offset = {}
		local posStart = mist.getLeadPos(group)

		offset.x = mist.utils.round(math.sin(heading - (math.pi/2)) * 50 + rndCoord.x, 3)
		offset.z = mist.utils.round(math.cos(heading + (math.pi/2)) * 50 + rndCoord.y, 3)
		path[#path + 1] = mist.ground.buildWP(posStart, form, speed)


		if useRoads == true and ((point.x - posStart.x)^2 + (point.z - posStart.z)^2)^0.5 > radius * 1.3 then
			path[#path + 1] = mist.ground.buildWP({x = posStart.x + 11, z = posStart.z + 11}, 'off_road', speed)
			path[#path + 1] = mist.ground.buildWP(posStart, 'on_road', speed)
			path[#path + 1] = mist.ground.buildWP(offset, 'on_road', speed)
		else
			path[#path + 1] = mist.ground.buildWP({x = posStart.x + 25, z = posStart.z + 25}, form, speed)
		end

		path[#path + 1] = mist.ground.buildWP(offset, form, speed)
		path[#path + 1] = mist.ground.buildWP(rndCoord, form, speed)

		mist.goRoute(group, path)

		return
	end

	function mist.groupRandomDistSelf(gpData, dist, form, heading, speed)
		local pos = mist.getLeadPos(gpData)
		local fakeZone = {}
		fakeZone.radius = dist or math.random(300, 1000)
		fakeZone.point = {x = pos.x, y = pos.y, z = pos.z}
		mist.groupToRandomZone(gpData, fakeZone, form, heading, speed)

		return
	end

	function mist.groupToRandomZone(gpData, zone, form, heading, speed)
		if type(gpData) == 'string' then
			gpData = Group.getByName(gpData)
		end

		if type(zone) == 'string' then
			zone = trigger.misc.getZone(zone)
		elseif type(zone) == 'table' and not zone.radius then
			zone = trigger.misc.getZone(zone[math.random(1, #zone)])
		end

		if speed then
			speed = mist.utils.kmphToMps(speed)
		end

		local vars = {}
		vars.group = gpData
		vars.radius = zone.radius
		vars.form = form
		vars.headingDegrees = heading
		vars.speed = speed
		vars.point = mist.utils.zoneToVec3(zone)

		mist.groupToRandomPoint(vars)

		return
	end

	function mist.isTerrainValid(coord, terrainTypes) -- vec2/3 and enum or table of acceptable terrain types
		if coord.z then
			coord.y = coord.z
		end
		local typeConverted = {}

		if type(terrainTypes) == 'string' then -- if its a string it does this check
			for constId, constData in pairs(land.SurfaceType) do
				if string.lower(constId) == string.lower(terrainTypes) or string.lower(constData) == string.lower(terrainTypes) then
					table.insert(typeConverted, constId)
				end
			end
		elseif type(terrainTypes) == 'table' then -- if its a table it does this check
			for typeId, typeData in pairs(terrainTypes) do
				for constId, constData in pairs(land.SurfaceType) do
					if string.lower(constId) == string.lower(typeData) or string.lower(constData) == string.lower(typeId) then
						table.insert(typeConverted, constId)
					end
				end
			end
		end
		for validIndex, validData in pairs(typeConverted) do
			if land.getSurfaceType(coord) == land.SurfaceType[validData] then
				return true
			end
		end
		return false
	end

	function mist.terrainHeightDiff(coord, searchSize)
		local samples = {}
		local searchRadius = 5
		if searchSize then
			searchRadius = searchSize
		end
		if type(coord) == 'string' then
			coord = mist.utils.zoneToVec3(coord)
		end

		coord = mist.utils.makeVec2(coord)

		samples[#samples + 1] = land.getHeight(coord)
		for i = 0, 360, 30 do
			samples[#samples + 1] = land.getHeight({x = (coord.x + (math.sin(math.rad(i))*searchRadius)), y = (coord.y + (math.cos(math.rad(i))*searchRadius))})
			if searchRadius >= 20 then -- if search radius is sorta large, take a sample halfway between center and outer edge
				samples[#samples + 1] = land.getHeight({x = (coord.x + (math.sin(math.rad(i))*(searchRadius/2))), y = (coord.y + (math.cos(math.rad(i))*(searchRadius/2)))})
			end
		end
		local tMax, tMin = 0, 1000000
		for index, height in pairs(samples) do
			if height > tMax then
				tMax = height
			end
			if height < tMin then
				tMin = height
			end
		end
		return mist.utils.round(tMax - tMin, 2)
	end

	function mist.groupToPoint(gpData, point, form, heading, speed, useRoads)
		if type(point) == 'string' then
			point = trigger.misc.getZone(point)
		end
		if speed then
			speed = mist.utils.kmphToMps(speed)
		end

		local vars = {}
		vars.group = gpData
		vars.form = form
		vars.headingDegrees = heading
		vars.speed = speed
		vars.disableRoads = useRoads
		vars.point = mist.utils.zoneToVec3(point)
		mist.groupToRandomPoint(vars)

		return
	end

	function mist.getLeadPos(group)
		if type(group) == 'string' then -- group name
			group = Group.getByName(group)
		end

		local units = group:getUnits()

		local leader = units[1]
		if not Unit.isExist(leader) then	-- SHOULD be good, but if there is a bug, this code future-proofs it then.
			local lowestInd = math.huge
			for ind, unit in pairs(units) do
				if Unit.isExist(unit) and ind < lowestInd then
					lowestInd = ind
					return unit:getPosition().p
				end
			end
		end
		if leader and Unit.isExist(leader) then	-- maybe a little too paranoid now...
			return leader:getPosition().p
		end
	end

end

--- Database tables.
-- @section mist.DBs

--- Mission data
-- @table mist.DBs.missionData
-- @field startTime mission start time
-- @field theatre mission theatre/map e.g. Caucasus
-- @field version mission version
-- @field files mission resources

--- Tables used as parameters.
-- @section varTables

--- mist.flagFunc.units_in_polygon parameter table.
-- @table unitsInPolygonVars
-- @tfield table unit name table @{UnitNameTable}.
-- @tfield table zone table defining a polygon.
-- @tfield number|string flag flag to set to true.
-- @tfield[opt] number|string stopflag if set to true the function
-- will stop evaluating.
-- @tfield[opt] number maxalt maximum altitude (MSL) for the
-- polygon.
-- @tfield[opt] number req_num minimum number of units that have
-- to be in the polygon.
-- @tfield[opt] number interval sets the interval for
-- checking if units are inside of the polygon in seconds. Default: 1.
-- @tfield[opt] boolean toggle switch the flag to false if required
-- conditions are not met. Default: false.
-- @tfield[opt] table unitTableDef

--- Logger class.
-- @type mist.Logger
do -- mist.Logger scope
	mist.Logger = {}

	--- parses text and substitutes keywords with values from given array.
	-- @param text string containing keywords to substitute with values
	-- or a variable.
	-- @param ... variables to use for substitution in string.
	-- @treturn string new string with keywords substituted or
	-- value of variable as string.
	local function formatText(text, ...)
		if type(text) ~= 'string' then
			if type(text) == 'table' then
				text = mist.utils.oneLineSerialize(text)
			else
				text = tostring(text)
			end
		else
			for index,value in ipairs(arg) do
				-- TODO: check for getmetatabel(value).__tostring
				if type(value) == 'table' then
					value = mist.utils.oneLineSerialize(value)
				else
					value = tostring(value)
				end
				text = text:gsub('$' .. index, value)
			end
		end
    local fName = nil
    local cLine = nil
		if debug then
			local dInfo = debug.getinfo(3)
			fName = dInfo.name
			cLine = dInfo.currentline
			-- local fsrc = dinfo.short_src
			--local fLine = dInfo.linedefined
		end
		if fName and cLine then
			return fName .. '|' .. cLine .. ': ' .. text
		elseif cLine then
			return cLine .. ': ' .. text
		else
			return ' ' .. text
		end
	end

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

	--- Creates a new logger.
	-- Each logger has it's own tag and log level.
	-- @tparam string tag tag which appears at the start of
	-- every log line produced by this logger.
	-- @tparam[opt] number|string level the log level defines which messages
	-- will be logged and which will be omitted. Log level 3 beeing the most verbose
	-- and 0 disabling all output. This can also be a string. Allowed strings are:
	-- "none" (0), "error" (1), "warning" (2) and "info" (3).
	-- @usage myLogger = mist.Logger:new("MyScript")
	-- @usage myLogger = mist.Logger:new("MyScript", 2)
	-- @usage myLogger = mist.Logger:new("MyScript", "info")
	-- @treturn mist.Logger
	function mist.Logger:new(tag, level)
		local l = {}
		l.tag = tag
		setmetatable(l, self)
		self.__index = self
		self:setLevel(level)
		return l
	end

	--- Sets the level of verbosity for this logger.
	-- @tparam[opt] number|string level the log level defines which messages
	-- will be logged and which will be omitted. Log level 3 beeing the most verbose
	-- and 0 disabling all output. This can also be a string. Allowed strings are:
	-- "none" (0), "error" (1), "warning" (2) and "info" (3).
	-- @usage myLogger:setLevel("info")
	-- @usage -- log everything
	--myLogger:setLevel(3)
	function mist.Logger:setLevel(level)
		if not level then
			self.level = 2
		else
			if type(level) == 'string' then
				if level == 'none' or level == 'off' then
					self.level = 0
				elseif level == 'error' then
					self.level = 1
				elseif level == 'warning' or level == 'warn' then
					self.level = 2
				elseif level == 'info' then
					self.level = 3
				end
			elseif type(level) == 'number' then
				self.level = level
			else
				self.level = 2
			end
		end
	end

	--- Logs error and shows alert window.
	-- This logs an error to the dcs.log and shows a popup window,
	-- pausing the simulation. This works always even if logging is
	-- disabled by setting a log level of "none" or 0.
	-- @tparam string text the text with keywords to substitute.
	-- @param ... variables to be used for substitution.
	-- @usage myLogger:alert("Shit just hit the fan! WEEEE!!!11")
	function mist.Logger:alert(text, ...)
		text = formatText(text, unpack(arg))
		if text:len() > 4000 then
			local texts = splitText(text)
			for i = 1, #texts do
				if i == 1 then
					env.error(self.tag .. '|' .. texts[i], true)
				else
					env.error(texts[i])
				end
			end
		else
			env.error(self.tag .. '|' .. text, true)
		end
	end

	--- Logs a message, disregarding the log level.
	-- @tparam string text the text with keywords to substitute.
	-- @param ... variables to be used for substitution.
	-- @usage myLogger:msg("Always logged!")
	function mist.Logger:msg(text, ...)
		text = formatText(text, unpack(arg))
		if text:len() > 4000 then
			local texts = splitText(text)
			for i = 1, #texts do
				if i == 1 then
					env.info(self.tag .. '|' .. texts[i])
				else
					env.info(texts[i])
				end
			end
		else
			env.info(self.tag .. '|' .. text)
		end
	end

	--- Logs an error.
	-- logs a message prefixed with this loggers tag to dcs.log as
	-- long as at least the "error" log level (1) is set.
	-- @tparam string text the text with keywords to substitute.
	-- @param ... variables to be used for substitution.
	-- @usage myLogger:error("Just an error!")
	-- @usage myLogger:error("Foo is $1 instead of $2", foo, "bar")
	function mist.Logger:error(text, ...)
		if self.level >= 1 then
			text = formatText(text, unpack(arg))
			if text:len() > 4000 then
				local texts = splitText(text)
				for i = 1, #texts do
					if i == 1 then
						env.error(self.tag .. '|' .. texts[i])
					else
						env.error(texts[i])
					end
				end
			else
				env.error(self.tag .. '|' .. text)
			end
		end
	end

	--- Logs a warning.
	-- logs a message prefixed with this loggers tag to dcs.log as
	-- long as at least the "warning" log level (2) is set.
	-- @tparam string text the text with keywords to substitute.
	-- @param ... variables to be used for substitution.
	-- @usage myLogger:warn("Mother warned you! Those $1 from the interwebs are $2", {"geeks", 1337})
	function mist.Logger:warn(text, ...)
		if self.level >= 2 then
			text = formatText(text, unpack(arg))
			if text:len() > 4000 then
				local texts = splitText(text)
				for i = 1, #texts do
					if i == 1 then
						env.warning(self.tag .. '|' .. texts[i])
					else
						env.warning(texts[i])
					end
				end
			else
				env.warning(self.tag .. '|' .. text)
			end
		end
	end

	--- Logs a info.
	-- logs a message prefixed with this loggers tag to dcs.log as
	-- long as the highest log level (3) "info" is set.
	-- @tparam string text the text with keywords to substitute.
	-- @param ... variables to be used for substitution.
	-- @see warn
	function mist.Logger:info(text, ...)
		if self.level >= 3 then
			text = formatText(text, unpack(arg))
			if text:len() > 4000 then
				local texts = splitText(text)
				for i = 1, #texts do
					if i == 1 then
						env.info(self.tag .. '|' .. texts[i])
					else
						env.info(texts[i])
					end
				end
			else
				env.info(self.tag .. '|' .. text)
			end
		end
	end

end

-- initialize mist
mist.init()
env.info(('Mist version ' .. mist.majorVersion .. '.' .. mist.minorVersion .. '.' .. mist.build .. ' loaded.'))

-- vim: noet:ts=2:sw=2
