--[[ v30 -Optimizations and fixes of refactoring v29 -Refactoring dbUpdate and related functions integration v28 added mist.getUnitSkill added mist.stringMatch added mist.groupTableCheck added mist.spawnRandomizedGroup added mist.randomizeGroupOrder added mist.getUnitsInPolygon v27 added mist.flagFunc.group_alive added mist.flagFunc.group_dead added mist.flagFunc.group_alive_less_than added mist.flagFunc.group_dead_less_than ]] --MiST Mission Scripting Tools mist = {} -- don't change these mist.majorVersion = 3 mist.minorVersion = 5 mist.build = 30 --[[ DB update refactor currently: mist.dynAdd adds table data it addToDBs table spawn events are monitored groupsToAdd table created addToDBs parsed and added to spawned events table spawnEvents table parsed and added to groupsToAdd table groupsToAdd then parsed and calls mist.dbUpdate() mist.dbUpdate() gets all of the group data and saves the group data to the mist.DBs plan/goal: make update into coroutine/remove in pairs loops seperate larger funcs into smaller funcs writeGroups contains all DB creation code ]] -------------------------------------------------------------------------------------------------------------- -- the main area do local coroutines = {} 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 tempSpawnedUnits = {} -- birth events added here local addToDBs = {} -- mist.dynAdd added here local sizeTempSpawnUnits = 0 local writeGroups = {} local function checkSpawnedEvents() if sizeTempSpawnUnits > 0 then local groupsToAdd = {} local ltemp = tempSpawnedUnits local ltable = table for index, gpData in pairs(addToDBs) do groupsToAdd[#groupsToAdd + 1] = gpData groupsToAdd[#groupsToAdd].mist = true -- ok now addToDBs[index] = nil end --env.info(#groupsToAdd) --env.info(#tempSpawnedUnits) for eventId, eventData in pairs(ltemp) do if eventData and eventData:isExist() then if eventData:getCategory() == 1 then -- normal groups local match = false if #groupsToAdd > 0 then -- if groups are expected for groupId, groupData in pairs(groupsToAdd) do -- iterate through known groups to add if (type(groupData) == 'string' and groupData == eventData:getGroup():getName()) or (type(groupData) == 'table' and groupData.name == eventData:getGroup():getName()) then -- already added, do nothing match = true break end end if match == false then -- hasn't been added groupsToAdd[#groupsToAdd + 1] = eventData:getGroup():getName() end else -- no groups added by mist groupsToAdd[#groupsToAdd + 1] = eventData:getGroup():getName() end elseif eventData:getCategory() == 3 then -- static objects local name = eventData:getName() local found = false for groupId, groupData in pairs(groupsToAdd) do if type(groupData) == 'string' and groupData == name then found = true break end end if found == false then groupsToAdd[#groupsToAdd + 1] = name end end end ltemp[eventId] = nil sizeTempSpawnUnits = sizeTempSpawnUnits - 1 end if #groupsToAdd > 0 then --env.info('doDBUpdate') for groupId, groupData in pairs(groupsToAdd) do if not mist.DBs.groupsByName[groupData] or mist.DBs.groupsByName[groupData] and mist.DBs.groupsByName[groupData].startTime + 10 < timer.getAbsTime() then writeGroups[#writeGroups + 1] = mist.dbUpdate(groupData) end end end end end local function updateDBTables() local i = 0 for index, newTable in pairs(writeGroups) do i = i + 1 end env.info(i) 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 writeDBTimer = 1 -- 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 ----------------------------------------------------------------------------------------------------------- --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)) sizeTempSpawnUnits = sizeTempSpawnUnits + 1 end end mist.dbUpdate = function(event) local groupData 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) --env.info('group') elseif StaticObject.getByName(event) then newObject = StaticObject.getByName(event) newType = 'static' -- env.info('its static') else env.info('WTF') return false end groupData = {} groupData.name = newObject:getName() groupData.groupId = tonumber(newObject:getID()) local unitOneRef if newType == 'static' then unitOneRef = newObject groupData.countryId = tonumber(newObject:getCountry()) groupData.coalitionId = tonumber(newObject:getCoalition()) groupData.category = 'static' else unitOneRef = newObject:getUnits() groupData.countryId = tonumber(unitOneRef[1]:getCountry()) groupData.coalitionId = tonumber(unitOneRef[1]:getCoalition()) groupData.category = tonumber(newObject:getCategory()) end groupData.units = {} if newType == 'group' then for unitId, unitData in pairs(unitOneRef) do groupData.units[unitId] = {} groupData.units[unitId].name = unitData:getName() groupData.units[unitId].x = mist.utils.round(unitData:getPosition().p.x) groupData.units[unitId].y = mist.utils.round(unitData:getPosition().p.z) groupData.units[unitId].alt = mist.utils.round(unitData:getPosition().p.y) groupData.units[unitId].alt_type = "BARO" groupData.units[unitId].heading = mist.getHeading(unitData, true) groupData.units[unitId].type = unitData:getTypeName() groupData.units[unitId].unitId = tonumber(unitData:getID()) groupData.units[unitId].skill = "HIGH" groupData.units[unitId].groupName = groupData.name groupData.units[unitId].groupId = groupData.groupId groupData.units[unitId].countryId = groupData.countryId groupData.units[unitId].coalitionId = groupData.coalitionId end else -- its a static groupData.units[1] = {} groupData.units[1].name = newObject:getName() groupData.units[1].x = mist.utils.round(newObject:getPosition().p.x) groupData.units[1].y = mist.utils.round(newObject:getPosition().p.z) groupData.units[1].alt = mist.utils.round(newObject:getPosition().p.y) groupData.units[1].heading = mist.getHeading(newObject, true) groupData.units[1].type = newObject:getTypeName() groupData.units[1].unitId = tonumber(newObject:getID()) groupData.units[1].groupName = groupData.name groupData.units[1].groupId = groupData.groupId groupData.units[1].countryId = groupData.countryId groupData.units[1].coalitionId = groupData.coalitionId end else -- its a table groupData = event end --mist.debug.writeData(mist.utils.serialize,{'DBs', groupData}, 'newUnits.txt') --for newGroupIndex, newGroupData in pairs(groupData) do local newTable = {} local tableSize = #mist.DBs.dynGroupsAdded + 1 newTable['name'] = groupData.name newTable['groupId'] = groupData.groupId newTable['startTime'] = timer.getAbsTime() newTable['task'] = groupData.task for countryData, countryId in pairs(country.id) do if groupData.country and string.upper(countryData) == string.upper(groupData.country) or countryId == groupData.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(groupData.name):isExist() then if catId == Group.getByName(groupData.name):getCategory() then newTable['category'] = string.lower(catData) end elseif StaticObject.getByName(groupData.name):isExist() then if catId == StaticObject.getByName(groupData.name):getCategory() then newTable['category'] = string.lower(catData) end end end newTable['units'] = {} for newUnitId, newUnitData in pairs(groupData.units) do newTable['units'][newUnitId] = {} newTable['units'][newUnitId]['unitName'] = newUnitData.name newTable['units'][newUnitId]['groupId'] = tonumber(groupData.groupId) newTable['units'][newUnitId]['heading'] = newUnitData.heading newTable['units'][newUnitId]['point'] = {} newTable['units'][newUnitId]['point']['x'] = newUnitData.x newTable['units'][newUnitId]['point']['y'] = newUnitData.y newTable['units'][newUnitId]['alt'] = newUnitData.alt newTable['units'][newUnitId]['alt_type'] = newUnitData.alt_type newTable['units'][newUnitId]['unitId'] = tonumber(newUnitData.unitId) newTable['units'][newUnitId]['speed'] = newUnitData.speed newTable['units'][newUnitId]['airdromeId'] = newUnitData.airdromeId newTable['units'][newUnitId]['type'] = newUnitData.type newTable['units'][newUnitId]['skill'] = newUnitData.skill newTable['units'][newUnitId]['groupName'] = groupData.name newTable['units'][newUnitId]['livery_id'] = groupData.livery_id newTable['units'][newUnitId]['country'] = string.lower(newTable.country) newTable['units'][newUnitId]['countryId'] = newTable.countryId newTable['units'][newUnitId]['coalition'] = newTable.coalition newTable['units'][newUnitId]['category'] = newTable.category newTable['units'][newUnitId]['shape_name'] = newTable.shape_name -- for statics end newTable['timeAdded'] = timer.getAbsTime() -- only on the dynGroupsAdded table. For other reference, see start time --mist.debug.dumpDBs() --end return newTable 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 end 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 addToDBs[#addToDBs + 1] = mist.utils.deepCopy(newGroup) -- 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 --env.info('added') 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 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_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"] = {} 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_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]["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]" - subtract this unit if its in the table "[g]" - add this group to the table "[-g]" - subtract this group from the table "[c]" - add this country's units "[-c]" - 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]" - add all of this country's helicopters "[-c][helicopter]" - subtract all of this country's helicopters "[c][plane]" - add all of this country's planes "[-c][plane]" - subtract all of this country's planes "[c][ship]" - add all of this country's ships "[-c][ship]" - subtract all of this country's ships "[c][vehicle]" - add all of this country's vehicles "[-c][vehicle]" - 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', stopflag = {'number', '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', stopflag = {'number', '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 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 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', stopflag = {'number', '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', stopflag = {'number', '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', stopflag = {'number', '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', stopflag = {'number', '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', stopflag = {'number', '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', stopflag = {'number', '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) env.info('aliveless') local type_tbl = { [{'group', 'groupname', 'gp', 'groupName'}] = 'string', percent = 'number', flag = 'number', stopflag = {'number', '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', stopflag = {'number', '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 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.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() 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()]) end return newData end end mist.getGroupData = function(gpName) --env.info('getgroupData') for groupName, groupData in pairs(mist.DBs.groupsByName) do if string.lower(groupName) == string.lower(gpName) then local newData = {} newData.hidden = false -- maybe add this to DBs newData.groupId = groupData.groupId newData.groupName = groupName newData.category = groupData.category newData.country = groupData.country newData.units = {} newData.task = groupData.task for unitNum, unitData in pairs(groupData.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"] = mist.getPayload(unitData.unitName) newData.units[unitNum]['livery_id'] = unitData.livery_id end end return newData end 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 string.lower(unitName) == string.lower(unitData.name) then return unitData.payload end end end end end end end end end end end else return false end 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 isStatic = false 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 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(), newGroupData.country = mist.DBs.groupsByName[gpName].country newGroupData.category = mist.DBs.groupsByName[gpName].category 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.randomizeGroupOrder = function(units, vars) -- does the heavy lifting local exclude = {} if vars and vars.exclude and type(vars.exclude) == 'table' then exclude = vars.exclude 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 newGroup = {} local randomizedUnits = {} local excludeIndex = {} local size = 0 for unitId, unitData in pairs(units) do if unitId >= low and unitId <= hi then -- if within range local found = false if #exclude > 0 then for excludeName, index in pairs(exclude) do -- check if excluded if mist.stringMatch(excludeName, unitData.type) then -- if excluded excludeIndex[unitId] = unitData.unitName newGroup[unitId] = unitData size = size + 1 found = true end end end if found == false then table.insert(randomizedUnits, unitData) end else -- unitId is either to low, or to high: added to exclude list newGroup[unitId] = unitData excludeIndex[unitId] = unitData.unitName size = size + 1 end end for unitId, unitData in pairs(randomizedUnits) do local found = false local i = 0 while found == false do i = mist.random(#units) -- get random int the size of the group env.info(i) if size > 0 then local noMatch = true for index, data in pairs(excludeIndex) do if i == index then noMatch = false break end end if noMatch == true then excludeIndex[i] = unitData.unitName size = size + 1 found = true end else excludeIndex[i] = unitData.unitName size = size + 1 found = true end end 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].unitId = mist.utils.deepCopy(unitData.unitId) -- replaces the units data with a new type end return newGroup end 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) if type(s1) == 'string' and type(s2) == 'string' then s1 = string.gsub(s1, "%-", '') s1 = string.gsub(s1, "%(", '') s1 = string.gsub(s1, "%)", '') s1 = string.gsub(s1, "%_", '') s1 = string.lower(s1) s2 = string.gsub(s2, "%-", '') s2 = string.gsub(s2, "%(", '') s2 = string.gsub(s2, "%)", '') s2 = string.gsub(s2, "%_", '') s2 = string.lower(s2) if s1 == s2 then return true else return false end else assert('Either the first or second variable were not strings') return false end end 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'}}} ]] mist.main() env.info(('Mist version ' .. mist.majorVersion .. '.' .. mist.minorVersion .. '.' .. mist.build .. ' loaded.'))