--- **Utilities** - Various routines. -- @module Utilities.Routines -- @image MOOSE.JPG env.setErrorMessageBoxEnabled( false ) --- Extract of MIST functions. -- @author Grimes routines = {} -- don't change these routines.majorVersion = 3 routines.minorVersion = 3 routines.build = 22 ----------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------- -- Utils- conversion, Lua utils, etc. routines.utils = {} routines.utils.round = function( number, decimals ) local power = 10 ^ decimals return math.floor( number * power ) / power end -- from http://lua-users.org/wiki/CopyTable routines.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 local objectreturn = _copy( object ) return objectreturn end -- porting in Slmod's serialize_slmod2 routines.utils.oneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function lookup_table = {} local function _Serialize( tbl ) if type( tbl ) == 'table' then -- function only works for tables! if lookup_table[tbl] then return lookup_table[object] end local tbl_str = {} lookup_table[tbl] = tbl_str tbl_str[#tbl_str + 1] = '{' for ind, val in pairs( tbl ) do -- serialize its fields local ind_str = {} if type( ind ) == "number" then ind_str[#ind_str + 1] = '[' ind_str[#ind_str + 1] = tostring( ind ) ind_str[#ind_str + 1] = ']=' else -- must be a string ind_str[#ind_str + 1] = '[' ind_str[#ind_str + 1] = routines.utils.basicSerialize( ind ) ind_str[#ind_str + 1] = ']=' end local val_str = {} if ((type( val ) == 'number') or (type( val ) == 'boolean')) then val_str[#val_str + 1] = tostring( val ) val_str[#val_str + 1] = ',' tbl_str[#tbl_str + 1] = table.concat( ind_str ) tbl_str[#tbl_str + 1] = table.concat( val_str ) elseif type( val ) == 'string' then val_str[#val_str + 1] = routines.utils.basicSerialize( val ) val_str[#val_str + 1] = ',' tbl_str[#tbl_str + 1] = table.concat( ind_str ) tbl_str[#tbl_str + 1] = table.concat( val_str ) elseif type( val ) == 'nil' then -- won't ever happen, right? val_str[#val_str + 1] = 'nil,' tbl_str[#tbl_str + 1] = table.concat( ind_str ) tbl_str[#tbl_str + 1] = table.concat( val_str ) elseif type( val ) == 'table' then if ind == "__index" then -- tbl_str[#tbl_str + 1] = "__index" -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it else val_str[#val_str + 1] = _Serialize( val ) val_str[#val_str + 1] = ',' -- I think this is right, I just added it tbl_str[#tbl_str + 1] = table.concat( ind_str ) tbl_str[#tbl_str + 1] = table.concat( val_str ) end elseif type( val ) == 'function' then -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it else -- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) -- env.info( debug.traceback() ) end end tbl_str[#tbl_str + 1] = '}' return table.concat( tbl_str ) else if type( tbl ) == 'string' then return tbl else return tostring( tbl ) end end end local objectreturn = _Serialize( tbl ) return objectreturn end -- porting in Slmod's "safestring" basic serialize routines.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( '%s', s:gsub( "%%", "%%%%" ) ) return s end end end routines.utils.toDegree = function( angle ) return angle * 180 / math.pi end routines.utils.toRadian = function( angle ) return angle * math.pi / 180 end routines.utils.metersToNM = function( meters ) return meters / 1852 end routines.utils.metersToFeet = function( meters ) return meters / 0.3048 end routines.utils.NMToMeters = function( NM ) return NM * 1852 end routines.utils.feetToMeters = function( feet ) return feet * 0.3048 end routines.utils.mpsToKnots = function( mps ) return mps * 3600 / 1852 end routines.utils.mpsToKmph = function( mps ) return mps * 3.6 end routines.utils.knotsToMps = function( knots ) return knots * 1852 / 3600 end routines.utils.kmphToMps = function( kmph ) return kmph / 3.6 end function routines.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 routines.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 routines.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 routines.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 routines.utils.getDir( vec, point ) local dir = math.atan2( vec.z, vec.x ) dir = dir + routines.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 routines.utils.get2DDist( point1, point2 ) point1 = routines.utils.makeVec3( point1 ) point2 = routines.utils.makeVec3( point2 ) return routines.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 routines.utils.get3DDist( point1, point2 ) return routines.vec.mag( { x = point1.x - point2.x, y = point1.y - point2.y, z = point1.z - point2.z } ) end -- 3D Vector manipulation routines.vec = {} routines.vec.add = function( vec1, vec2 ) return { x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z } end routines.vec.sub = function( vec1, vec2 ) return { x = vec1.x - vec2.x, y = vec1.y - vec2.y, z = vec1.z - vec2.z } end routines.vec.scalarMult = function( vec, mult ) return { x = vec.x * mult, y = vec.y * mult, z = vec.z * mult } end routines.vec.scalar_mult = routines.vec.scalarMult routines.vec.dp = function( vec1, vec2 ) return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z end routines.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 routines.vec.mag = function( vec ) return (vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2) ^ 0.5 end routines.vec.getUnitVec = function( vec ) local mag = routines.vec.mag( vec ) return { x = vec.x / mag, y = vec.y / mag, z = vec.z / mag } end routines.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. routines.tostringMGRS = function( MGRS, acc ) if acc == 0 then return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph else return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph .. ' ' .. string.format( '%0' .. acc .. 'd', routines.utils.round( MGRS.Easting / (10 ^ (5 - acc)), 0 ) ) .. ' ' .. string.format( '%0' .. acc .. 'd', routines.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. ]] routines.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 = routines.utils.round( (oldLatMin - latMin) * 60, acc ) local oldLonMin = lonMin lonMin = math.floor( lonMin ) local lonSec = routines.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 = routines.utils.round( latMin, acc ) lonMin = routines.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.]] routines.tostringBR = function( az, dist, alt, metric ) az = routines.utils.round( routines.utils.toDegree( az ), 0 ) if metric then dist = routines.utils.round( dist / 1000, 2 ) else dist = routines.utils.round( routines.utils.metersToNM( dist ), 2 ) end local s = string.format( '%03d', az ) .. ' for ' .. dist if alt then if metric then s = s .. ' at ' .. routines.utils.round( alt, 0 ) else s = s .. ' at ' .. routines.utils.round( routines.utils.metersToFeet( alt ), 0 ) end end return s end routines.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 do local idNum = 0 -- Simplified event handler routines.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 routines.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 end -- need to return a Vec3 or Vec2? function routines.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 routines.goRoute = function( group, path ) local misTask = { id = 'Mission', params = { route = { points = routines.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 -- Useful atomic functions from mist, ported. routines.ground = {} routines.fixedWing = {} routines.heli = {} routines.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 = routines.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 routines.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 'agl' then wp.alt_type = 'RADIO' elseif altType == 'baro' or '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 = routines.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 routines.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 'agl' then wp.alt_type = 'RADIO' elseif altType == 'baro' or '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 = routines.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 routines.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 routines.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 = routines.getRandPointInCircle(point, radius, innerRadius) local offset = {} local posStart = routines.getLeadPos(group) offset.x = routines.utils.round(math.sin(heading - (math.pi/2)) * 50 + rndCoord.x, 3) offset.z = routines.utils.round(math.cos(heading + (math.pi/2)) * 50 + rndCoord.y, 3) path[#path + 1] = routines.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] = routines.ground.buildWP({['x'] = posStart.x + 11, ['z'] = posStart.z + 11}, 'off_road', speed) path[#path + 1] = routines.ground.buildWP(posStart, 'on_road', speed) path[#path + 1] = routines.ground.buildWP(offset, 'on_road', speed) else path[#path + 1] = routines.ground.buildWP({['x'] = posStart.x + 25, ['z'] = posStart.z + 25}, form, speed) end path[#path + 1] = routines.ground.buildWP(offset, form, speed) path[#path + 1] = routines.ground.buildWP(rndCoord, form, speed) routines.goRoute(group, path) return end routines.groupRandomDistSelf = function(gpData, dist, form, heading, speed) local pos = routines.getLeadPos(gpData) local fakeZone = {} fakeZone.radius = dist or math.random(300, 1000) fakeZone.point = {x = pos.x, y = pos.y, z = pos.z} routines.groupToRandomZone(gpData, fakeZone, form, heading, speed) return end routines.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 = routines.utils.kmphToMps(speed) end local vars = {} vars.group = gpData vars.radius = zone.radius vars.form = form vars.headingDegrees = heading vars.speed = speed vars.point = routines.utils.zoneToVec3(zone) routines.groupToRandomPoint(vars) return end routines.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 routines.groupToPoint = function(gpData, point, form, heading, speed, useRoads) if type(point) == 'string' then point = trigger.misc.getZone(point) end if speed then speed = routines.utils.kmphToMps(speed) end local vars = {} vars.group = gpData vars.form = form vars.headingDegrees = heading vars.speed = speed vars.disableRoads = useRoads vars.point = routines.utils.zoneToVec3(point) routines.groupToRandomPoint(vars) return end routines.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 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 ind < lowestInd then lowestInd = ind leader = unit end end end if leader and Unit.isExist(leader) then -- maybe a little too paranoid now... return leader:getPosition().p end end --[[ vars for routines.getMGRSString: vars.units - table of unit names (NOT unitNameTable- maybe this should change). vars.acc - integer between 0 and 5, inclusive ]] routines.getMGRSString = function( vars ) local units = vars.units local acc = vars.acc or 5 local avgPos = routines.getAvgPos( units ) if avgPos then return routines.tostringMGRS( coord.LLtoMGRS( coord.LOtoLL( avgPos ) ), acc ) end end --[[ vars for routines.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. ]] routines.getLLString = function( vars ) local units = vars.units local acc = vars.acc or 3 local DMS = vars.DMS local avgPos = routines.getAvgPos( units ) if avgPos then local lat, lon = coord.LOtoLL( avgPos ) return routines.tostringLL( lat, lon, acc, DMS ) end end --[[ vars.zone - table of a zone name. 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. ]] routines.getBRStringZone = function( vars ) local zone = trigger.misc.getZone( vars.zone ) local ref = routines.utils.makeVec3( vars.ref, 0 ) -- turn it into Vec3 if it is not already. local alt = vars.alt local metric = vars.metric if zone then local vec = { x = zone.point.x - ref.x, y = zone.point.y - ref.y, z = zone.point.z - ref.z } local dir = routines.utils.getDir( vec, ref ) local dist = routines.utils.get2DDist( zone.point, ref ) if alt then alt = zone.y end return routines.tostringBR( dir, dist, alt, metric ) else env.info( 'routines.getBRStringZone: error: zone is nil' ) 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. ]] routines.getBRString = function( vars ) local units = vars.units local ref = routines.utils.makeVec3( vars.ref, 0 ) -- turn it into Vec3 if it is not already. local alt = vars.alt local metric = vars.metric local avgPos = routines.getAvgPos( units ) if avgPos then local vec = { x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z } local dir = routines.utils.getDir( vec, ref ) local dist = routines.utils.get2DDist( avgPos, ref ) if alt then alt = avgPos.y end return routines.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 routines.getLeadingPos: vars.units - table of unit names vars.heading - direction vars.radius - number vars.headingDegrees - boolean, switches heading to degrees ]] routines.getLeadingPos = function( vars ) local units = vars.units local heading = vars.heading local radius = vars.radius if vars.headingDegrees then heading = routines.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 = routines.vec.rotateVec2( routines.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 routines.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 routines.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. ]] routines.getLeadingMGRSString = function( vars ) local pos = routines.getLeadingPos( vars ) if pos then local acc = vars.acc or 5 return routines.tostringMGRS( coord.LLtoMGRS( coord.LOtoLL( pos ) ), acc ) end end --[[ vars for routines.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. ]] routines.getLeadingLLString = function( vars ) local pos = routines.getLeadingPos( vars ) if pos then local acc = vars.acc or 3 local DMS = vars.DMS local lat, lon = coord.LOtoLL( pos ) return routines.tostringLL( lat, lon, acc, DMS ) end end --[[ vars for routines.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. ]] routines.getLeadingBRString = function( vars ) local pos = routines.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 = routines.utils.getDir( vec, ref ) local dist = routines.utils.get2DDist( pos, ref ) if alt then alt = pos.y end return routines.tostringBR( dir, dist, alt, metric ) end end --[[ vars for routines.message.add vars.text = 'Hello World' vars.displayTime = 20 vars.msgFor = {coa = {'red'}, countries = {'Ukraine', 'Georgia'}, unitTypes = {'A-10C'}} ]] --[[ vars for routines.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 ]] routines.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 = routines.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 routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end --[[ vars for routines.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 ]] routines.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 = routines.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 routines.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 ]] routines.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 routines.getBRString local alt = vars.alt local metric = vars.metric local text = vars.text local displayTime = vars.displayTime local msgFor = vars.msgFor local s = routines.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 routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end -------------------------------------------------------------------------------------------- -- basically, just sub-types of routines.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 ]] routines.msgBullseye = function( vars ) if string.lower( vars.ref ) == 'red' then vars.ref = routines.DBs.missionData.bullseye.red routines.msgBR( vars ) elseif string.lower( vars.ref ) == 'blue' then vars.ref = routines.DBs.missionData.bullseye.blue routines.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 ]] routines.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 routines.msgBR( vars ) end end -------------------------------------------------------------------------------------------- --[[ vars for routines.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 ]] routines.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 = routines.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 routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end --[[ vars for routines.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 ]] routines.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 = routines.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 routines.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 ]] routines.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 routines.getBRString local text = vars.text local displayTime = vars.displayTime local msgFor = vars.msgFor local s = routines.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 routines.message.add { text = newText, displayTime = displayTime, msgFor = msgFor } end function spairs( t, order ) -- collect the keys local keys = {} for k in pairs( t ) do keys[#keys + 1] = k end -- if order function given, sort by it by passing the table and keys a, b, -- otherwise just sort the keys if order then table.sort( keys, function( a, b ) return order( t, a, b ) end ) else table.sort( keys ) end -- return the iterator function local i = 0 return function() i = i + 1 if keys[i] then return keys[i], t[keys[i]] end end end function routines.IsPartOfGroupInZones( CargoGroup, LandingZones ) -- trace.f() local CurrentZoneID = nil if CargoGroup then local CargoUnits = CargoGroup:getUnits() for CargoUnitID, CargoUnit in pairs( CargoUnits ) do if CargoUnit and CargoUnit:getLife() >= 1.0 then CurrentZoneID = routines.IsUnitInZones( CargoUnit, LandingZones ) if CurrentZoneID then break end end end end -- trace.r( "", "", { CurrentZoneID } ) return CurrentZoneID end function routines.IsUnitInZones( TransportUnit, LandingZones ) -- trace.f("", "routines.IsUnitInZones" ) local TransportZoneResult = nil local TransportZonePos = nil local TransportZone = nil -- fill-up some local variables to support further calculations to determine location of units within the zone. if TransportUnit then local TransportUnitPos = TransportUnit:getPosition().p if type( LandingZones ) == "table" then for LandingZoneID, LandingZoneName in pairs( LandingZones ) do TransportZone = trigger.misc.getZone( LandingZoneName ) if TransportZone then TransportZonePos = { radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z } if (((TransportUnitPos.x - TransportZonePos.x) ^ 2 + (TransportUnitPos.z - TransportZonePos.z) ^ 2) ^ 0.5 <= TransportZonePos.radius) then TransportZoneResult = LandingZoneID break end end end else TransportZone = trigger.misc.getZone( LandingZones ) TransportZonePos = { radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z } if (((TransportUnitPos.x - TransportZonePos.x) ^ 2 + (TransportUnitPos.z - TransportZonePos.z) ^ 2) ^ 0.5 <= TransportZonePos.radius) then TransportZoneResult = 1 end end if TransportZoneResult then -- trace.i( "routines", "TransportZone:" .. TransportZoneResult ) else -- trace.i( "routines", "TransportZone:nil logic" ) end return TransportZoneResult else -- trace.i( "routines", "TransportZone:nil hard" ) return nil end end function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius ) -- trace.f("", "routines.IsUnitInZones" ) local TransportZoneResult = nil local TransportZonePos = nil local TransportZone = nil -- fill-up some local variables to support further calculations to determine location of units within the zone. if TransportUnit then local TransportUnitPos = TransportUnit:getPosition().p if type( LandingZones ) == "table" then for LandingZoneID, LandingZoneName in pairs( LandingZones ) do TransportZone = trigger.misc.getZone( LandingZoneName ) if TransportZone then TransportZonePos = { radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z } if (((TransportUnitPos.x - TransportZonePos.x) ^ 2 + (TransportUnitPos.z - TransportZonePos.z) ^ 2) ^ 0.5 <= ZoneRadius) then TransportZoneResult = LandingZoneID break end end end else TransportZone = trigger.misc.getZone( LandingZones ) TransportZonePos = { radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z } if (((TransportUnitPos.x - TransportZonePos.x) ^ 2 + (TransportUnitPos.z - TransportZonePos.z) ^ 2) ^ 0.5 <= ZoneRadius) then TransportZoneResult = 1 end end if TransportZoneResult then -- trace.i( "routines", "TransportZone:" .. TransportZoneResult ) else -- trace.i( "routines", "TransportZone:nil logic" ) end return TransportZoneResult else -- trace.i( "routines", "TransportZone:nil hard" ) return nil end end function routines.IsStaticInZones( TransportStatic, LandingZones ) -- trace.f() local TransportZoneResult = nil local TransportZonePos = nil local TransportZone = nil -- fill-up some local variables to support further calculations to determine location of units within the zone. local TransportStaticPos = TransportStatic:getPosition().p if type( LandingZones ) == "table" then for LandingZoneID, LandingZoneName in pairs( LandingZones ) do TransportZone = trigger.misc.getZone( LandingZoneName ) if TransportZone then TransportZonePos = { radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z } if (((TransportStaticPos.x - TransportZonePos.x) ^ 2 + (TransportStaticPos.z - TransportZonePos.z) ^ 2) ^ 0.5 <= TransportZonePos.radius) then TransportZoneResult = LandingZoneID break end end end else TransportZone = trigger.misc.getZone( LandingZones ) TransportZonePos = { radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z } if (((TransportStaticPos.x - TransportZonePos.x) ^ 2 + (TransportStaticPos.z - TransportZonePos.z) ^ 2) ^ 0.5 <= TransportZonePos.radius) then TransportZoneResult = 1 end end -- trace.r( "", "", { TransportZoneResult } ) return TransportZoneResult end function routines.IsUnitInRadius( CargoUnit, ReferencePosition, Radius ) -- trace.f() local Valid = true -- fill-up some local variables to support further calculations to determine location of units within the zone. local CargoPos = CargoUnit:getPosition().p local ReferenceP = ReferencePosition.p if (((CargoPos.x - ReferenceP.x) ^ 2 + (CargoPos.z - ReferenceP.z) ^ 2) ^ 0.5 <= Radius) then else Valid = false end return Valid end function routines.IsPartOfGroupInRadius( CargoGroup, ReferencePosition, Radius ) -- trace.f() local Valid = true Valid = routines.ValidateGroup( CargoGroup, "CargoGroup", Valid ) -- fill-up some local variables to support further calculations to determine location of units within the zone local CargoUnits = CargoGroup:getUnits() for CargoUnitId, CargoUnit in pairs( CargoUnits ) do local CargoUnitPos = CargoUnit:getPosition().p -- env.info( 'routines.IsPartOfGroupInRadius: CargoUnitPos.x = ' .. CargoUnitPos.x .. ' CargoUnitPos.z = ' .. CargoUnitPos.z ) local ReferenceP = ReferencePosition.p -- env.info( 'routines.IsPartOfGroupInRadius: ReferenceGroupPos.x = ' .. ReferenceGroupPos.x .. ' ReferenceGroupPos.z = ' .. ReferenceGroupPos.z ) if (((CargoUnitPos.x - ReferenceP.x) ^ 2 + (CargoUnitPos.z - ReferenceP.z) ^ 2) ^ 0.5 <= Radius) then else Valid = false break end end return Valid end function routines.ValidateString( Variable, VariableName, Valid ) -- trace.f() if type( Variable ) == "string" then if Variable == "" then error( "routines.ValidateString: error: " .. VariableName .. " must be filled out!" ) Valid = false end else error( "routines.ValidateString: error: " .. VariableName .. " is not a string." ) Valid = false end -- trace.r( "", "", { Valid } ) return Valid end function routines.ValidateNumber( Variable, VariableName, Valid ) -- trace.f() if type( Variable ) == "number" then else error( "routines.ValidateNumber: error: " .. VariableName .. " is not a number." ) Valid = false end -- trace.r( "", "", { Valid } ) return Valid end function routines.ValidateGroup( Variable, VariableName, Valid ) -- trace.f() if Variable == nil then error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) Valid = false end -- trace.r( "", "", { Valid } ) return Valid end function routines.ValidateZone( LandingZones, VariableName, Valid ) -- trace.f() if LandingZones == nil then error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) Valid = false end if type( LandingZones ) == "table" then for LandingZoneID, LandingZoneName in pairs( LandingZones ) do if trigger.misc.getZone( LandingZoneName ) == nil then error( "routines.ValidateGroup: error: Zone " .. LandingZoneName .. " does not exist!" ) Valid = false break end end else if trigger.misc.getZone( LandingZones ) == nil then error( "routines.ValidateGroup: error: Zone " .. LandingZones .. " does not exist!" ) Valid = false end end -- trace.r( "", "", { Valid } ) return Valid end function routines.ValidateEnumeration( Variable, VariableName, Enum, Valid ) -- trace.f() local ValidVariable = false for EnumId, EnumData in pairs( Enum ) do if Variable == EnumData then ValidVariable = true break end end if ValidVariable then else error( 'TransportValidateEnum: " .. VariableName .. " is not a valid type.' .. Variable ) Valid = false end -- trace.r( "", "", { Valid } ) return Valid end function routines.getGroupRoute( groupIdent, task ) -- same as getGroupPoints but returns speed and formation type along with vec2 of point} -- refactor to search by groupId and allow groupId and groupName as inputs local gpId = groupIdent if type( groupIdent ) == 'string' and not tonumber( groupIdent ) then gpId = _DATABASE.Templates.Groups[groupIdent].groupId end for coa_name, coa_data in pairs( env.mission.coalition ) do if (coa_name == 'red' or coa_name == 'blue') and type( coa_data ) == 'table' then if coa_data.country then -- there is a country table for cntry_id, cntry_data in pairs( coa_data.country ) do for obj_type_name, obj_type_data in pairs( cntry_data ) do if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points if ((type( obj_type_data ) == 'table') and obj_type_data.group and (type( obj_type_data.group ) == 'table') and (#obj_type_data.group > 0)) then -- there's a group! for group_num, group_data in pairs( obj_type_data.group ) do if group_data and group_data.groupId == gpId then -- this is the group we are looking for if group_data.route and group_data.route.points and #group_data.route.points > 0 then local points = {} for point_num, point in pairs( group_data.route.points ) do local routeData = {} if env.mission.version > 7 then routeData.name = env.getValueDictByKey( point.name ) else routeData.name = point.name end 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 routines.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 = routines.getGroupRoute( useGroupRoute ) end else useRoute = vars.route local posStart = routines.getLeadPos( gpData ) useRoute[1] = routines.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 = routines.getLeadPos( gpData ) useRoute[#useRoute + 1] = routines.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] = routines.ground.buildWP( tempRoute[i], tempForm, tempSpeed ) end if pType and string.lower( pType ) == 'doubleback' then local curRoute = routines.utils.deepCopy( useRoute ) for i = #curRoute, 2, -1 do useRoute[#useRoute + 1] = routines.ground.buildWP( curRoute[i], curRoute[i].action, curRoute[i].speed ) end end useRoute[1].action = useRoute[#useRoute].action -- make it so the first WP matches the last WP end local cTask3 = {} local newPatrol = {} newPatrol.route = useRoute newPatrol.gpData = gpData:getName() cTask3[#cTask3 + 1] = 'routines.ground.patrolRoute(' cTask3[#cTask3 + 1] = routines.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 routines.goRoute( gpData, useRoute ) end routines.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 routines.ground.patrolRoute( vars ) end function routines.GetUnitHeight( CheckUnit ) -- trace.f( "routines" ) local UnitPoint = CheckUnit:getPoint() local UnitPosition = { x = UnitPoint.x, y = UnitPoint.z } local UnitHeight = UnitPoint.y local LandHeight = land.getHeight( UnitPosition ) -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) -- trace.f( "routines", "Unit Height = " .. UnitHeight - LandHeight ) return UnitHeight - LandHeight end Su34Status = { status = {} } boardMsgRed = { statusMsg = "" } boardMsgAll = { timeMsg = "" } SpawnSettings = {} Su34MenuPath = {} Su34Menus = 0 function Su34AttackCarlVinson( groupName ) -- trace.menu("", "Su34AttackCarlVinson") local groupSu34 = Group.getByName( groupName ) local controllerSu34 = groupSu34.getController( groupSu34 ) local groupCarlVinson = Group.getByName( "US Carl Vinson #001" ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) if groupCarlVinson ~= nil then controllerSu34.pushTask( controllerSu34, { id = 'AttackGroup', params = { groupId = groupCarlVinson:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = true } } ) end Su34Status.status[groupName] = 1 MessageToRed( string.format( '%s: ', groupName ) .. 'Attacking carrier Carl Vinson. ', 10, 'RedStatus' .. groupName ) end function Su34AttackWest( groupName ) -- trace.f("","Su34AttackWest") local groupSu34 = Group.getByName( groupName ) local controllerSu34 = groupSu34.getController( groupSu34 ) local groupShipWest1 = Group.getByName( "US Ship West #001" ) local groupShipWest2 = Group.getByName( "US Ship West #002" ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) if groupShipWest1 ~= nil then controllerSu34.pushTask( controllerSu34, { id = 'AttackGroup', params = { groupId = groupShipWest1:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = true } } ) end if groupShipWest2 ~= nil then controllerSu34.pushTask( controllerSu34, { id = 'AttackGroup', params = { groupId = groupShipWest2:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = true } } ) end Su34Status.status[groupName] = 2 MessageToRed( string.format( '%s: ', groupName ) .. 'Attacking invading ships in the west. ', 10, 'RedStatus' .. groupName ) end function Su34AttackNorth( groupName ) -- trace.menu("","Su34AttackNorth") local groupSu34 = Group.getByName( groupName ) local controllerSu34 = groupSu34.getController( groupSu34 ) local groupShipNorth1 = Group.getByName( "US Ship North #001" ) local groupShipNorth2 = Group.getByName( "US Ship North #002" ) local groupShipNorth3 = Group.getByName( "US Ship North #003" ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) if groupShipNorth1 ~= nil then controllerSu34.pushTask( controllerSu34, { id = 'AttackGroup', params = { groupId = groupShipNorth1:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = false } } ) end if groupShipNorth2 ~= nil then controllerSu34.pushTask( controllerSu34, { id = 'AttackGroup', params = { groupId = groupShipNorth2:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = false } } ) end if groupShipNorth3 ~= nil then controllerSu34.pushTask( controllerSu34, { id = 'AttackGroup', params = { groupId = groupShipNorth3:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = false } } ) end Su34Status.status[groupName] = 3 MessageToRed( string.format( '%s: ', groupName ) .. 'Attacking invading ships in the north. ', 10, 'RedStatus' .. groupName ) end function Su34Orbit( groupName ) -- trace.menu("","Su34Orbit") local groupSu34 = Group.getByName( groupName ) local controllerSu34 = groupSu34:getController() controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) controllerSu34:pushTask( { id = 'ControlledTask', params = { task = { id = 'Orbit', params = { pattern = AI.Task.OrbitPattern.RACE_TRACK } }, stopCondition = { duration = 600 } } } ) Su34Status.status[groupName] = 4 MessageToRed( string.format( '%s: ', groupName ) .. 'In orbit and awaiting further instructions. ', 10, 'RedStatus' .. groupName ) end function Su34TakeOff( groupName ) -- trace.menu("","Su34TakeOff") local groupSu34 = Group.getByName( groupName ) local controllerSu34 = groupSu34:getController() controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) Su34Status.status[groupName] = 8 MessageToRed( string.format( '%s: ', groupName ) .. 'Take-Off. ', 10, 'RedStatus' .. groupName ) end function Su34Hold( groupName ) -- trace.menu("","Su34Hold") local groupSu34 = Group.getByName( groupName ) local controllerSu34 = groupSu34:getController() controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) Su34Status.status[groupName] = 5 MessageToRed( string.format( '%s: ', groupName ) .. 'Holding Weapons. ', 10, 'RedStatus' .. groupName ) end function Su34RTB( groupName ) -- trace.menu("","Su34RTB") Su34Status.status[groupName] = 6 MessageToRed( string.format( '%s: ', groupName ) .. 'Return to Krasnodar. ', 10, 'RedStatus' .. groupName ) end function Su34Destroyed( groupName ) -- trace.menu("","Su34Destroyed") Su34Status.status[groupName] = 7 MessageToRed( string.format( '%s: ', groupName ) .. 'Destroyed. ', 30, 'RedStatus' .. groupName ) end function GroupAlive( groupName ) -- trace.menu("","GroupAlive") local groupTest = Group.getByName( groupName ) local groupExists = false if groupTest then groupExists = groupTest:isExist() end -- trace.r( "", "", { groupExists } ) return groupExists end function Su34IsDead() -- trace.f() end function Su34OverviewStatus() -- trace.menu("","Su34OverviewStatus") local msg = "" local currentStatus = 0 local Exists = false for groupName, currentStatus in pairs( Su34Status.status ) do env.info( ('Su34 Overview Status: GroupName = ' .. groupName) ) Alive = GroupAlive( groupName ) if Alive then if currentStatus == 1 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "Attacking carrier Carl Vinson. " elseif currentStatus == 2 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "Attacking supporting ships in the west. " elseif currentStatus == 3 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "Attacking invading ships in the north. " elseif currentStatus == 4 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "In orbit and awaiting further instructions. " elseif currentStatus == 5 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "Holding Weapons. " elseif currentStatus == 6 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "Return to Krasnodar. " elseif currentStatus == 7 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "Destroyed. " elseif currentStatus == 8 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "Take-Off. " end else if currentStatus == 7 then msg = msg .. string.format( "%s: ", groupName ) msg = msg .. "Destroyed. " else Su34Destroyed( groupName ) end end end boardMsgRed.statusMsg = msg end function UpdateBoardMsg() -- trace.f() Su34OverviewStatus() MessageToRed( boardMsgRed.statusMsg, 15, 'RedStatus' ) end function MusicReset( flg ) -- trace.f() trigger.action.setUserFlag( 95, flg ) end function PlaneActivate( groupNameFormat, flg ) -- trace.f() local groupName = groupNameFormat .. string.format( "#%03d", trigger.misc.getUserFlag( flg ) ) -- trigger.action.outText(groupName,10) trigger.action.activateGroup( Group.getByName( groupName ) ) end function Su34Menu( groupName ) -- trace.f() -- env.info(( 'Su34Menu(' .. groupName .. ')' )) local groupSu34 = Group.getByName( groupName ) if Su34Status.status[groupName] == 1 or Su34Status.status[groupName] == 2 or Su34Status.status[groupName] == 3 or Su34Status.status[groupName] == 4 or Su34Status.status[groupName] == 5 then if Su34MenuPath[groupName] == nil then if planeMenuPath == nil then planeMenuPath = missionCommands.addSubMenuForCoalition( coalition.side.RED, "SU-34 anti-ship flights", nil ) end Su34MenuPath[groupName] = missionCommands.addSubMenuForCoalition( coalition.side.RED, "Flight " .. groupName, planeMenuPath ) missionCommands.addCommandForCoalition( coalition.side.RED, "Attack carrier Carl Vinson", Su34MenuPath[groupName], Su34AttackCarlVinson, groupName ) missionCommands.addCommandForCoalition( coalition.side.RED, "Attack ships in the west", Su34MenuPath[groupName], Su34AttackWest, groupName ) missionCommands.addCommandForCoalition( coalition.side.RED, "Attack ships in the north", Su34MenuPath[groupName], Su34AttackNorth, groupName ) missionCommands.addCommandForCoalition( coalition.side.RED, "Hold position and await instructions", Su34MenuPath[groupName], Su34Orbit, groupName ) missionCommands.addCommandForCoalition( coalition.side.RED, "Report status", Su34MenuPath[groupName], Su34OverviewStatus ) end else if Su34MenuPath[groupName] then missionCommands.removeItemForCoalition( coalition.side.RED, Su34MenuPath[groupName] ) end end end --- Obsolete function, but kept to rework in framework. function ChooseInfantry( TeleportPrefixTable, TeleportMax ) -- trace.f("Spawn") -- env.info(( 'ChooseInfantry: ' )) TeleportPrefixTableCount = #TeleportPrefixTable TeleportPrefixTableIndex = math.random( 1, TeleportPrefixTableCount ) -- env.info(( 'ChooseInfantry: TeleportPrefixTableIndex = ' .. TeleportPrefixTableIndex .. ' TeleportPrefixTableCount = ' .. TeleportPrefixTableCount .. ' TeleportMax = ' .. TeleportMax )) local TeleportFound = false local TeleportLoop = true local Index = TeleportPrefixTableIndex local TeleportPrefix = '' while TeleportLoop do TeleportPrefix = TeleportPrefixTable[Index] if SpawnSettings[TeleportPrefix] then if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 TeleportFound = true else TeleportFound = false end else SpawnSettings[TeleportPrefix] = {} SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 TeleportFound = true end if TeleportFound then TeleportLoop = false else if Index < TeleportPrefixTableCount then Index = Index + 1 else TeleportLoop = false end end -- env.info(( 'ChooseInfantry: Loop 1 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) end if TeleportFound == false then TeleportLoop = true Index = 1 while TeleportLoop do TeleportPrefix = TeleportPrefixTable[Index] if SpawnSettings[TeleportPrefix] then if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 TeleportFound = true else TeleportFound = false end else SpawnSettings[TeleportPrefix] = {} SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 TeleportFound = true end if TeleportFound then TeleportLoop = false else if Index < TeleportPrefixTableIndex then Index = Index + 1 else TeleportLoop = false end end -- env.info(( 'ChooseInfantry: Loop 2 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) end end local TeleportGroupName = '' if TeleportFound == true then TeleportGroupName = TeleportPrefix .. string.format( "#%03d", SpawnSettings[TeleportPrefix]['SpawnCount'] ) else TeleportGroupName = '' end -- env.info(('ChooseInfantry: TeleportGroupName = ' .. TeleportGroupName )) -- env.info(('ChooseInfantry: return')) return TeleportGroupName end SpawnedInfantry = 0 function LandCarrier( CarrierGroup, LandingZonePrefix ) -- trace.f() -- env.info(( 'LandCarrier: ' )) -- env.info(( 'LandCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) -- env.info(( 'LandCarrier: LandingZone = ' .. LandingZonePrefix )) local controllerGroup = CarrierGroup:getController() local LandingZone = trigger.misc.getZone( LandingZonePrefix ) local LandingZonePos = {} LandingZonePos.x = LandingZone.point.x + math.random( LandingZone.radius * -1, LandingZone.radius ) LandingZonePos.y = LandingZone.point.z + math.random( LandingZone.radius * -1, LandingZone.radius ) controllerGroup:pushTask( { id = 'Land', params = { point = LandingZonePos, durationFlag = true, duration = 10 } } ) -- env.info(( 'LandCarrier: end' )) end EscortCount = 0 function EscortCarrier( CarrierGroup, EscortPrefix, EscortLastWayPoint, EscortEngagementDistanceMax, EscortTargetTypes ) -- trace.f() -- env.info(( 'EscortCarrier: ' )) -- env.info(( 'EscortCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) -- env.info(( 'EscortCarrier: EscortPrefix = ' .. EscortPrefix )) local CarrierName = CarrierGroup:getName() local EscortMission = {} local CarrierMission = {} local EscortMission = SpawnMissionGroup( EscortPrefix ) local CarrierMission = SpawnMissionGroup( CarrierGroup:getName() ) if EscortMission ~= nil and CarrierMission ~= nil then EscortCount = EscortCount + 1 EscortMissionName = string.format( EscortPrefix .. '#Escort %s', CarrierName ) EscortMission.name = EscortMissionName EscortMission.groupId = nil EscortMission.lateActivation = false EscortMission.taskSelected = false local EscortUnits = #EscortMission.units for u = 1, EscortUnits do EscortMission.units[u].name = string.format( EscortPrefix .. '#Escort %s %02d', CarrierName, u ) EscortMission.units[u].unitId = nil end EscortMission.route.points[1].task = { id = "ComboTask", params = { tasks = { [1] = { enabled = true, auto = false, id = "Escort", number = 1, params = { lastWptIndexFlagChangedManually = false, groupId = CarrierGroup:getID(), lastWptIndex = nil, lastWptIndexFlag = false, engagementDistMax = EscortEngagementDistanceMax, targetTypes = EscortTargetTypes, pos = { y = 20, x = 20, z = 0 } -- end of ["pos"] } -- end of ["params"] } -- end of [1] } -- end of ["tasks"] } -- end of ["params"] } -- end of ["task"] SpawnGroupAdd( EscortPrefix, EscortMission ) end end function SendMessageToCarrier( CarrierGroup, CarrierMessage ) -- trace.f() if CarrierGroup ~= nil then MessageToGroup( CarrierGroup, CarrierMessage, 30, 'Carrier/' .. CarrierGroup:getName() ) end end function MessageToGroup( MsgGroup, MsgText, MsgTime, MsgName ) -- trace.f() if type( MsgGroup ) == 'string' then -- env.info( 'MessageToGroup: Converted MsgGroup string "' .. MsgGroup .. '" into a Group structure.' ) MsgGroup = Group.getByName( MsgGroup ) end if MsgGroup ~= nil then local MsgTable = {} MsgTable.text = MsgText MsgTable.displayTime = MsgTime MsgTable.msgFor = { units = { MsgGroup:getUnits()[1]:getName() } } MsgTable.name = MsgName -- routines.message.add( MsgTable ) -- env.info(('MessageToGroup: Message sent to ' .. MsgGroup:getUnits()[1]:getName() .. ' -> ' .. MsgText )) end end function MessageToUnit( UnitName, MsgText, MsgTime, MsgName ) -- trace.f() if UnitName ~= nil then local MsgTable = {} MsgTable.text = MsgText MsgTable.displayTime = MsgTime MsgTable.msgFor = { units = { UnitName } } MsgTable.name = MsgName -- routines.message.add( MsgTable ) end end function MessageToAll( MsgText, MsgTime, MsgName ) -- trace.f() MESSAGE:New( MsgText, MsgTime, "Message" ):ToCoalition( coalition.side.RED ):ToCoalition( coalition.side.BLUE ) end function MessageToRed( MsgText, MsgTime, MsgName ) -- trace.f() MESSAGE:New( MsgText, MsgTime, "To Red Coalition" ):ToCoalition( coalition.side.RED ) end function MessageToBlue( MsgText, MsgTime, MsgName ) -- trace.f() MESSAGE:New( MsgText, MsgTime, "To Blue Coalition" ):ToCoalition( coalition.side.BLUE ) end function getCarrierHeight( CarrierGroup ) -- trace.f() if CarrierGroup ~= nil then if table.getn( CarrierGroup:getUnits() ) == 1 then local CarrierUnit = CarrierGroup:getUnits()[1] local CurrentPoint = CarrierUnit:getPoint() local CurrentPosition = { x = CurrentPoint.x, y = CurrentPoint.z } local CarrierHeight = CurrentPoint.y local LandHeight = land.getHeight( CurrentPosition ) -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) return CarrierHeight - LandHeight else return 999999 end else return 999999 end end function GetUnitHeight( CheckUnit ) -- trace.f() local UnitPoint = CheckUnit:getPoint() local UnitPosition = { x = CurrentPoint.x, y = CurrentPoint.z } local UnitHeight = CurrentPoint.y local LandHeight = land.getHeight( CurrentPosition ) -- env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) return UnitHeight - LandHeight end _MusicTable = {} _MusicTable.Files = {} _MusicTable.Queue = {} _MusicTable.FileCnt = 0 function MusicRegister( SndRef, SndFile, SndTime ) -- trace.f() env.info( ('MusicRegister: SndRef = ' .. SndRef) ) env.info( ('MusicRegister: SndFile = ' .. SndFile) ) env.info( ('MusicRegister: SndTime = ' .. SndTime) ) _MusicTable.FileCnt = _MusicTable.FileCnt + 1 _MusicTable.Files[_MusicTable.FileCnt] = {} _MusicTable.Files[_MusicTable.FileCnt].Ref = SndRef _MusicTable.Files[_MusicTable.FileCnt].File = SndFile _MusicTable.Files[_MusicTable.FileCnt].Time = SndTime if not _MusicTable.Function then _MusicTable.Function = routines.scheduleFunction( MusicScheduler, {}, timer.getTime() + 10, 10 ) end end function MusicToPlayer( SndRef, PlayerName, SndContinue ) -- trace.f() -- env.info(( 'MusicToPlayer: SndRef = ' .. SndRef )) local PlayerUnits = AlivePlayerUnits() for PlayerUnitIdx, PlayerUnit in pairs( PlayerUnits ) do local PlayerUnitName = PlayerUnit:getPlayerName() -- env.info(( 'MusicToPlayer: PlayerUnitName = ' .. PlayerUnitName )) if PlayerName == PlayerUnitName then PlayerGroup = PlayerUnit:getGroup() if PlayerGroup then -- env.info(( 'MusicToPlayer: PlayerGroup = ' .. PlayerGroup:getName() )) MusicToGroup( SndRef, PlayerGroup, SndContinue ) end break end end -- env.info(( 'MusicToPlayer: end' )) end function MusicToGroup( SndRef, SndGroup, SndContinue ) -- trace.f() -- env.info(( 'MusicToGroup: SndRef = ' .. SndRef )) if SndGroup ~= nil then if _MusicTable and _MusicTable.FileCnt > 0 then if SndGroup:isExist() then if MusicCanStart( SndGroup:getUnit( 1 ):getPlayerName() ) then -- env.info(( 'MusicToGroup: OK for Sound.' )) local SndIdx = 0 if SndRef == '' then -- env.info(( 'MusicToGroup: SndRef as empty. Queueing at random.' )) SndIdx = math.random( 1, _MusicTable.FileCnt ) else for SndIdx = 1, _MusicTable.FileCnt do if _MusicTable.Files[SndIdx].Ref == SndRef then break end end end -- env.info(( 'MusicToGroup: SndIdx = ' .. SndIdx )) -- env.info(( 'MusicToGroup: Queueing Music ' .. _MusicTable.Files[SndIdx].File .. ' for Group ' .. SndGroup:getID() )) trigger.action.outSoundForGroup( SndGroup:getID(), _MusicTable.Files[SndIdx].File ) MessageToGroup( SndGroup, 'Playing ' .. _MusicTable.Files[SndIdx].File, 15, 'Music-' .. SndGroup:getUnit( 1 ):getPlayerName() ) local SndQueueRef = SndGroup:getUnit( 1 ):getPlayerName() if _MusicTable.Queue[SndQueueRef] == nil then _MusicTable.Queue[SndQueueRef] = {} end _MusicTable.Queue[SndQueueRef].Start = timer.getTime() _MusicTable.Queue[SndQueueRef].PlayerName = SndGroup:getUnit( 1 ):getPlayerName() _MusicTable.Queue[SndQueueRef].Group = SndGroup _MusicTable.Queue[SndQueueRef].ID = SndGroup:getID() _MusicTable.Queue[SndQueueRef].Ref = SndIdx _MusicTable.Queue[SndQueueRef].Continue = SndContinue _MusicTable.Queue[SndQueueRef].Type = Group end end end end end function MusicCanStart( PlayerName ) -- trace.f() -- env.info(( 'MusicCanStart:' )) local MusicOut = false if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then -- env.info(( 'MusicCanStart: PlayerName = ' .. PlayerName )) local PlayerFound = false local MusicStart = 0 local MusicTime = 0 for SndQueueIdx, SndQueue in pairs( _MusicTable.Queue ) do if SndQueue.PlayerName == PlayerName then PlayerFound = true MusicStart = SndQueue.Start MusicTime = _MusicTable.Files[SndQueue.Ref].Time break end end if PlayerFound then -- env.info(( 'MusicCanStart: MusicStart = ' .. MusicStart )) -- env.info(( 'MusicCanStart: MusicTime = ' .. MusicTime )) -- env.info(( 'MusicCanStart: timer.getTime() = ' .. timer.getTime() )) if MusicStart + MusicTime <= timer.getTime() then MusicOut = true end else MusicOut = true end end if MusicOut then -- env.info(( 'MusicCanStart: true' )) else -- env.info(( 'MusicCanStart: false' )) end return MusicOut end function MusicScheduler() -- trace.scheduled("", "MusicScheduler") -- env.info(( 'MusicScheduler:' )) if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then -- env.info(( 'MusicScheduler: Walking Sound Queue.')) for SndQueueIdx, SndQueue in pairs( _MusicTable.Queue ) do if SndQueue.Continue then if MusicCanStart( SndQueue.PlayerName ) then -- env.info(('MusicScheduler: MusicToGroup')) MusicToPlayer( '', SndQueue.PlayerName, true ) end end end end end env.info( ('Init: Scripts Loaded v1.1') )