--[[

]]

--MiST Mission Scripting Tools
mist = {}

-- don't change these
mist.majorVersion = 3
mist.minorVersion = 5
mist.build = 37 



--------------------------------------------------------------------------------------------------------------
-- the main area
do
	local coroutines = {}
	
	local tempSpawnedUnits = {} -- birth events added here
	local mistAddedObjects = {} -- mist.dynAdd unit data added here
	local mistAddedGroups = {} -- mist.dynAdd groupdata added here
	local writeGroups = {}
	
	local function update_alive_units()  -- 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
						--print('unit named ' .. lunits[i].unitName .. ' alive!')
						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  
					--print('yielding at: ' .. tostring(i))
					coroutine.yield()
					--print('resuming at: ' .. tostring(i))
				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)
		local newTable = {}
		
		newTable['startTime'] =  timer.getAbsTime()
		
		if type(event) == 'string' then -- if name of an object. 
			--env.info('event')
			local newObject
			local newType = 'group'
			if Group.getByName(event) then
				newObject = Group.getByName(event)
			elseif StaticObject.getByName(event) then
				newObject = StaticObject.getByName(event)
				newType = 'static'
			--	env.info('its static')
			else
				env.info('WTF')
				return false
			end
			

			
			newTable.name = newObject:getName()
			newTable.groupId = tonumber(newObject:getID())
			newTable.groupName = newObject:getName()
			local unitOneRef 
			if newType == '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 Group.getByName(newTable.groupName):isExist() then
					if catId == Group.getByName(newTable.groupName):getCategory() then
						newTable['category'] = string.lower(catData)
					end
				elseif 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
					mistAddedGroups[index] = nil
				end
			end
			
			if gfound == false then
				newTable.uncontrolled = false
			end
			
			newTable.units = {}
			if newType == '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].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.units[1] = {}
				newTable.units[1].unitName = newObject:getName()
				
				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
				
				----- 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
						mistAddedObjects[index] = nil
					end
				end
			end
		end
	
		newTable['timeAdded'] = timer.getAbsTime() -- only on the dynGroupsAdded table. For other reference, see start time
		--mist.debug.dumpDBs()
		--end
		return newTable
	end
	

	
	local function checkSpawnedEvents()
		if #tempSpawnedUnits > 0 then
			local groupsToAdd = {}
			local ltemp = tempSpawnedUnits
			local ltable = table

			local updatesPerRun = math.ceil(#tempSpawnedUnits/20)
			if updatesPerRun < 5 then
				updatesPerRun = 5
			end
			--env.info(#groupsToAdd)
			--env.info(#tempSpawnedUnits)
			for x = 1, #tempSpawnedUnits do
				local spawnedObj = ltemp[x]
				if spawnedObj and spawnedObj:isExist() then
					local found = false
					for index, name in pairs(groupsToAdd) do
						if spawnedObj:getCategory() == 1 then -- normal groups
							if mist.stringMatch(spawnedObj:getGroup():getName(), name) == true then
								found = true
								break
							end
						elseif spawnedObj:getCategory() == 3 then -- static objects
							if mist.stringMatch(spawnedObj:getName(), name) == true then
								found = true
								break
							end
						end
					end
					if found == false then
						if spawnedObj:getCategory() == 1 then -- normal groups
							groupsToAdd[#groupsToAdd + 1] = spawnedObj:getGroup():getName()
						elseif  spawnedObj:getCategory() == 3 then -- static objects
							groupsToAdd[#groupsToAdd + 1] = spawnedObj:getName()
						end
					end
				end
				

				table.remove(ltemp, x)
				if x%updatesPerRun == 0 then  
					coroutine.yield()
				end 
				
			end
			
			
			if #groupsToAdd > 0 then
				for groupId, groupName in pairs(groupsToAdd) do
					if not mist.DBs.groupsByName[groupName] or mist.DBs.groupsByName[groupName] and mist.DBs.groupsByName[groupName].startTime + 10 < timer.getAbsTime() then
						writeGroups[#writeGroups + 1] = dbUpdate(groupName)
					end
				end
			end
		end
	end
	
	
	local function updateDBTables()
		local i = 0
		for index, newTable in pairs(writeGroups) do
			i = i + 1
		end
		local savesPerRun = math.ceil(i/10)
		if savesPerRun < 5 then
			savesPerRun = 5
		end
		if i > 0 then
			local ldeepCopy = mist.utils.deepCopy
			for x = 1, i do
				local newTable = writeGroups[x]
				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
				for newId, newUnitData in pairs(newTable.units) do
					newUnitData.category = mistCategory
					if newUnitData.unitId then
						mist.DBs.unitsById[tonumber(newUnitData.unitId)] = ldeepCopy(newUnitData) 
					end
					
					mist.DBs.unitsByName[newUnitData.unitName] = ldeepCopy(newUnitData)
					mist.DBs.unitsByCat[mistCategory][#mist.DBs.unitsByCat[mistCategory] + 1] = ldeepCopy(newUnitData)
					mist.DBs.unitsByNum[#mist.DBs.unitsByNum + 1] = ldeepCopy(newUnitData) 
				end
					-- this is a really annoying DB to populate. Gotta create new tables in case its missing
				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
				mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory][#mist.DBs.units[newTable.coalition][(newTable.country)][mistCategory] + 1] = ldeepCopy(newTable)
				
				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
		end
	end
	
	local update_alive_units_counter = 0
	local write_DB_table_counter = 0
	local check_spawn_events_counter = 0
	
	-- THE MAIN FUNCTION --   Accessed 100 times/sec.
	mist.main = function()
		timer.scheduleFunction(mist.main, {}, timer.getTime() + 0.01)  --reschedule first in case of Lua error
		----------------------------------------------------------------------------------------------------------
		--area to add new stuff in
		write_DB_table_counter = write_DB_table_counter + 1
		if write_DB_table_counter == 10 then 
			
			write_DB_table_counter = 0
			
			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

		check_spawn_events_counter = check_spawn_events_counter + 1
		if check_spawn_events_counter == 10 then 
			
			check_spawn_events_counter = 0
			
			if not coroutines.checkSpawnedEvents then
				coroutines['checkSpawnedEvents'] = coroutine.create(checkSpawnedEvents)
			end
			
			coroutine.resume(coroutines.checkSpawnedEvents)
			
			if coroutine.status(coroutines.checkSpawnedEvents) == 'dead' then
				coroutines.checkSpawnedEvents = nil
			end
		end
		
		-----------------------------------------------------------------------------------------------------------
		--updating alive units
		update_alive_units_counter = update_alive_units_counter + 1
		if update_alive_units_counter == 5 then 
			update_alive_units_counter = 0
			
			if not coroutines.update_alive_units then
				coroutines['update_alive_units'] = coroutine.create(update_alive_units)
			end
			
			coroutine.resume(coroutines.update_alive_units)
			
			if coroutine.status(coroutines.update_alive_units) == 'dead' then
				coroutines.update_alive_units = nil
			end
		end
		
		mist.do_scheduled_functions()
	end -- end of mist.main
	--------------------------------------------
	------------ mist dyn add stuff for coroutines
	local mistGpId = 7000
	local mistUnitId = 7000
	local mistDynAddIndex = 1
	
	 mist.nextGroupId = 1
	 mist.nextUnitId = 1
	
	mist.getNextUnitId = function()
		mist.nextUnitId = mist.nextUnitId + 1
		if mist.nextUnitId > 6900 then
			mist.nextUnitId = 14000
		end
		return mist.nextUnitId
	end
	
	mist.getNextGroupId = function()
		mist.nextGroupId = mist.nextGroupId + 1
		if mist.nextGroupId > 6900 then
			mist.nextGroupId = 14000
		end
		return mist.nextGroupId
	end
	
	local function groupSpawned(event)
		if event.id == world.event.S_EVENT_BIRTH and timer.getTime0() < timer.getAbsTime()then -- dont need to add units spawned in at the start of the mission if mist is loaded in init line
			table.insert(tempSpawnedUnits,(event.initiator))
		end
	end

	
	
	
	mist.dynAddStatic = function(staticObj)
		local newObj = {}
		newObj.groupId = staticObj.groupId
		newObj.category = staticObj.category
		newObj.type = staticObj.type
		newObj.unitId = staticObj.unitId
		newObj.y = staticObj.y
		newObj.x = staticObj.x
		newObj.heading = staticObj.heading
		newObj.name = staticObj.name
		newObj.dead = staticObj.dead
		newObj.country = staticObj.country
		newObj.clone = staticObj.clone
		newObj.shape_name = newObj.shape_name
		
		if staticObj.units then -- if its mist format
			newObj.groupId = staticObj.units[1].groupId
			newObj.category = staticObj.units[1].category
			newObj.type = staticObj.units[1].type
			newObj.unitId = staticObj.units[1].unitId
			newObj.y = staticObj.units[1].y
			newObj.x = staticObj.units[1].x
			newObj.heading = staticObj.units[1].heading
			newObj.name = staticObj.units[1].name
			newObj.dead = staticObj.units[1].dead
			newObj.country = staticObj.units[1].country
			newObj.shape_name = staticObj.units[1].shape_name
		end


		newObj.country = staticObj.country
		
		if not newObj.country then
			return false
		end
		
		local newCountry
		for countryName, countryId in pairs(country.id) do
			if type(newObj.country) == 'string' then
				if tostring(countryName) == string.upper(newObj.country) then
					newCountry = countryName
				end
			elseif type(newObj.country) == 'number' then
				if countryId == newObj.country then
					newCountry = countryName
				end
			end
		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 = mistDynAddIndex + 1
			newObj.name = (newCountry .. ' static ' .. mistDynAddIndex)
		end
		
		if not newObj.dead then
			newObj.dead = false
		end
		
		if not newObj.heading then
			newObj.heading = math.random(360)
		end
		
		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
			coalition.addStaticObject(country.id[newCountry], newObj)

			return newObj.name
		end	
		return false
	end

	mist.dynAdd = function(newGroup) -- 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
--
	
	
	--env.info('dynAdd')
	local cntry = newGroup.country
	local groupType = newGroup.category
	local newCountry = ''
	-- validate data
	for countryName, countryId in pairs(country.id) do
		if type(cntry) == 'string' then
			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
		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 = mistDynAddIndex + 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(tostring(cntry) .. tostring(typeName) .. mistDynAddIndex)
	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
				--[[env.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 ~= 'RADIO' or 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
	
	
	
	-- sanitize table
	newGroup.groupName = nil
	newGroup.clone = nil
	newGroup.category = nil
	newGroup.country = nil
	
	newGroup.tasks = {}
	newGroup.visible = false
	
	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.name
	
	end

---------------------------------------------------------------------------------------------
--Modified Slmod task scheduler, superior to timer.scheduleFunction
	
	local Tasks = {}
	local task_id = 0
	--[[ mist.scheduleFunction:
	int id = mist.schedule_task(f function, vars table, t number, rep number, st number)
	id - integer id of this function task
	f - function to run
	vars - table of vars for that function
	t - time to run function
	rep - time between repetitions of this function (OPTIONAL)
	st - time when repetitions of this function will stop automatically (OPTIONAL)
	]]
	mist.scheduleFunction = function(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
		task_id = task_id + 1
		table.insert(Tasks, {f = f, vars = vars, t = t, rep = rep, st = st, id = task_id})
		return task_id
	end
	
	-- removes a scheduled function based on the function's id.  returns true if successful, false if not successful.
	mist.removeFunction = function(id)
		local i = 1
		while i <= #Tasks do
			if Tasks[i].id == id then
				table.remove(Tasks, i)
			else
				i = i + 1
			end
		end
	end

	--------------------------------------------------------------------------------------------------------------------
	-- not intended for users to use this function.
	mist.do_scheduled_functions = function()
		local i = 1
		while i <= #Tasks do
			if not Tasks[i].rep then -- not a repeated process
				if Tasks[i].t <= timer.getTime() then 
					local Task = Tasks[i] -- local reference
					table.remove(Tasks, i)
					local err, errmsg = pcall(Task.f, unpack(Task.vars, 1, table.maxn(Task.vars)))
					if not err then
						env.info('mist.scheduleFunction, error in scheduled function: ' .. 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 Tasks[i].st and Tasks[i].st <= timer.getTime() then   --if a stoptime was specified, and the stop time exceeded
					table.remove(Tasks, i) -- stop time exceeded, do not execute, do not increment i
				elseif Tasks[i].t <= timer.getTime() then
					local Task = Tasks[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
						env.info('mist.scheduleFunction, error in scheduled function: ' .. errmsg)
					end
					--Tasks[i].f(unpack(Tasks[i].vars, 1, table.maxn(Tasks[i].vars)))  -- do the task
					i = i + 1
				else
					i = i + 1
				end
			end
		end
	end

	

	local idNum = 0

	--Simplified event handler
	mist.addEventHandler = function(f) --id is optional!
		local handler = {}
		idNum = idNum + 1
		handler.id = idNum
		handler.f = f
		handler.onEvent = function(self, event)
			self.f(event)
		end
		world.addEventHandler(handler)
	end

	mist.removeEventHandler = function(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	

	mist.addEventHandler(groupSpawned)
--	mist.scheduleFunction(checkSpawnedEvents, {}, timer.getTime() + 5, 1)
	
end
------------------------------------------------------------------------------------------------------------


----------------------------------------------------------------------------------------------
-- Utils- conversion, Lua utils, etc.
mist.utils = {}

mist.utils.toDegree = function(angle)
	return angle*180/math.pi
end

mist.utils.toRadian = function(angle)
	return angle*math.pi/180
end

mist.utils.metersToNM = function(meters)
	return meters/1852
end

mist.utils.metersToFeet = function(meters)
	return meters/0.3048
end

mist.utils.NMToMeters = function(NM)
	return NM*1852
end

mist.utils.feetToMeters = function(feet)
	return feet*0.3048
end

mist.utils.mpsToKnots = function(mps)
	return mps*3600/1852
end

mist.utils.mpsToKmph = function(mps)
	return mps*3.6
end

mist.utils.knotsToMps = function(knots)
	return knots*1852/3600
end

mist.utils.kmphToMps = function(kmph)
	return kmph/3.6
end

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

function mist.utils.makeVec3(Vec2, y)
	if not Vec2.z then
		if not y then
			y = 0
		end
		return {x = Vec2.x, y = y, z = Vec2.y}
	else
		return {x = Vec2.x, y = Vec2.y, z = Vec2.z}  -- it was already Vec3, actually.
	end
end

function mist.utils.makeVec3GL(Vec2, offset)
	local adj = offset or 0
	
	if not Vec2.z then
		return {x = Vec2.x, y = (land.getHeight(Vec2) + adj), z = Vec2.y}
	else
		return {x = Vec2.x, y = (land.getHeight({x = Vec2.x, y = Vec2.z}) + adj), z = Vec2.z}
	end
end

mist.utils.zoneToVec3 = function(zone)
	local new = {}
	if type(zone) == 'table' and zone.point then
		new.x = zone.point.x
		new.y = zone.point.y
		new.z = zone.point.z
		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

-- gets heading-error corrected direction from point along vector vec.
function mist.utils.getDir(vec, point)
	local dir = math.atan2(vec.z, vec.x)
	dir = dir + mist.getNorthCorrection(point)
	if dir < 0 then
		dir = dir + 2*math.pi  -- put dir in range of 0 to 2*pi
	end
	return dir
end

-- gets distance in meters between two points (2 dimensional)
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

-- gets distance in meters between two points (3 dimensional)
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





--from http://lua-users.org/wiki/CopyTable
mist.utils.deepCopy = function(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

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

-- porting in Slmod's dostring
mist.utils.dostring = function(s)
	local f, err = loadstring(s)
	if f then
		return true, f()
	else
		return false, err
	end
end


--[[ mist.utils.typeCheck(fname, type_tbl, var_tbl)
Type-checking function:
Checks a var_tbl to a type_tbl.  Returns true if the var_tbl passes the type check, returns false plus an error message if the var_tbl fails.

type_tbl examples: 
type_tbl = { {'table', 'number'}, 'string', 'number', 'number', {'string', 'nil'}, {'number', 'nil'} } 
Compare to a var_tbl with up to six entries; var_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.

Another example:
type_tbl = { {'text', 'msg', 'text_out'} = 'string', display_time = 'number', display_mode = {'string', 'nil'} coa = {'string', 'nil'}}

var_tbl must have a string at one of the following table keys: "text", "msg", or "text_out".  var_tbl must have a number at table key "display_time", 
the table key "display_mode" must be either a string or nil, and the table key "coa" must be either a string or nil.
]]
function mist.utils.typeCheck(fname, type_tbl, var_tbl)
	--env.info('type check')
	for type_key, type_val in pairs(type_tbl) do
		--print('type_key:')
		--print(type_key)
		--print('type_val:')
		--print(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
			--print('err_msg, before and after:')
			--print(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
			--print(err_msg)
		else
			--print('err_msg, before and after:')
			--print(err_msg)
			err_msg = err_msg .. type_tbl[type_key]
			--print(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

--porting in Slmod's "safestring" basic serialize
mist.utils.basicSerialize = function(s)
	if s == nil then
		return "\"\""
	else
		if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'table') or (type(s) == 'userdata') ) then
			return tostring(s)
		elseif type(s) == 'string' then
			s = string.format('%q', s)
			return s
		end
	end	
end

--porting in Slmod's serialize_slmod
mist.utils.serialize = function(name, value, level)
	-----Based on ED's serialize_simple2
	local basicSerialize = function (o)
	  if type(o) == "number" then
		return tostring(o)
	  elseif type(o) == "boolean" then
		return tostring(o)
	  else -- assume it is a string
		return mist.utils.basicSerialize(o)
	  end
	end
	
	local serialize_to_t = function (name, value, level)
	----Based on ED's serialize_simple2


	  local var_str_tbl = {}
	  if level == nil then level = "" end
	  if level ~= "" then level = level.."  " end
	  
	  table.insert(var_str_tbl, level .. name .. " = ")
	  
	  if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then
		table.insert(var_str_tbl, basicSerialize(value) ..  ",\n")
	  elseif type(value) == "table" then
		  table.insert(var_str_tbl, "\n"..level.."{\n")
		  
		  for k,v in pairs(value) do -- serialize its fields
			local key
			if type(k) == "number" then
			  key = string.format("[%s]", k)
			else
			  key = string.format("[%q]", k)
			end

			table.insert(var_str_tbl, 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
		env.info("Cannot serialize a "..type(value))
	  end
	  return var_str_tbl
	end
	
	local t_str = serialize_to_t(name, value, level)
	
	return table.concat(t_str)
end

-- porting in slmod's serialize_wcycles
mist.utils.serializeWithCycles = function(name, value, saved)
	--mostly straight out of Programming in Lua
	local basicSerialize = function (o)
		if type(o) == "number" then
			return tostring(o)
		elseif type(o) == "boolean" then
			return tostring(o)
		else -- assume it is a string
			return 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

-- porting in Slmod's serialize_slmod2
mist.utils.oneLineSerialize = function(tbl)  -- serialization of a table all on a single line, no comments, made to replace old get_table_string function
	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
				env.info('unable to serialize value type ' .. mist.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind))
			end
		
		end
		tbl_str[#tbl_str + 1] = '}'
		return table.concat(tbl_str)
	end
end

--Function to create string for viewing the contents of a table -NOT for serialization
mist.utils.tableShow = function(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
					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

mist.debug = {}

mist.debug.dump_G = function(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()
		local errmsg = 'mist.debug.dump_G wrote data to ' .. fdir
		env.info(errmsg)
		trigger.action.outText(errmsg, 10)
	else
		local errmsg = 'Error: insufficient libraries to run mist.debug.dump_G, you must disable the sanitization of the io and lfs libraries in ./Scripts/MissionScripting.lua'
		env.info(errmsg)
		trigger.action.outText(errmsg, 10)
	end
end

mist.debug.writeData = function(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()
		local errmsg = 'mist.debug.writeData wrote data to ' .. fdir
		env.info(errmsg)
		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'
		env.info(errmsg)
		trigger.action.outText(errmsg, 10)
	end
end

mist.debug.dumpDBs = function()
	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

-----------------------------------------------------------------------------------------------------------------
--3D Vector manipulation
mist.vec = {}

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

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

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

mist.vec.scalar_mult = mist.vec.scalarMult

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

mist.vec.cp = function(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

mist.vec.mag = function(vec)
	return (vec.x^2 + vec.y^2 + vec.z^2)^0.5
end

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

mist.vec.rotateVec2 = function(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
---------------------------------------------------------------------------------------------------------------------------

-- acc- the accuracy of each easting/northing.  0, 1, 2, 3, 4, or 5.
mist.tostringMGRS = function(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.
]]
mist.tostringLL = function(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.]]
mist.tostringBR = function(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

mist.getNorthCorrection = function(point)  --gets the correction needed for true north
	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

mist.getUnitSkill = function(unitName)
	if Unit.getByName(unitName) then
		local lunit = Unit.getByName(unitName)
		for name, data in pairs(mist.DBs.unitsByName) do
			if name == unitName and data.type == lunit:getTypeName() and data.unitId == lunit:getID() and data.skill then
				return data.skill
			end
		end
	end
	return false
end

function mist.getGroupPoints(groupname)   -- if groupname exists in env.mission, then returns table of the group's points in numerical order, such as: { [1] = {x = 299435.224, y = -1146632.6773}, [2] = { x = 663324.6563, y = 322424.1112}}
	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.name and mist.stringMatch(group_data.name, groupname) 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



--[[ table attitude = getAttitude(string unitname) -- will work on any unit, even if not an aircraft.

attitude = { 
	Heading = number, -- in radians, range of 0 to 2*pi, relative to true north
	Pitch = number, -- in radians, range of -pi/2 to pi/2
	Roll = number, -- in radians, range of 0 to 2*pi, right roll is positive direction
	
	--Yaw, AoA, ClimbAngle - relative to earth reference- DOES NOT TAKE INTO ACCOUNT WIND.
	Yaw = number, -- in radians, range of -pi to pi, right yaw is positive direction
	AoA = number, -- in radians, range of -pi to pi, rotation of aircraft to the right in comparison to flight direction being positive
	ClimbAngle = number, -- in radians, range of -pi/2 to pi/2
	
	--Maybe later?
	AxialVel = table, velocity of the aircraft transformed into directions of aircraft axes
	Speed = number -- absolute velocity in meters/sec

	} 

]]
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
		env.info('unit:getPosition() is nil!')
	end
end

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

function mist.getPitch(unit)
	local unitpos = unit:getPosition()
	if unitpos then
		return math.asin(unitpos.x.y)
	end
end

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

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

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

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
-----------------------------------------------------------------------------------------------------------
-- Database building
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
	mist.DBs.missionData['bullseye'] = {['red'] = {}, ['blue'] = {}} -- 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['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] = {}
										mist.DBs.units[coa_name][countryName][category][group_num]["groupName"] = group_data.name
										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]["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] = {}
											units_tbl[unit_num]["unitName"] = unit_data.name
											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]["shape_name"] = unit_data.shape_name
											
											units_tbl[unit_num]["groupName"] = group_data.name
											units_tbl[unit_num]["groupId"] = group_data.groupId
										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.aliveUnits = {}  -- will be filled in by the "update_alive_units" coroutine in mist.main.

mist.DBs.removedAliveUnits = {} -- will be filled in by the "update_alive_units" coroutine in mist.main.
-- 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))
							--print('inserting ' .. 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)
							end
						end
					end
				end
			end
		end
	end
end

--------------
-------- mist unitID funcs 
do
	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

--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 = {}
	
	mt.__newindex = function(t, key, val)
		---------------------------------------------------------------
		local original_key = key --only for duplicate runtime IDs.
		local key_ind = 1
		while mist.DBs.deadObjects[key] do
			--print('duplicate runtime id of previously dead object- key: ' .. tostring(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
			--print('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
			--print('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...
			--print('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...
						--print('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

-- Event handler to start creating the dead_objects table
do 

	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
					--print('duplicate runtime id of previously dead object- id: ' .. tostring(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
					--print('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
					--print('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...
					--print('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...
								--print('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

	
	mist.addEventHandler(addDeadObject)
	
end





function mist.makeUnitTable(tbl)

--[[
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"
"UK"
"Denmark"
"USA"
"Georgia"
"Germany"
"Belgium"
"Canada"
"France"
"Israel"
"Ukraine"
"Russia"
"South Osetia"
"Abkhazia"
"Italy"
]]

	--Assumption: will be passed a table of strings, sequential
	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'] = true  --add the processed flag
	return units_tbl
end


mist.getDeadMapObjsInZones = function(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


mist.getDeadMapObjsInPolygonZone = function(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

mist.flagFunc = {}

mist.flagFunc.mapobjs_dead_zones = function(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 = {'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 -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 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



mist.flagFunc.mapobjs_dead_polygon = function(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 = {'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 -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 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



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

mist.getUnitsInPolygon = function (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.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
]]
-- type_tbl
	local type_tbl = {
		[{'units', 'unit'}] = 'table', 
		[{'zone', 'polyzone'}] = 'table', 
		flag = {'number', 'string'},  
		stopflag = {'number', 'string', 'nil'},  
		[{'maxalt', 'alt'}] = {'number', 'nil'}, 
		interval = {'number', 'nil'}, 
		[{'req_num', 'reqnum'}] = {'number', 'nil'},
		toggle = {'boolean', '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 -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
	
	
	if not units.processed then -- run unit table short cuts
		units = mist.makeUnitTable(units)
		--mist.debug.writeData(mist.utils.serialize,{'vars', vars}, 'vars.txt')
	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 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 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) == false) 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}}, timer.getTime() + interval)
		end
	end
	
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.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 = {'number', 'string', 'nil'}, 
		[{'zone_type', 'zonetype'}] = {'string', 'nil'}, 
		[{'req_num', 'reqnum'}] = {'number', 'nil'},
		interval = {'number', 'nil'},
		toggle = {'boolean', '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 -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
	
	if not units.processed then -- run unit table short cuts
		units = mist.makeUnitTable(units)
	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 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}}, timer.getTime() + interval)
		end
	end
	
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.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 = {'number', 'string', 'nil'}, 
		[{'zone_type', 'zonetype'}] = {'string', 'nil'}, 
		[{'req_num', 'reqnum'}] = {'number', 'nil'},
		interval = {'number', 'nil'},
		toggle = {'boolean', '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 -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
	
	if not units.processed then -- run unit table short cuts
		units = mist.makeUnitTable(units)
	end
	
	if not zone_units.processed then -- run unit table short cuts
		zone_units = mist.makeUnitTable(zone_units)
	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 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}}, timer.getTime() + interval)
		end
	end
	
end


mist.getUnitsLOS = function(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 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 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

mist.flagFunc.units_LOS = function(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 = {'number', 'string', 'nil'}, 
		[{'req_num', 'reqnum'}] = {'number', 'nil'}, 
		interval = {'number', 'nil'}, 
		radius = {'number', 'nil'},
		toggle = {'boolean', '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 -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
	
	
	if not unitset1.processed then -- run unit table short cuts
		unitset1 = mist.makeUnitTable(unitset1)
	end
	
	if not unitset2.processed then -- run unit table short cuts
		unitset2 = mist.makeUnitTable(unitset2)
	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 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}}, timer.getTime() + interval)
		end
	end
end

mist.flagFunc.group_alive = function(vars)
--[[vars
groupName
flag
toggle
interval
stopFlag

]]
	local type_tbl = {
	[{'group', 'groupname', 'gp', 'groupName'}] = 'string', 
	flag = {'number', 'string'}, 
	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 -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):isActive() 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

mist.flagFunc.group_dead = function(vars)
	local type_tbl = {
	[{'group', 'groupname', 'gp', 'groupName'}] = 'string', 
	flag = {'number', 'string'}, 
	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 -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 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

mist.flagFunc.group_alive_less_than = function(vars)
	local type_tbl = {
	[{'group', 'groupname', 'gp', 'groupName'}] = 'string',
	percent = 'number',
	flag = {'number', 'string'}, 
	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 -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):isActive() 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

mist.flagFunc.group_alive_more_than = function(vars)
	local type_tbl = {
	[{'group', 'groupname', 'gp', 'groupName'}] = 'string',
	percent = 'number',
	flag = {'number', 'string'}, 
	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 -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):isActive() 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



--Gets the average position of a group of units (by name)
mist.getAvgPos = function(unitNames)
	local avgX, avgY, avgZ, totNum = 0, 0, 0, 0
	for i = 1, #unitNames do
		local unit = Unit.getByName(unitNames[i])
		if unit then
			oneUnit = true  -- at least one unit existed.
			local pos = unit:getPosition().p
			avgX = avgX + pos.x
			avgY = avgY + pos.y
			avgZ = avgZ + pos.z
			totNum = totNum + 1
		end	
	end
	if totNum ~= 0 then
		return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum}
	end
end

mist.getAvgGroupPos = function(groupName)
	if type(groupName) == 'string' and Group.getByName(groupName) 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

---------------------------------------------------------------------------------------
-- demos
mist.demos = {}

mist.demos.printFlightData = function(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
					xAcc = (unitVel.x - prevVel.x)/(curTime - prevTime)
					yAcc = (unitVel.y - prevVel.y)/(curTime - prevTime)
					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
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
--start of Mission task functions
--*****************************************************
mist.ground = {}
mist.fixedWing = {}
mist.heli = {}
mist.air = {}
mist.air.fixedWing = {}
mist.air.heli = {}

mist.goRoute = function(group, path)
	local misTask = { 
		id = 'Mission', 
		params = { 
			route = { 
				points = mist.utils.deepCopy(path),
			},	
		},
	}	
	if type(group) == 'string' then
		group = Group.getByName(group)
	end
	local groupCon = group:getController()
	if groupCon then
		groupCon:setTask(misTask)
		return true
	end
	--Controller.setTask(groupCon, misTask)
	return false
end

function mist.getGroupRoute(groupname, task)   -- same as getGroupPoints but returns speed and formation type along with vec2 of point}
	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.name and mist.stringMatch(group_data.name, groupname) 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
										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



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


-- No longer accepts path
mist.ground.buildWP = function(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

mist.fixedWing.buildWP = function(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

mist.heli.buildWP = function(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

mist.groupToRandomPoint = function(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

mist.groupRandomDistSelf = function(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

mist.groupToRandomZone = function(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

mist.isTerrainValid = function(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

mist.terrainHeightDiff = function(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



mist.groupToPoint = function(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


mist.getLeadPos = function(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 of Mission task functions
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------



--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
-------MESAGGES------
--[[
local msg = {}
msg.text = string (required)
msg.displayTime = number (required)
msg.msgFor = table (required)
msg.name = string (optional)
mist.message.add(msg) 

msgFor accepts a table 


]]


--[[ 
Need to change to this format...
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'}}}
]]


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

]]
do
	local messageList = {}
	local messageDisplayRate = 0.1 -- 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 messageID = 0
		
	mist.message = {
	

		add = function(vars)
			local function msgSpamFilter(recList, spamBlockOn)
				for id, name in pairs(recList) do
					if name == spamBlockOn then
					--	env.info('already on recList')
						return recList
					end
				end
				--env.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()
			
			
			if vars.sound then -- has no function yet, basic idea is to play the sound file for designated players. Had considered a more complex system similar to On Station audio messaging with staggering mesages, but that isn't entirely needed.
			-- additionally we could have an "outSound" function that will do just the audio alone with no text
				new.sound = vars.sound
				new.playAudio = true
			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, clientId) -- 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 string.lower(list) == string.lower(clientDataVal) or list == 'all' then
											if typeData == clientData.type then
												found = true
												newMsgFor = msgSpamFilter(newMsgFor, clientId) -- 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)
								--table.insert(newMsgFor, coaData)
							-- added redca or blueca to list
							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
							--env.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
							return messageList[i].messageID
						end
					end
				end
			end
			
			messageID = messageID + 1
			new.messageID = messageID

			--mist.debug.writeData(mist.utils.serialize,{'msg', new}, 'newMsg.txt')
			
			messageList[#messageList + 1] = new
			
			local mt = { __index =  mist.message}
			setmetatable(new, mt)  
								
								
		
			return messageID
			
		end,
				
		-- remove = function(self)  -- not a self variable in this case; this function should be passed a self variable and does not need a message id;  see example below.
			-- for i, msgData in pairs(messageList) do
				-- if messageList[i].messageID == self then
					-- table.remove(messageList, i)
					-- return true --removal successful
				-- end
			-- end
			-- return false -- removal not successful this script fails at life!
		-- end,
		
		------------------------------------------------------
		------------------------------------------------------
		-- proposed changes:
		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,
		
		
		
		------------------------------------------------------
		------------------------------------------------------
	}	
	
	-----------------------------------------------------------------
	-- No longer necessary, use the self:remove() instead.
	
	-- Local function now
	-- local function mistMSGDestroy(self)  -- not a self variable
		-- 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
	-------------------------------------------------------------------------------
	
	-- local function now

	--[[
	audio design concept
	Need to stagger messages?
	
	]]
	
	-- local function now
	local function mistdisplayV3() -- adding audio file support
		-- CA roles
		local caMessageRed = false
		local caMessageBlue = false
		local audioRed = false
		local audioBlue = false
		local audioPlaying = false

		if #messageList > 0 then
			--mist.debug.writeData(mist.utils.serialize,{'msg', messageList}, 'messageList.lua')
			for messageId, messageData in pairs(messageList) do
				if messageData.displayedFor > messageData.displayTime then
					--mistMSGDestroy(messageData)
					messageData:remove()  -- now using the remove/destroy function.
					
				else
					if messageList[messageId].displayedFor then
						messageList[messageId].displayedFor = messageList[messageId].displayedFor + messageDisplayRate
					end
				--[[else
					if messageData.fileName then
						audioPlaying = true
					end]]
				end

				
			end
			for coaData, coaId in pairs(coalition.side) do
				local CAmsg = {}
				local newestMsg = 100000000
				
				for messageIndex, messageData in pairs(messageList) do 
					for forIndex, forData in pairs(messageData.msgFor) do
					
						if coaData == forData then
							if messageData.addedAt < newestMsg then
								newestMsg = messageData.addedAt
							end
							if messageData.text then
								CAmsg[#CAmsg + 1] = messageData.text
								CAmsg[#CAmsg + 1] = '\n ---------------- \n'
							end
							if type(messageData.sound) == 'string' and messageData.addedAt + messageDisplayRate > timer.getTime() then
								if coaData == 'RED' then
									audioRed = true
									trigger.action.outSoundForCoalition(coalition.side.RED, messageData.sound)
								elseif coaData == 'BLUE' then
									audioBlue = true
									trigger.action.outSoundForCoalition(coalition.side.BLUE, messageData.sound)
								end
							end							
						end
					end
				end
				if #CAmsg > 0 then
					if newestMsg < timer.getTime() + .5 then
						if coaData == 'BLUE' then
							trigger.action.outTextForCoalition(coalition.side.BLUE, table.concat(CAmsg), 1)
							caMessageBlue = true
						elseif coaData == 'RED' then
							trigger.action.outTextForCoalition(coalition.side.RED, table.concat(CAmsg), 1)
							caMessageRed = true
						end
					end
				end	
			end
			for clientId, clientData in pairs(mist.DBs.humansById) do	
				local clientDisplay = {}
		
				for messageIndex, messageData in pairs(messageList) do 
					for forIndex, forData in pairs(messageData.msgFor) do
						if clientId == forData and Group.getByName(clientData.groupName) then
							if messageData.text then
								clientDisplay[#clientDisplay + 1] = messageData.text
								clientDisplay[#clientDisplay + 1] = '\n ---------------- \n'
							end
							if string.lower(clientData.coalition) == 'red' and audioRed == false or string.lower(clientData.coalition) == 'blue' and audioBlue == false then
								if type(messageData.sound) == 'string' and messageData.addedAt + messageDisplayRate > timer.getTime() then
									trigger.action.outSoundForGroup(clientData.groupId, messageData.sound)
								end
							end
						end
					end
				end
				if #clientDisplay > 0 then
					trigger.action.outTextForGroup(clientData.groupId, table.concat(clientDisplay), 1)
					
				elseif #clientDisplay == 0 then
					if clientData.coalition == 'blue' and caMessageBlue == true then
						trigger.action.outTextForGroup(clientData.groupId, 'Blue CA Recieving Message', 1) -- I'd rather this recive the message with a note that its for CA than a blank message box.
					elseif clientData.coalition == 'red' and caMessageRed == true then
						trigger.action.outTextForGroup(clientData.groupId, 'Red CA Recieving Message', 1)
					end
				end
			
				
			end
		end
		
	end
	

	
	mist.scheduleFunction(mistdisplayV3, {}, timer.getTime() + messageDisplayRate, messageDisplayRate) -- add this to the main mist thing
	
end
-- End of message system
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
-- Beginning of coordinate messages
--[[
Design:

Already have:
mist.tostringBR = function(az, dist, alt, metric)
mist.tostringLL = function(lat, lon, acc, DMS)
mist.tostringMGRS = function(MGRS, acc)

Need:
mist.getMGRSString(UnitNameTable, acc)
mist.getLeadingMGRSString(UnitNameTable, dir, radius, acc)

mist.getLLString(UnitNameTable, acc)
mist.getLeadingLLString(UnitNameTable, dir, radius acc)

mist.getBRString(UnitNameTable, ref, alt, metric)
mist.getLeadingBRString(UnitNameTable, ref, alt, metric, dir, radius, acc)   -- vars versions?


mist.sendMGRSMsg(vars)
mist.sendLeadingMGRSMsg(vars)

mist.sendLLMsg(vars)
mist.sendLeadingLLMsg(vars)

mist.sendBRMsg(vars)
mist.sendLeadingBRMsg(vars)

]]

--[[RE-EXAMINE USAGE OF VARS FOR SIMPLE FUNCTIONS.
(Answer: the leading functions require a lot of input variables; maybe better to leave in vars
format for consistency.  Maybe individual variable specification could also be supported?)

]]


--[[ vars for mist.getMGRSString:
vars.units - table of unit names (NOT unitNameTable- maybe this should change).
vars.acc - integer between 0 and 5, inclusive
]]
mist.getMGRSString = function(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.


]]
mist.getLLString = function(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.
]]
mist.getBRString = function(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
]]
mist.getLeadingPos = function(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.
]]
mist.getLeadingMGRSString = function(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.
]]
mist.getLeadingLLString = function(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.
]]
mist.getLeadingBRString = function(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

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

]]

--[[ 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
]]
mist.msgMGRS = function(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 string.find(text, '%%s') then  -- look for %s
		newText = string.format(text, s)  -- insert the coordinates into the message
	else  -- else, just append to the end.
		newText = text .. 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
]]
mist.msgLL = function(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 string.find(text, '%%s') then  -- look for %s
		newText = string.format(text, s)  -- insert the coordinates into the message
	else  -- else, just append to the end.
		newText = text .. 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
]]
mist.msgBR = function(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 string.find(text, '%%s') then  -- look for %s
		newText = string.format(text, s)  -- insert the coordinates into the message
	else  -- else, just append to the end.
		newText = text .. 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
]]
mist.msgBullseye = function(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
]]

mist.msgBRA = function(vars)
	if Unit.getByName(vars.ref) 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
]]
mist.msgLeadingMGRS = function(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 string.find(text, '%%s') then  -- look for %s
		newText = string.format(text, s)  -- insert the coordinates into the message
	else  -- else, just append to the end.
		newText = text .. 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
]]
mist.msgLeadingLL = function(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 string.find(text, '%%s') then  -- look for %s
		newText = string.format(text, s)  -- insert the coordinates into the message
	else  -- else, just append to the end.
		newText = text .. 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
]]
mist.msgLeadingBR = function(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 string.find(text, '%%s') then  -- look for %s
		newText = string.format(text, s)  -- insert the coordinates into the message
	else  -- else, just append to the end.
		newText = text .. s
	end
	
	mist.message.add{
		text = newText,
		displayTime = displayTime,
		msgFor = msgFor
	}
end


-- end of coordinate messages
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
-- start of sct Merge

do -- all function uses of group and unit Ids must be in this do statement
	
	
mist.groupTableCheck = function(groupData)
	local isOk = false
	
	if groupData.country then
		isOk = true
	end
	if groupData.category then
		isOk = true
	else
		isOk = false
	end
	if groupData.units then
		for unitId, unitData in pairs(groupData.units) do
			if unitData.x and unitData.y and unitData.type then
				isOk = true
			end
		end
	else
		isOk = false
	end
	
	return isOk
end	

mist.getCurrentGroupData = function(gpName)
	if Group.getByName(gpName) then
		local newGroup = Group.getByName(gpName)
		local newData = {}
		newData.name = gpName
		newData.groupId = tonumber(newGroup:getID())
		newData.category = newGroup:getCategory()
		newData.groupName = gpName
        
		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] = {}
			newData.units[unitNum]["unitId"] = tonumber(unitData:getID())
			newData.units[unitNum]['point'] = unitData.point
			newData.units[unitNum]['x'] = unitData:getPosition().p.x
			newData.units[unitNum]['y'] = unitData:getPosition().p.z
			newData.units[unitNum]["type"] = unitData:getTypeName()
			newData.units[unitNum]["skill"] = mist.getUnitSkill(unitData:getName())
			
			-- get velocity needed
			newData.units[unitNum]["unitName"] = unitData:getName()
			newData.units[unitNum]["heading"] = mist.getHeading(unitData, true) -- added to DBs
			newData.units[unitNum]['alt'] = unitData:getPosition().p.y
			newData.country = string.lower(country.name[unitData:getCountry()])
			newData.units[unitNum]['callsign'] = unitData:getCallsign()
		end

		return newData
	end

end

mist.getGroupData = function(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
			end
		end
		
		return newData	
	else
		env.info(gpName .. ' not found in mist.getGroupData')
		return
	end
end

mist.getPayload = function(unitName)
	if unitName and type(unitName) == 'string' 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.name then
											for unitIndex, unitData in pairs(group_data.units) do --group index
												if mist.stringMatch(unitName, unitData.name) == true then
													return unitData.payload
												end						
											end
										end
									end
								end
							end
						end
					end
				end
			end
		end
	else
		env.info('mist.getPayload got ' .. type(unitName))
		return false
	end
	env.info('mist.getPayload, payload not found')
	return 
end

mist.getGroupPayload = function(groupName)
	if groupName and type(groupName) == 'string' 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.name and mist.stringMatch(groupName, group_data.name) == true 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
		env.info('mist.getGroupPayload got ' .. type(groupName))
		return false
	end
	env.info('mist.getGroupPayload, payload not found')
	return 

end

mist.teleportToPoint = function(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
		env.info('teleportToPoint missing either vars.groupName or vars.gpName')
	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 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)
		elseif string.lower(action) == 'clone' then
			newGroupData = mist.getGroupData(gpName)
			newGroupData.clone = 'order66'
		else
			action = 'tele'
			newGroupData = mist.getCurrentGroupData(gpName)
		end
	else
		action = 'tele'
		newGroupData = vars.groupData
	end

	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
			env.info('mist.teleportToPoint; vars.point not a valid coordinate')
			return false
		end
	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
	end
	
	--tostring, tostring(),
	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
	
	if route then
		newGroupData.route = route
	end
	
	if string.lower(newGroupData.category) == 'static' then
		
		 return mist.dynAddStatic(newGroupData)
	end
	return mist.dynAdd(newGroupData)
	
end

mist.respawnInZone = function(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

mist.cloneInZone = function(gpName, zone, disperse, maxDisp)
	
	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
	return mist.teleportToPoint(vars)
end

mist.teleportInZone = function(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

mist.respawnGroup = function(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

mist.cloneGroup = function(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

mist.teleportGroup = function(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

mist.spawnRandomizedGroup = function(groupName, vars) -- need to debug
	if Group.getByName(groupName) 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
	
mist.randomizeNumTable = function(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
		env.info(newTable[i])
	end
	]]
	return newTable
end

mist.randomizeGroupOrder = function(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	

mist.ground.patrolRoute = function(vars)
	
	
	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
	
	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
	mist.goRoute(gpData, useRoute)
	
	return
end

mist.ground.patrol = function(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


mist.random = function(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



mist.stringMatch = function(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
		
		if s1 == s2 then
			return true
		else
			return false
		end
	else
		env.info('mist.stringMatch; 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

mist.DBs.const = {}

--[[
	['LAND'] = 1,
	['SHALLOW_WATER'] = 2,
	['WATER'] = 3,
	['ROAD'] = 4,
	['RUNWAY'] = 5	
]]
--[[mist.DBs.const.ME_SSE_terms = {
	['ME'] = {
		['vehicle'] = {'GROUND', 'GROUND_UNIT'},
		['plane'] = {'AIRPLANE'},
	},
	['SSE'] = {
	},

}]]


mist.DBs.const.callsigns = { -- not accessible by SSE, must use static list :-/
	['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',
					},
				},
			},
		},
	},

}
--[[ 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
mist.main()
env.info(('Mist version ' .. mist.majorVersion .. '.' .. mist.minorVersion .. '.' .. mist.build .. ' loaded.'))