From 3f0c983194f031a09fe8b58d47af6ce2dd7e29b0 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Fri, 17 Mar 2017 13:59:19 +0100 Subject: [PATCH] Updates --- Moose Development/Moose/Actions/Act_Route.lua | 14 +- Moose Development/Moose/Core/Event.lua | 4 +- .../Moose/Functional/Detection.lua | 6 +- .../Moose/Tasking/Task_A2G_Dispatcher.lua | 16 +- .../l10n/DEFAULT/Moose.lua | 35179 +--------------- Moose Mission Setup/Moose.lua | 35179 +--------------- Moose Presentations/TASK_DISPATCHER.pptx | Bin 10778702 -> 10047754 bytes ...- A2G Task Dispatching DETECTION_UNITS.lua | 4 +- ...- A2G Task Dispatching DETECTION_UNITS.miz | Bin 270350 -> 54503 bytes docs/Documentation/Cargo.html | 1 - docs/Documentation/Detection.html | 1 - docs/Documentation/MOVEMENT.html | 4 - docs/Documentation/Spawn.html | 37 +- docs/Documentation/Task_A2G_Dispatcher.html | 41 +- 14 files changed, 81 insertions(+), 70405 deletions(-) diff --git a/Moose Development/Moose/Actions/Act_Route.lua b/Moose Development/Moose/Actions/Act_Route.lua index 201393901..ec5e9fff3 100644 --- a/Moose Development/Moose/Actions/Act_Route.lua +++ b/Moose Development/Moose/Actions/Act_Route.lua @@ -256,12 +256,14 @@ do -- ACT_ROUTE_POINT -- @return #boolean function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit ) - local Distance = self.PointVec2:Get2DDistance( ProcessUnit:GetPointVec2() ) - - if Distance <= self.Range then - local RouteText = "You have arrived." - self:Message( RouteText ) - return true + if ProcessUnit:IsAlive() then + local Distance = self.PointVec2:Get2DDistance( ProcessUnit:GetPointVec2() ) + + if Distance <= self.Range then + local RouteText = "You have arrived." + self:Message( RouteText ) + return true + end end return false diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index c80e7bc62..9c64d9b9f 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -425,11 +425,11 @@ function EVENT:Init( EventID, EventClass ) -- Each event has a subtable of EventClasses, ordered by EventPriority. local EventPriority = EventClass:GetEventPriority() if not self.Events[EventID][EventPriority] then - self.Events[EventID][EventPriority] = setmetatable( {}, { __mode = "kv" } ) + self.Events[EventID][EventPriority] = setmetatable( {}, { __mode = "k" } ) end if not self.Events[EventID][EventPriority][EventClass] then - self.Events[EventID][EventPriority][EventClass] = {} + self.Events[EventID][EventPriority][EventClass] = setmetatable( {}, { __mode = "v" } ) end return self.Events[EventID][EventPriority][EventClass] end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index b02a3182f..5975416be 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -1379,10 +1379,10 @@ do -- DETECTION_UNITS if DetectedItemUnit then self:T(DetectedItemUnit) - local UnitCategoryName = DetectedItemUnit:GetCategoryName() - local UnitCategoryType = DetectedItemUnit:GetTypeName() + local UnitCategoryName = DetectedItemUnit:GetCategoryName() or "" + local UnitCategoryType = DetectedItemUnit:GetTypeName() or "" - if DetectedItem.Type then + if DetectedItem.Type and UnitCategoryName and UnitCategoryType then UnitCategoryText = UnitCategoryName .. " (" .. UnitCategoryType .. ") at " else UnitCategoryText = "Unknown target at " diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index a495301d5..06c3a4234 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -50,28 +50,26 @@ do -- TASK_A2G_DISPATCHER -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @field Tasking.Mission#MISSION Mission - -- @field Wrapper.Group#GROUP CommandCenter -- @extends Tasking.DetectionManager#DETECTION_MANAGER TASK_A2G_DISPATCHER = { ClassName = "TASK_A2G_DISPATCHER", Mission = nil, - CommandCenter = nil, Detection = nil, } --- TASK_A2G_DISPATCHER constructor. -- @param #TASK_A2G_DISPATCHER self - -- @param Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_BASE Detection + -- @param Tasking.Mission#MISSION The mission for which the task dispatching is done. + -- @param Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission. + -- @param Functional.Detection#DETECTION_BASE Detection The detection results that are used to dynamically assign new tasks to human players. -- @return #TASK_A2G_DISPATCHER self - function TASK_A2G_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) + function TASK_A2G_DISPATCHER:New( Mission, SetGroup, Detection ) -- Inherits from DETECTION_MANAGER local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER self.Detection = Detection - self.CommandCenter = CommandCenter self.Mission = Mission self:Schedule( 30 ) @@ -245,7 +243,7 @@ do -- TASK_A2G_DISPATCHER local BAITask = Mission:GetTask( string.format( "BAI.%03d", ItemID ) ) BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedItem ) if not BAITask then - local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... + local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then local Task = TASK_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", ItemID ), TargetSetUnit ) Task:SetTargetZone( DetectedZone ) @@ -272,12 +270,12 @@ do -- TASK_A2G_DISPATCHER for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do if not TaskGroup:GetState( TaskGroup, "Assigned" ) then - self.CommandCenter:MessageToGroup( + Mission:GetCommandCenter():MessageToGroup( string.format( "HQ Reporting - Planned tasks for mission '%s':\n%s\n", self.Mission:GetName(), string.format( "%s\n%s\n%s\n%s", ReportSEAD:Text(), ReportCAS:Text(), ReportBAI:Text(), ReportChanges:Text() ) - ), self:GetReportDisplayTime(), TaskGroup + ), TaskGroup ) end end diff --git a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua index 11c91cda5..0768235fb 100644 --- a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua +++ b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua @@ -1,35176 +1,31 @@ -env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20170317_1149' ) +env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) +env.info( 'Moose Generation Timestamp: 20170317_1239' ) + local base = _G Include = {} -Include.Files = {} + Include.File = function( IncludeFile ) -end - ---- Various routines --- @module routines --- @author Flightcontrol - -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 = {} - ---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) + if not Include.Files[ IncludeFile ] then + Include.Files[IncludeFile] = IncludeFile + env.info( "Include:" .. IncludeFile .. " from " .. Include.ProgramPath ) + local f = assert( base.loadfile( Include.ProgramPath .. IncludeFile .. ".lua" ) ) + if f == nil then + error ("Could not load MOOSE file " .. IncludeFile .. ".lua" ) else - return tostring(tbl) + env.info( "Include:" .. IncludeFile .. " loaded from " .. Include.ProgramPath ) + return f() 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('%q', s) - 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 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) - - return -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) - - return -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.RED ) -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' )) - ---- This module contains derived utilities taken from the MIST framework, --- which are excellent tools to be reused in an OO environment!. --- --- ### Authors: --- --- * Grimes : Design & Programming of the MIST framework. --- --- ### Contributions: --- --- * FlightControl : Rework to OO framework --- --- @module Utils - - ---- @type SMOKECOLOR --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - -SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR - ---- @type FLARECOLOR --- @field Green --- @field Red --- @field White --- @field Yellow - -FLARECOLOR = trigger.flareColor -- #FLARECOLOR - ---- Utilities static class. --- @type UTILS -UTILS = {} - - ---from http://lua-users.org/wiki/CopyTable -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 -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] = "f() " .. 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 - return tostring(tbl) - end - end - - local objectreturn = _Serialize(tbl) - return objectreturn -end - ---porting in Slmod's "safestring" basic serialize -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 - - -UTILS.ToDegree = function(angle) - return angle*180/math.pi -end - -UTILS.ToRadian = function(angle) - return angle*math.pi/180 -end - -UTILS.MetersToNM = function(meters) - return meters/1852 -end - -UTILS.MetersToFeet = function(meters) - return meters/0.3048 -end - -UTILS.NMToMeters = function(NM) - return NM*1852 -end - -UTILS.FeetToMeters = function(feet) - return feet*0.3048 -end - -UTILS.MpsToKnots = function(mps) - return mps*3600/1852 -end - -UTILS.MpsToKmph = function(mps) - return mps*3.6 -end - -UTILS.KnotsToMps = function(knots) - return knots*1852/3600 -end - -UTILS.KmphToMps = function(kmph) - return kmph/3.6 -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. -]] -UTILS.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 = UTILS.Round((oldLatMin - latMin)*60, acc) - - local oldLonMin = lonMin - lonMin = math.floor(lonMin) - local lonSec = 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 = UTILS.Round(latMin, acc) - lonMin = 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 - - ---- From http://lua-users.org/wiki/SimpleRound --- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place -function UTILS.Round( num, idp ) - local mult = 10 ^ ( idp or 0 ) - return math.floor( num * mult + 0.5 ) / mult -end - --- porting in Slmod's dostring -function UTILS.DoString( s ) - local f, err = loadstring( s ) - if f then - return true, f() - else - return false, err - end -end ---- **Core** - BASE forms **the basis of the MOOSE framework**. Each class within the MOOSE framework derives from BASE. --- --- ![Banner Image](..\Presentations\BASE\Dia1.JPG) --- --- === --- --- # 1) @{#BASE} class --- --- All classes within the MOOSE framework are derived from the @{#BASE} class. --- --- BASE provides facilities for : --- --- * The construction and inheritance of MOOSE classes. --- * The class naming and numbering system. --- * The class hierarchy search system. --- * The tracing of information or objects during mission execution for debuggin purposes. --- * The subscription to DCS events for event handling in MOOSE objects. --- --- Note: The BASE class is an abstract class and is not meant to be used directly. --- --- ## 1.1) BASE constructor --- --- Any class derived from BASE, will use the @{Base#BASE.New} constructor embedded in the @{Base#BASE.Inherit} method. --- See an example at the @{Base#BASE.New} method how this is done. --- --- ## 1.2) Trace information for debugging --- --- The BASE class contains trace methods to trace progress within a mission execution of a certain object. --- These trace methods are inherited by each MOOSE class interiting BASE, soeach object created from derived class from BASE can use the tracing methods to trace its execution. --- --- Any type of information can be passed to these tracing methods. See the following examples: --- --- self:E( "Hello" ) --- --- Result in the word "Hello" in the dcs.log. --- --- local Array = { 1, nil, "h", { "a","b" }, "x" } --- self:E( Array ) --- --- Results with the text [1]=1,[3]="h",[4]={[1]="a",[2]="b"},[5]="x"} in the dcs.log. --- --- local Object1 = "Object1" --- local Object2 = 3 --- local Object3 = { Object 1, Object 2 } --- self:E( { Object1, Object2, Object3 } ) --- --- Results with the text [1]={[1]="Object",[2]=3,[3]={[1]="Object",[2]=3}} in the dcs.log. --- --- local SpawnObject = SPAWN:New( "Plane" ) --- local GroupObject = GROUP:FindByName( "Group" ) --- self:E( { Spawn = SpawnObject, Group = GroupObject } ) --- --- Results with the text [1]={Spawn={....),Group={...}} in the dcs.log. --- --- Below a more detailed explanation of the different method types for tracing. --- --- ### 1.2.1) Tracing methods categories --- --- There are basically 3 types of tracing methods available: --- --- * @{#BASE.F}: Used to trace the entrance of a function and its given parameters. An F is indicated at column 44 in the DCS.log file. --- * @{#BASE.T}: Used to trace further logic within a function giving optional variables or parameters. A T is indicated at column 44 in the DCS.log file. --- * @{#BASE.E}: Used to always trace information giving optional variables or parameters. An E is indicated at column 44 in the DCS.log file. --- --- ### 1.2.2) Tracing levels --- --- There are 3 tracing levels within MOOSE. --- These tracing levels were defined to avoid bulks of tracing to be generated by lots of objects. --- --- As such, the F and T methods have additional variants to trace level 2 and 3 respectively: --- --- * @{#BASE.F2}: Trace the beginning of a function and its given parameters with tracing level 2. --- * @{#BASE.F3}: Trace the beginning of a function and its given parameters with tracing level 3. --- * @{#BASE.T2}: Trace further logic within a function giving optional variables or parameters with tracing level 2. --- * @{#BASE.T3}: Trace further logic within a function giving optional variables or parameters with tracing level 3. --- --- ### 1.2.3) Trace activation. --- --- Tracing can be activated in several ways: --- --- * Switch tracing on or off through the @{#BASE.TraceOnOff}() method. --- * Activate all tracing through the @{#BASE.TraceAll}() method. --- * Activate only the tracing of a certain class (name) through the @{#BASE.TraceClass}() method. --- * Activate only the tracing of a certain method of a certain class through the @{#BASE.TraceClassMethod}() method. --- * Activate only the tracing of a certain level through the @{#BASE.TraceLevel}() method. --- --- ### 1.2.4) Check if tracing is on. --- --- The method @{#BASE.IsTrace}() will validate if tracing is activated or not. --- --- ## 1.3 DCS simulator Event Handling --- --- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator, --- and handled through lua scripting. MOOSE provides an encapsulation to handle these events more efficiently. --- --- ### 1.3.1 Subscribe / Unsubscribe to DCS Events --- --- At first, the mission designer will need to **Subscribe** to a specific DCS event for the class. --- So, when the DCS event occurs, the class will be notified of that event. --- There are two methods which you use to subscribe to or unsubscribe from an event. --- --- * @{#BASE.HandleEvent}(): Subscribe to a DCS Event. --- * @{#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event. --- --- ### 1.3.2 Event Handling of DCS Events --- --- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called --- when the DCS event occurs. The Event Handling method receives an @{Event#EVENTDATA} structure, which contains a lot of information --- about the event that occurred. --- --- Find below an example of the prototype how to write an event handling function for two units: --- --- local Tank1 = UNIT:FindByName( "Tank A" ) --- local Tank2 = UNIT:FindByName( "Tank B" ) --- --- -- Here we subscribe to the Dead events. So, if one of these tanks dies, the Tank1 or Tank2 objects will be notified. --- Tank1:HandleEvent( EVENTS.Dead ) --- Tank2:HandleEvent( EVENTS.Dead ) --- --- --- This function is an Event Handling function that will be called when Tank1 is Dead. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank1:OnEventDead( EventData ) --- --- self:SmokeGreen() --- end --- --- --- This function is an Event Handling function that will be called when Tank2 is Dead. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank2:OnEventDead( EventData ) --- --- self:SmokeBlue() --- end --- --- --- --- See the @{Event} module for more information about event handling. --- --- ## 1.4) Class identification methods --- --- BASE provides methods to get more information of each object: --- --- * @{#BASE.GetClassID}(): Gets the ID (number) of the object. Each object created is assigned a number, that is incremented by one. --- * @{#BASE.GetClassName}(): Gets the name of the object, which is the name of the class the object was instantiated from. --- * @{#BASE.GetClassNameAndID}(): Gets the name and ID of the object. --- --- ## 1.5) All objects derived from BASE can have "States" --- --- A mechanism is in place in MOOSE, that allows to let the objects administer **states**. --- States are essentially properties of objects, which are identified by a **Key** and a **Value**. --- --- The method @{#BASE.SetState}() can be used to set a Value with a reference Key to the object. --- To **read or retrieve** a state Value based on a Key, use the @{#BASE.GetState} method. --- --- These two methods provide a very handy way to keep state at long lasting processes. --- Values can be stored within the objects, and later retrieved or changed when needed. --- There is one other important thing to note, the @{#BASE.SetState}() and @{#BASE.GetState} methods --- receive as the **first parameter the object for which the state needs to be set**. --- Thus, if the state is to be set for the same object as the object for which the method is used, then provide the same --- object name to the method. --- --- ## 1.10) Inheritance --- --- The following methods are available to implement inheritance --- --- * @{#BASE.Inherit}: Inherits from a class. --- * @{#BASE.GetParent}: Returns the parent object from the object it is handling, or nil if there is no parent object. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- YYYY-MM-DD: CLASS:**NewFunction**( Params ) replaces CLASS:_OldFunction_( Params ) --- YYYY-MM-DD: CLASS:**NewFunction( Params )** added --- --- Hereby the change log: --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * None. --- --- ### Authors: --- --- * **FlightControl**: Design & Programming --- --- @module Base - - - -local _TraceOnOff = true -local _TraceLevel = 1 -local _TraceAll = false -local _TraceClass = {} -local _TraceClassMethod = {} - -local _ClassID = 0 - ---- The BASE Class --- @type BASE --- @field ClassName The name of the class. --- @field ClassID The ID number of the class. --- @field ClassNameAndID The name of the class concatenated with the ID number of the class. -BASE = { - ClassName = "BASE", - ClassID = 0, - _Private = {}, - Events = {}, - States = {} -} - ---- The Formation Class --- @type FORMATION --- @field Cone A cone formation. -FORMATION = { - Cone = "Cone" -} - - - ---- BASE constructor. --- --- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE. --- --- function EVENT:New() --- local self = BASE:Inherit( self, BASE:New() ) -- #EVENT --- return self --- end --- --- @param #BASE self --- @return #BASE -function BASE:New() - local self = routines.utils.deepCopy( self ) -- Create a new self instance - local MetaTable = {} - setmetatable( self, MetaTable ) - self.__index = self - _ClassID = _ClassID + 1 - self.ClassID = _ClassID - - - return self -end - -function BASE:_Destructor() - --self:E("_Destructor") - - --self:EventRemoveAll() -end - - --- THIS IS WHY WE NEED LUA 5.2 ... -function BASE:_SetDestructor() - - -- TODO: Okay, this is really technical... - -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak... - -- Therefore, I am parking this logic until I've properly discussed all this with the community. - - local proxy = newproxy(true) - local proxyMeta = getmetatable(proxy) - - proxyMeta.__gc = function () - env.info("In __gc for " .. self:GetClassNameAndID() ) - if self._Destructor then - self:_Destructor() - end - end - - -- keep the userdata from newproxy reachable until the object - -- table is about to be garbage-collected - then the __gc hook - -- will be invoked and the destructor called - rawset( self, '__proxy', proxy ) - -end - ---- This is the worker method to inherit from a parent class. --- @param #BASE self --- @param Child is the Child class that inherits. --- @param #BASE Parent is the Parent class that the Child inherits from. --- @return #BASE Child -function BASE:Inherit( Child, Parent ) - local Child = routines.utils.deepCopy( Child ) - --local Parent = routines.utils.deepCopy( Parent ) - --local Parent = Parent - if Child ~= nil then - setmetatable( Child, Parent ) - Child.__index = Child - - --Child:_SetDestructor() - end - --self:T( 'Inherited from ' .. Parent.ClassName ) - return Child -end - ---- This is the worker method to retrieve the Parent class. --- Note that the Parent class must be passed to call the parent class method. --- --- self:GetParent(self):ParentMethod() --- --- --- @param #BASE self --- @param #BASE Child is the Child class from which the Parent class needs to be retrieved. --- @return #BASE -function BASE:GetParent( Child ) - local Parent = getmetatable( Child ) --- env.info('Inherited class of ' .. Child.ClassName .. ' is ' .. Parent.ClassName ) - return Parent -end - ---- Get the ClassName + ClassID of the class instance. --- The ClassName + ClassID is formatted as '%s#%09d'. --- @param #BASE self --- @return #string The ClassName + ClassID of the class instance. -function BASE:GetClassNameAndID() - return string.format( '%s#%09d', self.ClassName, self.ClassID ) -end - ---- Get the ClassName of the class instance. --- @param #BASE self --- @return #string The ClassName of the class instance. -function BASE:GetClassName() - return self.ClassName -end - ---- Get the ClassID of the class instance. --- @param #BASE self --- @return #string The ClassID of the class instance. -function BASE:GetClassID() - return self.ClassID -end - -do -- Event Handling - - --- Returns the event dispatcher - -- @param #BASE self - -- @return Core.Event#EVENT - function BASE:EventDispatcher() - - return _EVENTDISPATCHER - end - - - --- Get the Class @{Event} processing Priority. - -- The Event processing Priority is a number from 1 to 10, - -- reflecting the order of the classes subscribed to the Event to be processed. - -- @param #BASE self - -- @return #number The @{Event} processing Priority. - function BASE:GetEventPriority() - return self._Private.EventPriority or 5 - end - - --- Set the Class @{Event} processing Priority. - -- The Event processing Priority is a number from 1 to 10, - -- reflecting the order of the classes subscribed to the Event to be processed. - -- @param #BASE self - -- @param #number EventPriority The @{Event} processing Priority. - -- @return self - function BASE:SetEventPriority( EventPriority ) - self._Private.EventPriority = EventPriority - end - - --- Remove all subscribed events - -- @param #BASE self - -- @return #BASE - function BASE:EventRemoveAll() - - self:EventDispatcher():RemoveAll( self ) - - return self - end - - --- Subscribe to a DCS Event. - -- @param #BASE self - -- @param Core.Event#EVENTS Event - -- @param #function EventFunction (optional) The function to be called when the event occurs for the unit. - -- @return #BASE - function BASE:HandleEvent( Event, EventFunction ) - - self:EventDispatcher():OnEventGeneric( EventFunction, self, Event ) - - return self - end - - --- UnSubscribe to a DCS event. - -- @param #BASE self - -- @param Core.Event#EVENTS Event - -- @return #BASE - function BASE:UnHandleEvent( Event ) - - self:EventDispatcher():Remove( self, Event ) - - return self - end - - -- Event handling function prototypes - - --- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or autocannon based weapon, those are handled by EVENT.ShootingStart. - -- @function [parent=#BASE] OnEventShot - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs whenever an object is hit by a weapon. - -- initiator : The unit object the fired the weapon - -- weapon: Weapon object that hit the target - -- target: The Object that was hit. - -- @function [parent=#BASE] OnEventHit - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an aircraft takes off from an airbase, farp, or ship. - -- initiator : The unit that tookoff - -- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships - -- @function [parent=#BASE] OnEventTakeoff - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an aircraft lands at an airbase, farp or ship - -- initiator : The unit that has landed - -- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships - -- @function [parent=#BASE] OnEventLand - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any aircraft crashes into the ground and is completely destroyed. - -- initiator : The unit that has crashed - -- @function [parent=#BASE] OnEventCrash - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when a pilot ejects from an aircraft - -- initiator : The unit that has ejected - -- @function [parent=#BASE] OnEventEjection - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an aircraft connects with a tanker and begins taking on fuel. - -- initiator : The unit that is receiving fuel. - -- @function [parent=#BASE] OnEventRefueling - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an object is completely destroyed. - -- initiator : The unit that is was destroyed. - -- @function [parent=#BASE] OnEvent - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when the pilot of an aircraft is killed. Can occur either if the player is alive and crashes or if a weapon kills the pilot without completely destroying the plane. - -- initiator : The unit that the pilot has died in. - -- @function [parent=#BASE] OnEventPilotDead - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when a ground unit captures either an airbase or a farp. - -- initiator : The unit that captured the base - -- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction. - -- @function [parent=#BASE] OnEventBaseCaptured - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when a mission starts - -- @function [parent=#BASE] OnEventMissionStart - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when a mission ends - -- @function [parent=#BASE] OnEventMissionEnd - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an aircraft is finished taking fuel. - -- initiator : The unit that was receiving fuel. - -- @function [parent=#BASE] OnEventRefuelingStop - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any object is spawned into the mission. - -- initiator : The unit that was spawned - -- @function [parent=#BASE] OnEventBirth - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any system fails on a human controlled aircraft. - -- initiator : The unit that had the failure - -- @function [parent=#BASE] OnEventHumanFailure - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any aircraft starts its engines. - -- initiator : The unit that is starting its engines. - -- @function [parent=#BASE] OnEventEngineStartup - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any aircraft shuts down its engines. - -- initiator : The unit that is stopping its engines. - -- @function [parent=#BASE] OnEventEngineShutdown - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any player assumes direct control of a unit. - -- initiator : The unit that is being taken control of. - -- @function [parent=#BASE] OnEventPlayerEnterUnit - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any player relieves control of a unit to the AI. - -- initiator : The unit that the player left. - -- @function [parent=#BASE] OnEventPlayerLeaveUnit - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns. - -- initiator : The unit that is doing the shooing. - -- target: The unit that is being targeted. - -- @function [parent=#BASE] OnEventShootingStart - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any unit stops firing its weapon. Event will always correspond with a shooting start event. - -- initiator : The unit that was doing the shooing. - -- @function [parent=#BASE] OnEventShootingEnd - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - -end - - ---- Creation of a Birth Event. --- @param #BASE self --- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. --- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. --- @param #string IniUnitName The initiating unit name. --- @param place --- @param subplace -function BASE:CreateEventBirth( EventTime, Initiator, IniUnitName, place, subplace ) - self:F( { EventTime, Initiator, IniUnitName, place, subplace } ) - - local Event = { - id = world.event.S_EVENT_BIRTH, - time = EventTime, - initiator = Initiator, - IniUnitName = IniUnitName, - place = place, - subplace = subplace - } - - world.onEvent( Event ) -end - ---- Creation of a Crash Event. --- @param #BASE self --- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. --- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. -function BASE:CreateEventCrash( EventTime, Initiator ) - self:F( { EventTime, Initiator } ) - - local Event = { - id = world.event.S_EVENT_CRASH, - time = EventTime, - initiator = Initiator, - } - - world.onEvent( Event ) -end - --- TODO: Complete Dcs.DCSTypes#Event structure. ---- The main event handling function... This function captures all events generated for the class. --- @param #BASE self --- @param Dcs.DCSTypes#Event event -function BASE:onEvent(event) - --self:F( { BaseEventCodes[event.id], event } ) - - if self then - for EventID, EventObject in pairs( self.Events ) do - if EventObject.EventEnabled then - --env.info( 'onEvent Table EventObject.Self = ' .. tostring(EventObject.Self) ) - --env.info( 'onEvent event.id = ' .. tostring(event.id) ) - --env.info( 'onEvent EventObject.Event = ' .. tostring(EventObject.Event) ) - if event.id == EventObject.Event then - if self == EventObject.Self then - if event.initiator and event.initiator:isExist() then - event.IniUnitName = event.initiator:getName() - end - if event.target and event.target:isExist() then - event.TgtUnitName = event.target:getName() - end - --self:T( { BaseEventCodes[event.id], event } ) - --EventObject.EventFunction( self, event ) - end - end - end - end - end -end - ---- Set a state or property of the Object given a Key and a Value. --- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone. --- @param #BASE self --- @param Object The object that will hold the Value set by the Key. --- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! --- @param Value The value to is stored in the object. --- @return The Value set. --- @return #nil The Key was not found and thus the Value could not be retrieved. -function BASE:SetState( Object, Key, Value ) - - local ClassNameAndID = Object:GetClassNameAndID() - - self.States[ClassNameAndID] = self.States[ClassNameAndID] or {} - self.States[ClassNameAndID][Key] = Value - self:T2( { ClassNameAndID, Key, Value } ) - - return self.States[ClassNameAndID][Key] -end - - ---- Get a Value given a Key from the Object. --- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone. --- @param #BASE self --- @param Object The object that holds the Value set by the Key. --- @param Key The key that is used to retrieve the value. Note that the key can be a #string, but it can also be any other type! --- @param Value The value to is stored in the Object. --- @return The Value retrieved. -function BASE:GetState( Object, Key ) - - local ClassNameAndID = Object:GetClassNameAndID() - - if self.States[ClassNameAndID] then - local Value = self.States[ClassNameAndID][Key] or false - self:T2( { ClassNameAndID, Key, Value } ) - return Value - end - - return nil -end - -function BASE:ClearState( Object, StateName ) - - local ClassNameAndID = Object:GetClassNameAndID() - if self.States[ClassNameAndID] then - self.States[ClassNameAndID][StateName] = nil - end -end - --- Trace section - --- Log a trace (only shown when trace is on) --- TODO: Make trace function using variable parameters. - ---- Set trace on or off --- Note that when trace is off, no debug statement is performed, increasing performance! --- When Moose is loaded statically, (as one file), tracing is switched off by default. --- So tracing must be switched on manually in your mission if you are using Moose statically. --- When moose is loading dynamically (for moose class development), tracing is switched on by default. --- @param #BASE self --- @param #boolean TraceOnOff Switch the tracing on or off. --- @usage --- -- Switch the tracing On --- BASE:TraceOnOff( true ) --- --- -- Switch the tracing Off --- BASE:TraceOnOff( false ) -function BASE:TraceOnOff( TraceOnOff ) - _TraceOnOff = TraceOnOff -end - - ---- Enquires if tracing is on (for the class). --- @param #BASE self --- @return #boolean -function BASE:IsTrace() - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - return true - else - return false - end -end - ---- Set trace level --- @param #BASE self --- @param #number Level -function BASE:TraceLevel( Level ) - _TraceLevel = Level - self:E( "Tracing level " .. Level ) -end - ---- Trace all methods in MOOSE --- @param #BASE self --- @param #boolean TraceAll true = trace all methods in MOOSE. -function BASE:TraceAll( TraceAll ) - - _TraceAll = TraceAll - - if _TraceAll then - self:E( "Tracing all methods in MOOSE " ) - else - self:E( "Switched off tracing all methods in MOOSE" ) - end -end - ---- Set tracing for a class --- @param #BASE self --- @param #string Class -function BASE:TraceClass( Class ) - _TraceClass[Class] = true - _TraceClassMethod[Class] = {} - self:E( "Tracing class " .. Class ) -end - ---- Set tracing for a specific method of class --- @param #BASE self --- @param #string Class --- @param #string Method -function BASE:TraceClassMethod( Class, Method ) - if not _TraceClassMethod[Class] then - _TraceClassMethod[Class] = {} - _TraceClassMethod[Class].Method = {} - end - _TraceClassMethod[Class].Method[Method] = true - self:E( "Tracing method " .. Method .. " of class " .. Class ) -end - ---- Trace a function call. This function is private. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - - local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo( 2, "nl" ) - local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - if _TraceAll == true or _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName].Method[Function] then - local LineCurrent = 0 - if DebugInfoCurrent.currentline then - LineCurrent = DebugInfoCurrent.currentline - end - local LineFrom = 0 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) - end - end -end - ---- Trace a function call. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 1 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - - ---- Trace a function call level 2. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F2( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 2 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function call level 3. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F3( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 3 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - - local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo( 2, "nl" ) - local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - if _TraceAll == true or _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName].Method[Function] then - local LineCurrent = 0 - if DebugInfoCurrent.currentline then - LineCurrent = DebugInfoCurrent.currentline - end - local LineFrom = 0 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) - end - end -end - ---- Trace a function logic level 1. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 1 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - - ---- Trace a function logic level 2. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T2( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 2 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function logic level 3. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T3( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 3 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Log an exception which will be traced always. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:E( Arguments ) - - if debug then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - local LineCurrent = DebugInfoCurrent.currentline - local LineFrom = -1 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) - end - -end - - - ---- **Core** - SCHEDULER prepares and handles the **execution of functions over scheduled time (intervals)**. --- --- ![Banner Image](..\Presentations\SCHEDULER\Dia1.JPG) --- --- === --- --- # 1) @{Scheduler#SCHEDULER} class, extends @{Base#BASE} --- --- The @{Scheduler#SCHEDULER} class creates schedule. --- --- ## 1.1) SCHEDULER constructor --- --- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: --- --- * @{Scheduler#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. --- * @{Scheduler#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. --- * @{Scheduler#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. --- * @{Scheduler#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. --- --- ## 1.2) SCHEDULER timer stopping and (re-)starting. --- --- The SCHEDULER can be stopped and restarted with the following methods: --- --- * @{Scheduler#SCHEDULER.Start}(): (Re-)Start the schedules within the SCHEDULER object. If a CallID is provided to :Start(), only the schedule referenced by CallID will be (re-)started. --- * @{Scheduler#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped. --- --- ## 1.3) Create a new schedule --- --- With @{Scheduler#SCHEDULER.Schedule}() a new time event can be scheduled. This function is used by the :New() constructor when a new schedule is planned. --- --- === --- --- ### Contributions: --- --- * FlightControl : Concept & Testing --- --- ### Authors: --- --- * FlightControl : Design & Programming --- --- ### Test Missions: --- --- * SCH - Scheduler --- --- === --- --- @module Scheduler - - ---- The SCHEDULER class --- @type SCHEDULER --- @field #number ScheduleID the ID of the scheduler. --- @extends Core.Base#BASE -SCHEDULER = { - ClassName = "SCHEDULER", - Schedules = {}, -} - ---- SCHEDULER constructor. --- @param #SCHEDULER self --- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. --- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. --- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. --- @return #SCHEDULER self. --- @return #number The ScheduleID of the planned schedule. -function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( { Start, Repeat, RandomizeFactor, Stop } ) - - local ScheduleID = nil - - self.MasterObject = SchedulerObject - - if SchedulerFunction then - ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - end - - return self, ScheduleID -end - ---function SCHEDULER:_Destructor() --- --self:E("_Destructor") --- --- _SCHEDULEDISPATCHER:RemoveSchedule( self.CallID ) ---end - ---- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also. --- @param #SCHEDULER self --- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. --- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. --- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. --- @return #number The ScheduleID of the planned schedule. -function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - self:F2( { Start, Repeat, RandomizeFactor, Stop } ) - self:T3( { SchedulerArguments } ) - - local ObjectName = "-" - if SchedulerObject and SchedulerObject.ClassName and SchedulerObject.ClassID then - ObjectName = SchedulerObject.ClassName .. SchedulerObject.ClassID - end - self:F3( { "Schedule :", ObjectName, tostring( SchedulerObject ), Start, Repeat, RandomizeFactor, Stop } ) - self.SchedulerObject = SchedulerObject - - local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( - self, - SchedulerFunction, - SchedulerArguments, - Start, - Repeat, - RandomizeFactor, - Stop - ) - - self.Schedules[#self.Schedules+1] = ScheduleID - - return ScheduleID -end - ---- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided. --- @param #SCHEDULER self --- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. -function SCHEDULER:Start( ScheduleID ) - self:F3( { ScheduleID } ) - - _SCHEDULEDISPATCHER:Start( self, ScheduleID ) -end - ---- Stops the schedules or a specific schedule if a valid ScheduleID is provided. --- @param #SCHEDULER self --- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. -function SCHEDULER:Stop( ScheduleID ) - self:F3( { ScheduleID } ) - - _SCHEDULEDISPATCHER:Stop( self, ScheduleID ) -end - ---- Removes a specific schedule if a valid ScheduleID is provided. --- @param #SCHEDULER self --- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. -function SCHEDULER:Remove( ScheduleID ) - self:F3( { ScheduleID } ) - - _SCHEDULEDISPATCHER:Remove( self, ScheduleID ) -end - ---- Clears all pending schedules. --- @param #SCHEDULER self -function SCHEDULER:Clear() - self:F3( ) - - _SCHEDULEDISPATCHER:Clear( self ) -end - - - - - - - - - - - - - - ---- This module defines the SCHEDULEDISPATCHER class, which is used by a central object called _SCHEDULEDISPATCHER. --- --- === --- --- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. --- --- This class is tricky and needs some thorought explanation. --- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. --- The SCHEDULEDISPATCHER class ensures that: --- --- - Scheduled functions are planned according the SCHEDULER object parameters. --- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. --- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. --- --- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection: --- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER --- object is _persistent_ within memory. --- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection! --- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected. --- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, --- these will not be executed anymore when the SCHEDULER object has been destroyed. --- --- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. --- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. --- The SCHEDULER object plans new scheduled functions through the @{Scheduler#SCHEDULER.Schedule}() method. --- The Schedule() method returns the CallID that is the reference ID for each planned schedule. --- --- === --- --- === --- --- ### Contributions: - --- ### Authors: FlightControl : Design & Programming --- --- @module ScheduleDispatcher - ---- The SCHEDULEDISPATCHER structure --- @type SCHEDULEDISPATCHER -SCHEDULEDISPATCHER = { - ClassName = "SCHEDULEDISPATCHER", - CallID = 0, -} - -function SCHEDULEDISPATCHER:New() - local self = BASE:Inherit( self, BASE:New() ) - self:F3() - return self -end - ---- Add a Schedule to the ScheduleDispatcher. --- The development of this method was really tidy. --- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. --- Nothing of this code should be modified without testing it thoroughly. --- @param #SCHEDULEDISPATCHER self --- @param Core.Scheduler#SCHEDULER Scheduler -function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop ) - self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } ) - - self.CallID = self.CallID + 1 - - -- Initialize the ObjectSchedulers array, which is a weakly coupled table. - -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. - self.PersistentSchedulers = self.PersistentSchedulers or {} - - -- Initialize the ObjectSchedulers array, which is a weakly coupled table. - -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. - self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) -- or {} - - if Scheduler.MasterObject then - self.ObjectSchedulers[self.CallID] = Scheduler - self:F3( { CallID = self.CallID, ObjectScheduler = tostring(self.ObjectSchedulers[self.CallID]), MasterObject = tostring(Scheduler.MasterObject) } ) - else - self.PersistentSchedulers[self.CallID] = Scheduler - self:F3( { CallID = self.CallID, PersistentScheduler = self.PersistentSchedulers[self.CallID] } ) - end - - self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } ) - self.Schedule[Scheduler] = self.Schedule[Scheduler] or {} - self.Schedule[Scheduler][self.CallID] = {} - self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction - self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments - self.Schedule[Scheduler][self.CallID].StartTime = timer.getTime() + ( Start or 0 ) - self.Schedule[Scheduler][self.CallID].Start = Start + .1 - self.Schedule[Scheduler][self.CallID].Repeat = Repeat - self.Schedule[Scheduler][self.CallID].Randomize = Randomize - self.Schedule[Scheduler][self.CallID].Stop = Stop - - self:T3( self.Schedule[Scheduler][self.CallID] ) - - self.Schedule[Scheduler][self.CallID].CallHandler = function( CallID ) - self:F2( CallID ) - - local ErrorHandler = function( errmsg ) - env.info( "Error in timer function: " .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - return errmsg - end - - local Scheduler = self.ObjectSchedulers[CallID] - if not Scheduler then - Scheduler = self.PersistentSchedulers[CallID] - end - - self:T3( { Scheduler = Scheduler } ) - - if Scheduler then - - local Schedule = self.Schedule[Scheduler][CallID] - - self:T3( { Schedule = Schedule } ) - - local ScheduleObject = Scheduler.SchedulerObject - --local ScheduleObjectName = Scheduler.SchedulerObject:GetNameAndClassID() - local ScheduleFunction = Schedule.Function - local ScheduleArguments = Schedule.Arguments - local Start = Schedule.Start - local Repeat = Schedule.Repeat or 0 - local Randomize = Schedule.Randomize or 0 - local Stop = Schedule.Stop or 0 - local ScheduleID = Schedule.ScheduleID - - local Status, Result - if ScheduleObject then - local function Timer() - return ScheduleFunction( ScheduleObject, unpack( ScheduleArguments ) ) - end - Status, Result = xpcall( Timer, ErrorHandler ) - else - local function Timer() - return ScheduleFunction( unpack( ScheduleArguments ) ) - end - Status, Result = xpcall( Timer, ErrorHandler ) - end - - local CurrentTime = timer.getTime() - local StartTime = CurrentTime + Start - - if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then - if Repeat ~= 0 and ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) then - local ScheduleTime = - CurrentTime + - Repeat + - math.random( - - ( Randomize * Repeat / 2 ), - ( Randomize * Repeat / 2 ) - ) + - 0.01 - self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) - return ScheduleTime -- returns the next time the function needs to be called. - else - self:Stop( Scheduler, CallID ) - end - else - self:Stop( Scheduler, CallID ) - end - else - self:E( "Scheduled obscolete call for CallID: " .. CallID ) - end - - return nil - end - - self:Start( Scheduler, self.CallID ) - - return self.CallID -end - -function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID ) - self:F2( { Remove = CallID, Scheduler = Scheduler } ) - - if CallID then - self:Stop( Scheduler, CallID ) - self.Schedule[Scheduler][CallID] = nil - end -end - -function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) - self:F2( { Start = CallID, Scheduler = Scheduler } ) - - if CallID then - local Schedule = self.Schedule[Scheduler] - -- Only start when there is no ScheduleID defined! - -- This prevents to "Start" the scheduler twice with the same CallID... - if not Schedule[CallID].ScheduleID then - Schedule[CallID].ScheduleID = timer.scheduleFunction( - Schedule[CallID].CallHandler, - CallID, - timer.getTime() + Schedule[CallID].Start - ) - end - else - for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do - self:Start( Scheduler, CallID ) -- Recursive - end - end -end - -function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) - self:F2( { Stop = CallID, Scheduler = Scheduler } ) - - if CallID then - local Schedule = self.Schedule[Scheduler] - -- Only stop when there is a ScheduleID defined for the CallID. - -- So, when the scheduler was stopped before, do nothing. - if Schedule[CallID].ScheduleID then - timer.removeFunction( Schedule[CallID].ScheduleID ) - Schedule[CallID].ScheduleID = nil - end - else - for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do - self:Stop( Scheduler, CallID ) -- Recursive - end - end -end - -function SCHEDULEDISPATCHER:Clear( Scheduler ) - self:F2( { Scheduler = Scheduler } ) - - for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do - self:Stop( Scheduler, CallID ) -- Recursive - end -end - - - ---- **Core** - EVENT models DCS **event dispatching** using a **publish-subscribe** model. --- --- ![Banner Image](..\Presentations\EVENT\Dia1.JPG) --- --- === --- --- # 1) Event Handling Overview --- --- ![Objects](..\Presentations\EVENT\Dia2.JPG) --- --- Within a running mission, various DCS events occur. Units are dynamically created, crash, die, shoot stuff, get hit etc. --- This module provides a mechanism to dispatch those events occuring within your running mission, to the different objects orchestrating your mission. --- --- ![Objects](..\Presentations\EVENT\Dia3.JPG) --- --- Objects can subscribe to different events. The Event dispatcher will publish the received DCS events to the subscribed MOOSE objects, in a specified order. --- In this way, the subscribed MOOSE objects are kept in sync with your evolving running mission. --- --- ## 1.1) Event Dispatching --- --- ![Objects](..\Presentations\EVENT\Dia4.JPG) --- --- The _EVENTDISPATCHER object is automatically created within MOOSE, --- and handles the dispatching of DCS Events occurring --- in the simulator to the subscribed objects --- in the correct processing order. --- --- ![Objects](..\Presentations\EVENT\Dia5.JPG) --- --- There are 5 levels of kind of objects that the _EVENTDISPATCHER services: --- --- * _DATABASE object: The core of the MOOSE objects. Any object that is created, deleted or updated, is done in this database. --- * SET_ derived classes: Subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority. --- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to teh subscribed UNIT object. --- * GROUP objects: GROUP objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed GROUP object. --- * Any other object: Various other objects can subscribe to DCS events. Each DCS event triggered will be published to each subscribed object. --- --- ![Objects](..\Presentations\EVENT\Dia6.JPG) --- --- For most DCS events, the above order of updating will be followed. --- --- ![Objects](..\Presentations\EVENT\Dia7.JPG) --- --- But for some DCS events, the publishing order is reversed. This is due to the fact that objects need to be **erased** instead of added. --- --- ## 1.2) Event Handling --- --- ![Objects](..\Presentations\EVENT\Dia8.JPG) --- --- The actual event subscribing and handling is not facilitated through the _EVENTDISPATCHER, but it is done through the @{BASE} class, @{UNIT} class and @{GROUP} class. --- The _EVENTDISPATCHER is a component that is quietly working in the background of MOOSE. --- --- ![Objects](..\Presentations\EVENT\Dia9.JPG) --- --- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator, --- and handled through lua scripting. MOOSE provides an encapsulation to handle these events more efficiently. --- --- ### 1.2.1 Subscribe / Unsubscribe to DCS Events --- --- At first, the mission designer will need to **Subscribe** to a specific DCS event for the class. --- So, when the DCS event occurs, the class will be notified of that event. --- There are two functions which you use to subscribe to or unsubscribe from an event. --- --- * @{Base#BASE.HandleEvent}(): Subscribe to a DCS Event. --- * @{Base#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event. --- --- Note that for a UNIT, the event will be handled **for that UNIT only**! --- Note that for a GROUP, the event will be handled **for all the UNITs in that GROUP only**! --- --- For all objects of other classes, the subscribed events will be handled for **all UNITs within the Mission**! --- So if a UNIT within the mission has the subscribed event for that object, --- then the object event handler will receive the event for that UNIT! --- --- ### 1.3.2 Event Handling of DCS Events --- --- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called --- when the DCS event occurs. The Event Handling method receives an @{Event#EVENTDATA} structure, which contains a lot of information --- about the event that occurred. --- --- Find below an example of the prototype how to write an event handling function for two units: --- --- local Tank1 = UNIT:FindByName( "Tank A" ) --- local Tank2 = UNIT:FindByName( "Tank B" ) --- --- -- Here we subscribe to the Dead events. So, if one of these tanks dies, the Tank1 or Tank2 objects will be notified. --- Tank1:HandleEvent( EVENTS.Dead ) --- Tank2:HandleEvent( EVENTS.Dead ) --- --- --- This function is an Event Handling function that will be called when Tank1 is Dead. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank1:OnEventDead( EventData ) --- --- self:SmokeGreen() --- end --- --- --- This function is an Event Handling function that will be called when Tank2 is Dead. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank2:OnEventDead( EventData ) --- --- self:SmokeBlue() --- end --- --- ### 1.3.3 Event Handling methods that are automatically called upon subscribed DCS events --- --- ![Objects](..\Presentations\EVENT\Dia10.JPG) --- --- The following list outlines which EVENTS item in the structure corresponds to which Event Handling method. --- Always ensure that your event handling methods align with the events being subscribed to, or nothing will be executed. --- --- # 2) EVENTS type --- --- The EVENTS structure contains names for all the different DCS events that objects can subscribe to using the --- @{Base#BASE.HandleEvent}() method. --- --- # 3) EVENTDATA type --- --- The @{Event#EVENTDATA} structure contains all the fields that are populated with event information before --- an Event Handler method is being called by the event dispatcher. --- The Event Handler received the EVENTDATA object as a parameter, and can be used to investigate further the different events. --- There are basically 4 main categories of information stored in the EVENTDATA structure: --- --- * Initiator Unit data: Several fields documenting the initiator unit related to the event. --- * Target Unit data: Several fields documenting the target unit related to the event. --- * Weapon data: Certain events populate weapon information. --- * Place data: Certain events populate place information. --- --- --- This function is an Event Handling function that will be called when Tank1 is Dead. --- -- EventData is an EVENTDATA structure. --- -- We use the EventData.IniUnit to smoke the tank Green. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank1:OnEventDead( EventData ) --- --- EventData.IniUnit:SmokeGreen() --- end --- --- --- Find below an overview which events populate which information categories: --- --- ![Objects](..\Presentations\EVENT\Dia14.JPG) --- --- **IMPORTANT NOTE:** Some events can involve not just UNIT objects, but also STATIC objects!!! --- In that case the initiator or target unit fields will refer to a STATIC object! --- In case a STATIC object is involved, the documentation indicates which fields will and won't not be populated. --- The fields **IniObjectCategory** and **TgtObjectCategory** contain the indicator which **kind of object is involved** in the event. --- You can use the enumerator **Object.Category.UNIT** and **Object.Category.STATIC** to check on IniObjectCategory and TgtObjectCategory. --- Example code snippet: --- --- if Event.IniObjectCategory == Object.Category.UNIT then --- ... --- end --- if Event.IniObjectCategory == Object.Category.STATIC then --- ... --- end --- --- When a static object is involved in the event, the Group and Player fields won't be populated. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- YYYY-MM-DD: CLASS:**NewFunction**( Params ) replaces CLASS:_OldFunction_( Params ) --- YYYY-MM-DD: CLASS:**NewFunction( Params )** added --- --- Hereby the change log: --- --- * 2017-03-07: Added the correct event dispatching in case the event is subscribed by a GROUP. --- --- * 2017-02-07: Did a complete revision of the Event Handing API and underlying mechanisms. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- ### Authors: --- --- * [**FlightControl**](https://forums.eagle.ru/member.php?u=89536): Design & Programming & documentation. --- --- @module Event - - ---- The EVENT structure --- @type EVENT --- @field #EVENT.Events Events --- @extends Core.Base#BASE -EVENT = { - ClassName = "EVENT", - ClassID = 0, -} - ---- The different types of events supported by MOOSE. --- Use this structure to subscribe to events using the @{Base#BASE.HandleEvent}() method. --- @type EVENTS -EVENTS = { - Shot = world.event.S_EVENT_SHOT, - Hit = world.event.S_EVENT_HIT, - Takeoff = world.event.S_EVENT_TAKEOFF, - Land = world.event.S_EVENT_LAND, - Crash = world.event.S_EVENT_CRASH, - Ejection = world.event.S_EVENT_EJECTION, - Refueling = world.event.S_EVENT_REFUELING, - Dead = world.event.S_EVENT_DEAD, - PilotDead = world.event.S_EVENT_PILOT_DEAD, - BaseCaptured = world.event.S_EVENT_BASE_CAPTURED, - MissionStart = world.event.S_EVENT_MISSION_START, - MissionEnd = world.event.S_EVENT_MISSION_END, - TookControl = world.event.S_EVENT_TOOK_CONTROL, - RefuelingStop = world.event.S_EVENT_REFUELING_STOP, - Birth = world.event.S_EVENT_BIRTH, - HumanFailure = world.event.S_EVENT_HUMAN_FAILURE, - EngineStartup = world.event.S_EVENT_ENGINE_STARTUP, - EngineShutdown = world.event.S_EVENT_ENGINE_SHUTDOWN, - PlayerEnterUnit = world.event.S_EVENT_PLAYER_ENTER_UNIT, - PlayerLeaveUnit = world.event.S_EVENT_PLAYER_LEAVE_UNIT, - PlayerComment = world.event.S_EVENT_PLAYER_COMMENT, - ShootingStart = world.event.S_EVENT_SHOOTING_START, - ShootingEnd = world.event.S_EVENT_SHOOTING_END, -} - ---- The Event structure --- Note that at the beginning of each field description, there is an indication which field will be populated depending on the object type involved in the Event: --- --- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event. --- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ --- --- @type EVENTDATA --- @field #number id The identifier of the event. --- --- @field Dcs.DCSUnit#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{Dcs.DCSUnit#Unit} or @{Dcs.DCSStaticObject#StaticObject}. --- @field Dcs.DCSObject#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ). --- @field Dcs.DCSUnit#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. --- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name. --- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Unit#UNIT} of the initiator Unit object. --- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName). --- @field Dcs.DCSGroup#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}. --- @field #string IniDCSGroupName (UNIT) The initiating Group name. --- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Group#GROUP} of the initiator Group object. --- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName). --- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot. --- @field Dcs.DCScoalition#coalition.side IniCoalition (UNIT) The coalition of the initiator. --- @field Dcs.DCSUnit#Unit.Category IniCategory (UNIT) The category of the initiator. --- @field #string IniTypeName (UNIT) The type name of the initiator. --- --- @field Dcs.DCSUnit#Unit target (UNIT/STATIC) The target @{Dcs.DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. --- @field Dcs.DCSObject#Object.Category TgtObjectCategory (UNIT/STATIC) The target object category ( Object.Category.UNIT or Object.Category.STATIC ). --- @field Dcs.DCSUnit#Unit TgtDCSUnit (UNIT/STATIC) The target @{DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. --- @field #string TgtDCSUnitName (UNIT/STATIC) The target Unit name. --- @field Wrapper.Unit#UNIT TgtUnit (UNIT/STATIC) The target MOOSE wrapper @{Unit#UNIT} of the target Unit object. --- @field #string TgtUnitName (UNIT/STATIC) The target UNIT name (same as TgtDCSUnitName). --- @field Dcs.DCSGroup#Group TgtDCSGroup (UNIT) The target {DCSGroup#Group}. --- @field #string TgtDCSGroupName (UNIT) The target Group name. --- @field Wrapper.Group#GROUP TgtGroup (UNIT) The target MOOSE wrapper @{Group#GROUP} of the target Group object. --- @field #string TgtGroupName (UNIT) The target GROUP name (same as TgtDCSGroupName). --- @field #string TgtPlayerName (UNIT) The name of the target player in case the Unit is a client or player slot. --- @field Dcs.DCScoalition#coalition.side TgtCoalition (UNIT) The coalition of the target. --- @field Dcs.DCSUnit#Unit.Category TgtCategory (UNIT) The category of the target. --- @field #string TgtTypeName (UNIT) The type name of the target. --- --- @field weapon The weapon used during the event. --- @field Weapon --- @field WeaponName --- @field WeaponTgtDCSUnit - - -local _EVENTMETA = { - [world.event.S_EVENT_SHOT] = { - Order = 1, - Event = "OnEventShot", - Text = "S_EVENT_SHOT" - }, - [world.event.S_EVENT_HIT] = { - Order = 1, - Event = "OnEventHit", - Text = "S_EVENT_HIT" - }, - [world.event.S_EVENT_TAKEOFF] = { - Order = 1, - Event = "OnEventTakeoff", - Text = "S_EVENT_TAKEOFF" - }, - [world.event.S_EVENT_LAND] = { - Order = 1, - Event = "OnEventLand", - Text = "S_EVENT_LAND" - }, - [world.event.S_EVENT_CRASH] = { - Order = -1, - Event = "OnEventCrash", - Text = "S_EVENT_CRASH" - }, - [world.event.S_EVENT_EJECTION] = { - Order = 1, - Event = "OnEventEjection", - Text = "S_EVENT_EJECTION" - }, - [world.event.S_EVENT_REFUELING] = { - Order = 1, - Event = "OnEventRefueling", - Text = "S_EVENT_REFUELING" - }, - [world.event.S_EVENT_DEAD] = { - Order = -1, - Event = "OnEventDead", - Text = "S_EVENT_DEAD" - }, - [world.event.S_EVENT_PILOT_DEAD] = { - Order = 1, - Event = "OnEventPilotDead", - Text = "S_EVENT_PILOT_DEAD" - }, - [world.event.S_EVENT_BASE_CAPTURED] = { - Order = 1, - Event = "OnEventBaseCaptured", - Text = "S_EVENT_BASE_CAPTURED" - }, - [world.event.S_EVENT_MISSION_START] = { - Order = 1, - Event = "OnEventMissionStart", - Text = "S_EVENT_MISSION_START" - }, - [world.event.S_EVENT_MISSION_END] = { - Order = 1, - Event = "OnEventMissionEnd", - Text = "S_EVENT_MISSION_END" - }, - [world.event.S_EVENT_TOOK_CONTROL] = { - Order = 1, - Event = "OnEventTookControl", - Text = "S_EVENT_TOOK_CONTROL" - }, - [world.event.S_EVENT_REFUELING_STOP] = { - Order = 1, - Event = "OnEventRefuelingStop", - Text = "S_EVENT_REFUELING_STOP" - }, - [world.event.S_EVENT_BIRTH] = { - Order = 1, - Event = "OnEventBirth", - Text = "S_EVENT_BIRTH" - }, - [world.event.S_EVENT_HUMAN_FAILURE] = { - Order = 1, - Event = "OnEventHumanFailure", - Text = "S_EVENT_HUMAN_FAILURE" - }, - [world.event.S_EVENT_ENGINE_STARTUP] = { - Order = 1, - Event = "OnEventEngineStartup", - Text = "S_EVENT_ENGINE_STARTUP" - }, - [world.event.S_EVENT_ENGINE_SHUTDOWN] = { - Order = 1, - Event = "OnEventEngineShutdown", - Text = "S_EVENT_ENGINE_SHUTDOWN" - }, - [world.event.S_EVENT_PLAYER_ENTER_UNIT] = { - Order = 1, - Event = "OnEventPlayerEnterUnit", - Text = "S_EVENT_PLAYER_ENTER_UNIT" - }, - [world.event.S_EVENT_PLAYER_LEAVE_UNIT] = { - Order = -1, - Event = "OnEventPlayerLeaveUnit", - Text = "S_EVENT_PLAYER_LEAVE_UNIT" - }, - [world.event.S_EVENT_PLAYER_COMMENT] = { - Order = 1, - Event = "OnEventPlayerComment", - Text = "S_EVENT_PLAYER_COMMENT" - }, - [world.event.S_EVENT_SHOOTING_START] = { - Order = 1, - Event = "OnEventShootingStart", - Text = "S_EVENT_SHOOTING_START" - }, - [world.event.S_EVENT_SHOOTING_END] = { - Order = 1, - Event = "OnEventShootingEnd", - Text = "S_EVENT_SHOOTING_END" - }, -} - - ---- The Events structure --- @type EVENT.Events --- @field #number IniUnit - -function EVENT:New() - local self = BASE:Inherit( self, BASE:New() ) - self:F2() - self.EventHandler = world.addEventHandler( self ) - return self -end - -function EVENT:EventText( EventID ) - - local EventText = _EVENTMETA[EventID].Text - - return EventText -end - - ---- Initializes the Events structure for the event --- @param #EVENT self --- @param Dcs.DCSWorld#world.event EventID --- @param Core.Base#BASE EventClass --- @return #EVENT.Events -function EVENT:Init( EventID, EventClass ) - self:F3( { _EVENTMETA[EventID].Text, EventClass } ) - - if not self.Events[EventID] then - -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. - self.Events[EventID] = setmetatable( {}, { __mode = "k" } ) - end - - -- Each event has a subtable of EventClasses, ordered by EventPriority. - local EventPriority = EventClass:GetEventPriority() - if not self.Events[EventID][EventPriority] then - self.Events[EventID][EventPriority] = setmetatable( {}, { __mode = "kv" } ) - end - - if not self.Events[EventID][EventPriority][EventClass] then - self.Events[EventID][EventPriority][EventClass] = {} - end - return self.Events[EventID][EventPriority][EventClass] -end - ---- Removes an Events entry --- @param #EVENT self --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param Dcs.DCSWorld#world.event EventID --- @return #EVENT.Events -function EVENT:Remove( EventClass, EventID ) - self:F3( { EventClass, _EVENTMETA[EventID].Text } ) - - local EventClass = EventClass - local EventPriority = EventClass:GetEventPriority() - self.Events[EventID][EventPriority][EventClass] = nil -end - ---- Removes an Events entry for a UNIT. --- @param #EVENT self --- @param #string UnitName The name of the UNIT. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param Dcs.DCSWorld#world.event EventID --- @return #EVENT.Events -function EVENT:RemoveForUnit( UnitName, EventClass, EventID ) - self:F3( { EventClass, _EVENTMETA[EventID].Text } ) - - local EventClass = EventClass - local EventPriority = EventClass:GetEventPriority() - local Event = self.Events[EventID][EventPriority][EventClass] - Event.EventUnit[UnitName] = nil -end - ---- Removes an Events entry for a GROUP. --- @param #EVENT self --- @param #string GroupName The name of the GROUP. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param Dcs.DCSWorld#world.event EventID --- @return #EVENT.Events -function EVENT:RemoveForGroup( GroupName, EventClass, EventID ) - self:F3( { EventClass, _EVENTMETA[EventID].Text } ) - - local EventClass = EventClass - local EventPriority = EventClass:GetEventPriority() - local Event = self.Events[EventID][EventPriority][EventClass] - Event.EventGroup[GroupName] = nil -end - ---- Clears all event subscriptions for a @{Base#BASE} derived object. --- @param #EVENT self --- @param Core.Base#BASE EventObject -function EVENT:RemoveAll( EventObject ) - self:F3( { EventObject:GetClassNameAndID() } ) - - local EventClass = EventObject:GetClassNameAndID() - local EventPriority = EventClass:GetEventPriority() - for EventID, EventData in pairs( self.Events ) do - self.Events[EventID][EventPriority][EventClass] = nil - end -end - - - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventClass The instance of the class for which the event is. --- @param #function OnEventFunction --- @return #EVENT -function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EventID ) - self:F2( EventTemplate.name ) - - for EventUnitID, EventUnit in pairs( EventTemplate.units ) do - self:OnEventForUnit( EventUnit.name, EventFunction, EventClass, EventID ) - end - return self -end - ---- Set a new listener for an S_EVENT_X event independent from a unit or a weapon. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is captured. When the event happens, the event process will be called in this class provided. --- @param EventID --- @return #EVENT -function EVENT:OnEventGeneric( EventFunction, EventClass, EventID ) - self:F2( { EventID } ) - - local EventData = self:Init( EventID, EventClass ) - EventData.EventFunction = EventFunction - EventData.EventClass = EventClass - - return self -end - - ---- Set a new listener for an S_EVENT_X event for a UNIT. --- @param #EVENT self --- @param #string UnitName The name of the UNIT. --- @param #function EventFunction The function to be called when the event occurs for the GROUP. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param EventID --- @return #EVENT -function EVENT:OnEventForUnit( UnitName, EventFunction, EventClass, EventID ) - self:F2( UnitName ) - - local EventData = self:Init( EventID, EventClass ) - if not EventData.EventUnit then - EventData.EventUnit = {} - end - EventData.EventUnit[UnitName] = {} - EventData.EventUnit[UnitName].EventFunction = EventFunction - EventData.EventUnit[UnitName].EventClass = EventClass - return self -end - ---- Set a new listener for an S_EVENT_X event for a GROUP. --- @param #EVENT self --- @param #string GroupName The name of the GROUP. --- @param #function EventFunction The function to be called when the event occurs for the GROUP. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param EventID --- @return #EVENT -function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID ) - self:F2( GroupName ) - - local Event = self:Init( EventID, EventClass ) - if not Event.EventGroup then - Event.EventGroup = {} - end - Event.EventGroup[GroupName] = {} - Event.EventGroup[GroupName].EventFunction = EventFunction - Event.EventGroup[GroupName].EventClass = EventClass - return self -end - -do -- OnBirth - - --- Create an OnBirth event handler for a group - -- @param #EVENT self - -- @param Wrapper.Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Birth ) - - return self - end - -end - -do -- OnCrash - - --- Create an OnCrash event handler for a group - -- @param #EVENT self - -- @param Wrapper.Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Crash ) - - return self - end - -end - -do -- OnDead - - --- Create an OnDead event handler for a group - -- @param #EVENT self - -- @param Wrapper.Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Dead ) - - return self - end - -end - - -do -- OnLand - --- Create an OnLand event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Land ) - - return self - end - -end - -do -- OnTakeOff - --- Create an OnTakeOff event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Takeoff ) - - return self - end - -end - -do -- OnEngineShutDown - - --- Create an OnDead event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.EngineShutdown ) - - return self - end - -end - - ---- @param #EVENT self --- @param #EVENTDATA Event -function EVENT:onEvent( Event ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - - if self and self.Events and self.Events[Event.id] then - - - if Event.initiator then - - Event.IniObjectCategory = Event.initiator:getCategory() - - if Event.IniObjectCategory == Object.Category.UNIT then - Event.IniDCSUnit = Event.initiator - Event.IniDCSUnitName = Event.IniDCSUnit:getName() - Event.IniUnitName = Event.IniDCSUnitName - Event.IniDCSGroup = Event.IniDCSUnit:getGroup() - Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName ) - if not Event.IniUnit then - -- Unit can be a CLIENT. Most likely this will be the case ... - Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true ) - end - Event.IniDCSGroupName = "" - if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then - Event.IniDCSGroupName = Event.IniDCSGroup:getName() - Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) - if Event.IniGroup then - Event.IniGroupName = Event.IniDCSGroupName - end - end - Event.IniPlayerName = Event.IniDCSUnit:getPlayerName() - Event.IniCoalition = Event.IniDCSUnit:getCoalition() - Event.IniTypeName = Event.IniDCSUnit:getTypeName() - Event.IniCategory = Event.IniDCSUnit:getDesc().category - end - - if Event.IniObjectCategory == Object.Category.STATIC then - Event.IniDCSUnit = Event.initiator - Event.IniDCSUnitName = Event.IniDCSUnit:getName() - Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false ) - Event.IniCoalition = Event.IniDCSUnit:getCoalition() - Event.IniCategory = Event.IniDCSUnit:getDesc().category - Event.IniTypeName = Event.IniDCSUnit:getTypeName() - end - - if Event.IniObjectCategory == Object.Category.SCENERY then - Event.IniDCSUnit = Event.initiator - Event.IniDCSUnitName = Event.IniDCSUnit:getName() - Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator ) - Event.IniCategory = Event.IniDCSUnit:getDesc().category - Event.IniTypeName = Event.IniDCSUnit:getTypeName() - end - end - - if Event.target then - - Event.TgtObjectCategory = Event.target:getCategory() - - if Event.TgtObjectCategory == Object.Category.UNIT then - Event.TgtDCSUnit = Event.target - Event.TgtDCSGroup = Event.TgtDCSUnit:getGroup() - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = UNIT:FindByName( Event.TgtDCSUnitName ) - Event.TgtDCSGroupName = "" - if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then - Event.TgtDCSGroupName = Event.TgtDCSGroup:getName() - Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) - if Event.TgtGroup then - Event.TgtGroupName = Event.TgtDCSGroupName - end - end - Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName() - Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - end - - if Event.TgtObjectCategory == Object.Category.STATIC then - Event.TgtDCSUnit = Event.target - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName ) - Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - end - - if Event.TgtObjectCategory == Object.Category.SCENERY then - Event.TgtDCSUnit = Event.target - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - end - end - - if Event.weapon then - Event.Weapon = Event.weapon - Event.WeaponName = Event.Weapon:getTypeName() - --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() - end - - local PriorityOrder = _EVENTMETA[Event.id].Order - local PriorityBegin = PriorityOrder == -1 and 5 or 1 - local PriorityEnd = PriorityOrder == -1 and 1 or 5 - - if Event.IniObjectCategory ~= 3 then - self:E( { _EVENTMETA[Event.id].Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) - end - - for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do - - if self.Events[Event.id][EventPriority] then - - -- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called. - for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do - - Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) - Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) - - -- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT. - if ( Event.IniDCSUnitName and EventData.EventUnit and EventData.EventUnit[Event.IniDCSUnitName] ) or - ( Event.TgtDCSUnitName and EventData.EventUnit and EventData.EventUnit[Event.TgtDCSUnitName] ) then - - if EventData.EventUnit[Event.IniDCSUnitName] then - - -- First test if a EventFunction is Set, otherwise search for the default function - if EventData.EventUnit[Event.IniDCSUnitName].EventFunction then - - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventData.EventUnit[Event.IniDCSUnitName].EventFunction( EventClass, Event ) - end, ErrorHandler ) - - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - - if EventData.EventUnit[Event.TgtDCSUnitName] then - - -- First test if a EventFunction is Set, otherwise search for the default function - if EventData.EventUnit[Event.TgtDCSUnitName].EventFunction then - - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventData.EventUnit[Event.TgtDCSUnitName].EventFunction( EventClass, Event ) - end, ErrorHandler ) - - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - - else - - -- If the EventData is for a GROUP, the call directly the EventClass EventFunction for the UNIT in that GROUP. - if ( Event.IniDCSUnitName and Event.IniDCSGroupName and Event.IniGroupName and EventData.EventGroup and EventData.EventGroup[Event.IniGroupName] ) or - ( Event.TgtDCSUnitName and Event.TgtDCSGroupName and Event.TgtGroupName and EventData.EventGroup and EventData.EventGroup[Event.TgtGroupName] ) then - - if EventData.EventGroup[Event.IniGroupName] then - -- First test if a EventFunction is Set, otherwise search for the default function - if EventData.EventGroup[Event.IniGroupName].EventFunction then - - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventData.EventGroup[Event.IniGroupName].EventFunction( EventClass, Event ) - end, ErrorHandler ) - - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - - if EventData.EventGroup[Event.TgtGroupName] then - if EventData.EventGroup[Event.TgtGroupName].EventFunction then - - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventData.EventGroup[Event.TgtGroupName].EventFunction( EventClass, Event ) - end, ErrorHandler ) - - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - - else - - -- If the EventData is not bound to a specific unit, then call the EventClass EventFunction. - -- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon. - if Event.IniDCSUnit and not EventData.EventUnit then - - if EventClass == EventData.EventClass then - - -- First test if a EventFunction is Set, otherwise search for the default function - if EventData.EventFunction then - - -- There is an EventFunction defined, so call the EventFunction. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - end - local Result, Value = xpcall( - function() - return EventData.EventFunction( EventClass, Event ) - end, ErrorHandler ) - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - end - end - end - end - end - end - else - self:E( { _EVENTMETA[Event.id].Text, Event } ) - end -end - ---- The EVENTHANDLER structure --- @type EVENTHANDLER --- @extends Core.Base#BASE -EVENTHANDLER = { - ClassName = "EVENTHANDLER", - ClassID = 0, -} - ---- The EVENTHANDLER constructor --- @param #EVENTHANDLER self --- @return #EVENTHANDLER -function EVENTHANDLER:New() - self = BASE:Inherit( self, BASE:New() ) -- #EVENTHANDLER - return self -end ---- **Core** -- MENU_ classes model the definition of **hierarchical menu structures** and **commands for players** within a mission. --- --- === --- --- DCS Menus can be managed using the MENU classes. --- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to --- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing --- menus is not a easy feat if you have complex menu hierarchies defined. --- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. --- On top, MOOSE implements **variable parameter** passing for command menus. --- --- There are basically two different MENU class types that you need to use: --- --- ### To manage **main menus**, the classes begin with **MENU_**: --- --- * @{Menu#MENU_MISSION}: Manages main menus for whole mission file. --- * @{Menu#MENU_COALITION}: Manages main menus for whole coalition. --- * @{Menu#MENU_GROUP}: Manages main menus for GROUPs. --- * @{Menu#MENU_CLIENT}: Manages main menus for CLIENTs. This manages menus for units with the skill level "Client". --- --- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: --- --- * @{Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. --- * @{Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. --- * @{Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- * @{Menu#MENU_CLIENT_COMMAND}: Manages command menus for CLIENTs. This manages menus for units with the skill level "Client". --- --- === --- --- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these): --- --- 1) MENU_ BASE abstract base classes (don't use them) --- ==================================================== --- The underlying base menu classes are **NOT** to be used within your missions. --- These are simply abstract base classes defining a couple of fields that are used by the --- derived MENU_ classes to manage menus. --- --- 1.1) @{#MENU_BASE} class, extends @{Base#BASE} --- -------------------------------------------------- --- The @{#MENU_BASE} class defines the main MENU class where other MENU classes are derived from. --- --- 1.2) @{#MENU_COMMAND_BASE} class, extends @{Base#BASE} --- ---------------------------------------------------------- --- The @{#MENU_COMMAND_BASE} class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands. --- --- === --- --- **The next menus define the MENU classes that you can use within your missions.** --- --- 2) MENU MISSION classes --- ====================== --- The underlying classes manage the menus for a complete mission file. --- --- 2.1) @{#MENU_MISSION} class, extends @{Menu#MENU_BASE} --- --------------------------------------------------------- --- The @{Menu#MENU_MISSION} class manages the main menus for a complete mission. --- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. --- --- 2.2) @{#MENU_MISSION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} --- ------------------------------------------------------------------------- --- The @{Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution. --- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. --- --- === --- --- 3) MENU COALITION classes --- ========================= --- The underlying classes manage the menus for whole coalitions. --- --- 3.1) @{#MENU_COALITION} class, extends @{Menu#MENU_BASE} --- ------------------------------------------------------------ --- The @{Menu#MENU_COALITION} class manages the main menus for coalitions. --- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. --- --- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} --- ---------------------------------------------------------------------------- --- The @{Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. --- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. --- --- === --- --- 4) MENU GROUP classes --- ===================== --- The underlying classes manage the menus for groups. Note that groups can be inactive, alive or can be destroyed. --- --- 4.1) @{Menu#MENU_GROUP} class, extends @{Menu#MENU_BASE} --- -------------------------------------------------------- --- The @{Menu#MENU_GROUP} class manages the main menus for coalitions. --- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. --- --- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} --- ------------------------------------------------------------------------ --- The @{Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. --- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. --- --- === --- --- 5) MENU CLIENT classes --- ====================== --- The underlying classes manage the menus for units with skill level client or player. --- --- 5.1) @{Menu#MENU_CLIENT} class, extends @{Menu#MENU_BASE} --- --------------------------------------------------------- --- The @{Menu#MENU_CLIENT} class manages the main menus for coalitions. --- You can add menus with the @{#MENU_CLIENT.New} method, which constructs a MENU_CLIENT object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT.Remove}. --- --- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} --- ------------------------------------------------------------------------- --- The @{Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. --- You can add menus with the @{#MENU_CLIENT_COMMAND.New} method, which constructs a MENU_CLIENT_COMMAND object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT_COMMAND.Remove}. --- --- === --- --- ### Contributions: - --- ### Authors: FlightControl : Design & Programming --- --- @module Menu - - -do -- MENU_BASE - - --- The MENU_BASE class - -- @type MENU_BASE - -- @extends Base#BASE - MENU_BASE = { - ClassName = "MENU_BASE", - MenuPath = nil, - MenuText = "", - MenuParentPath = nil - } - - --- Consructor - -- @param #MENU_BASE - -- @return #MENU_BASE - function MENU_BASE:New( MenuText, ParentMenu ) - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, BASE:New() ) - - self.MenuPath = nil - self.MenuText = MenuText - self.MenuParentPath = MenuParentPath - self.Menus = {} - self.MenuCount = 0 - self.MenuRemoveParent = false - self.MenuTime = timer.getTime() - - return self - end - - --- Gets a @{Menu} from a parent @{Menu} - -- @param #MENU_BASE self - -- @param #string MenuText The text of the child menu. - -- @return #MENU_BASE - function MENU_BASE:GetMenu( MenuText ) - self:F( { self.Menus, MenuText } ) - return self.Menus[MenuText] - end - - --- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}. - -- @param #MENU_BASE self - -- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}. - -- @return #MENU_BASE - function MENU_BASE:SetRemoveParent( RemoveParent ) - self:F( { RemoveParent } ) - self.MenuRemoveParent = RemoveParent - return self - end - - - --- Sets a time stamp for later prevention of menu removal. - -- @param #MENU_BASE self - -- @param MenuTime - -- @return #MENU_BASE - function MENU_BASE:SetTime( MenuTime ) - self.MenuTime = MenuTime - return self - end - -end - -do -- MENU_COMMAND_BASE - - --- The MENU_COMMAND_BASE class - -- @type MENU_COMMAND_BASE - -- @field #function MenuCallHandler - -- @extends Core.Menu#MENU_BASE - MENU_COMMAND_BASE = { - ClassName = "MENU_COMMAND_BASE", - CommandMenuFunction = nil, - CommandMenuArgument = nil, - MenuCallHandler = nil, - } - - --- Constructor - -- @param #MENU_COMMAND_BASE - -- @return #MENU_COMMAND_BASE - function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self.CommandMenuFunction = CommandMenuFunction - self.MenuCallHandler = function( CommandMenuArguments ) - self.CommandMenuFunction( unpack( CommandMenuArguments ) ) - end - - return self - end - -end - - -do -- MENU_MISSION - - --- The MENU_MISSION class - -- @type MENU_MISSION - -- @extends Core.Menu#MENU_BASE - MENU_MISSION = { - ClassName = "MENU_MISSION" - } - - --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. - -- @param #MENU_MISSION self - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). - -- @return #MENU_MISSION - function MENU_MISSION:New( MenuText, ParentMenu ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self:F( { MenuText, ParentMenu } ) - - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - self:T( { MenuText } ) - - self.MenuPath = missionCommands.addSubMenu( MenuText, self.MenuParentPath ) - - self:T( { self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - - return self - end - - --- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept! - -- @param #MENU_MISSION self - -- @return #MENU_MISSION - function MENU_MISSION:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the main menu and the sub menus recursively of this MENU_MISSION. - -- @param #MENU_MISSION self - -- @return #nil - function MENU_MISSION:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - missionCommands.removeItem( self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - - return nil - end - -end - -do -- MENU_MISSION_COMMAND - - --- The MENU_MISSION_COMMAND class - -- @type MENU_MISSION_COMMAND - -- @extends Core.Menu#MENU_COMMAND_BASE - MENU_MISSION_COMMAND = { - ClassName = "MENU_MISSION_COMMAND" - } - - --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. - -- @param #MENU_MISSION_COMMAND self - -- @param #string MenuText The text for the menu. - -- @param Menu#MENU_MISSION ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. - -- @return #MENU_MISSION_COMMAND self - function MENU_MISSION_COMMAND:New( MenuText, ParentMenu, CommandMenuFunction, ... ) - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { MenuText, CommandMenuFunction, arg } ) - - - self.MenuPath = missionCommands.addCommand( MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - --- Removes a radio command item for a coalition - -- @param #MENU_MISSION_COMMAND self - -- @return #nil - function MENU_MISSION_COMMAND:Remove() - self:F( self.MenuPath ) - - missionCommands.removeItem( self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - return nil - end - -end - - - -do -- MENU_COALITION - - --- The MENU_COALITION class - -- @type MENU_COALITION - -- @extends Core.Menu#MENU_BASE - -- @usage - -- -- This demo creates a menu structure for the planes within the red coalition. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- - -- local Plane1 = CLIENT:FindByName( "Plane 1" ) - -- local Plane2 = CLIENT:FindByName( "Plane 2" ) - -- - -- - -- -- This would create a menu for the red coalition under the main DCS "Others" menu. - -- local MenuCoalitionRed = MENU_COALITION:New( coalition.side.RED, "Manage Menus" ) - -- - -- - -- local function ShowStatus( StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- Plane1:Message( StatusText, 15 ) - -- Plane2:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus -- Menu#MENU_COALITION - -- local MenuStatusShow -- Menu#MENU_COALITION_COMMAND - -- - -- local function RemoveStatusMenu() - -- MenuStatus:Remove() - -- end - -- - -- local function AddStatusMenu() - -- - -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. - -- MenuStatus = MENU_COALITION:New( coalition.side.RED, "Status for Planes" ) - -- MenuStatusShow = MENU_COALITION_COMMAND:New( coalition.side.RED, "Show Status", MenuStatus, ShowStatus, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- local MenuAdd = MENU_COALITION_COMMAND:New( coalition.side.RED, "Add Status Menu", MenuCoalitionRed, AddStatusMenu ) - -- local MenuRemove = MENU_COALITION_COMMAND:New( coalition.side.RED, "Remove Status Menu", MenuCoalitionRed, RemoveStatusMenu ) - MENU_COALITION = { - ClassName = "MENU_COALITION" - } - - --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. - -- @param #MENU_COALITION self - -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). - -- @return #MENU_COALITION self - function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self:F( { Coalition, MenuText, ParentMenu } ) - - self.Coalition = Coalition - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - self:T( { MenuText } ) - - self.MenuPath = missionCommands.addSubMenuForCoalition( Coalition, MenuText, self.MenuParentPath ) - - self:T( { self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - - return self - end - - --- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept! - -- @param #MENU_COALITION self - -- @return #MENU_COALITION - function MENU_COALITION:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the main menu and the sub menus recursively of this MENU_COALITION. - -- @param #MENU_COALITION self - -- @return #nil - function MENU_COALITION:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - - return nil - end - -end - -do -- MENU_COALITION_COMMAND - - --- The MENU_COALITION_COMMAND class - -- @type MENU_COALITION_COMMAND - -- @extends Core.Menu#MENU_COMMAND_BASE - MENU_COALITION_COMMAND = { - ClassName = "MENU_COALITION_COMMAND" - } - - --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. - -- @param #MENU_COALITION_COMMAND self - -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. - -- @param #string MenuText The text for the menu. - -- @param Menu#MENU_COALITION ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. - -- @return #MENU_COALITION_COMMAND - function MENU_COALITION_COMMAND:New( Coalition, MenuText, ParentMenu, CommandMenuFunction, ... ) - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuCoalition = Coalition - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { MenuText, CommandMenuFunction, arg } ) - - - self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - --- Removes a radio command item for a coalition - -- @param #MENU_COALITION_COMMAND self - -- @return #nil - function MENU_COALITION_COMMAND:Remove() - self:F( self.MenuPath ) - - missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - return nil - end - -end - -do -- MENU_CLIENT - - -- This local variable is used to cache the menus registered under clients. - -- Menus don't dissapear when clients are destroyed and restarted. - -- So every menu for a client created must be tracked so that program logic accidentally does not create - -- the same menus twice during initialization logic. - -- These menu classes are handling this logic with this variable. - local _MENUCLIENTS = {} - - --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. - -- @type MENU_CLIENT - -- @extends Core.Menu#MENU_BASE - -- @usage - -- -- This demo creates a menu structure for the two clients of planes. - -- -- Each client will receive a different menu structure. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- -- And play with the Add and Remove menu options. - -- - -- -- Note that in multi player, this will only work after the DCS clients bug is solved. - -- - -- local function ShowStatus( PlaneClient, StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- PlaneClient:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus = {} - -- - -- local function RemoveStatusMenu( MenuClient ) - -- local MenuClientName = MenuClient:GetName() - -- MenuStatus[MenuClientName]:Remove() - -- end - -- - -- --- @param Wrapper.Client#CLIENT MenuClient - -- local function AddStatusMenu( MenuClient ) - -- local MenuClientName = MenuClient:GetName() - -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. - -- MenuStatus[MenuClientName] = MENU_CLIENT:New( MenuClient, "Status for Planes" ) - -- MENU_CLIENT_COMMAND:New( MenuClient, "Show Status", MenuStatus[MenuClientName], ShowStatus, MenuClient, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneClient = CLIENT:FindByName( "Plane 1" ) - -- if PlaneClient and PlaneClient:IsAlive() then - -- local MenuManage = MENU_CLIENT:New( PlaneClient, "Manage Menus" ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Add Status Menu Plane 1", MenuManage, AddStatusMenu, PlaneClient ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 1", MenuManage, RemoveStatusMenu, PlaneClient ) - -- end - -- end, {}, 10, 10 ) - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneClient = CLIENT:FindByName( "Plane 2" ) - -- if PlaneClient and PlaneClient:IsAlive() then - -- local MenuManage = MENU_CLIENT:New( PlaneClient, "Manage Menus" ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Add Status Menu Plane 2", MenuManage, AddStatusMenu, PlaneClient ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneClient ) - -- end - -- end, {}, 10, 10 ) - MENU_CLIENT = { - ClassName = "MENU_CLIENT" - } - - --- MENU_CLIENT constructor. Creates a new radio menu item for a client. - -- @param #MENU_CLIENT self - -- @param Wrapper.Client#CLIENT Client The Client owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. - -- @return #MENU_CLIENT self - function MENU_CLIENT:New( Client, MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, MenuParentPath ) ) - self:F( { Client, MenuText, ParentMenu } ) - - self.MenuClient = Client - self.MenuClientGroupID = Client:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { Client:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath - - self:T( { Client:GetClientGroupName(), self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - return self - end - - --- Removes the sub menus recursively of this @{#MENU_CLIENT}. - -- @param #MENU_CLIENT self - -- @return #MENU_CLIENT self - function MENU_CLIENT:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the sub menus recursively of this MENU_CLIENT. - -- @param #MENU_CLIENT self - -- @return #nil - function MENU_CLIENT:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil - end - - - --- The MENU_CLIENT_COMMAND class - -- @type MENU_CLIENT_COMMAND - -- @extends Core.Menu#MENU_COMMAND - MENU_CLIENT_COMMAND = { - ClassName = "MENU_CLIENT_COMMAND" - } - - --- MENU_CLIENT_COMMAND constructor. Creates a new radio command item for a client, which can invoke a function with parameters. - -- @param #MENU_CLIENT_COMMAND self - -- @param Wrapper.Client#CLIENT Client The Client owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #MENU_BASE ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @return Menu#MENU_CLIENT_COMMAND self - function MENU_CLIENT_COMMAND:New( Client, MenuText, ParentMenu, CommandMenuFunction, ... ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, MenuParentPath, CommandMenuFunction, arg ) ) -- Menu#MENU_CLIENT_COMMAND - - self.MenuClient = Client - self.MenuClientGroupID = Client:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { Client:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, arg } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, self.MenuCallHandler, arg ) - MenuPath[MenuPathID] = self.MenuPath - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - - return self - end - - --- Removes a menu structure for a client. - -- @param #MENU_CLIENT_COMMAND self - -- @return #nil - function MENU_CLIENT_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil - end -end - ---- MENU_GROUP - -do - -- This local variable is used to cache the menus registered under groups. - -- Menus don't dissapear when groups for players are destroyed and restarted. - -- So every menu for a client created must be tracked so that program logic accidentally does not create. - -- the same menus twice during initialization logic. - -- These menu classes are handling this logic with this variable. - local _MENUGROUPS = {} - - --- The MENU_GROUP class - -- @type MENU_GROUP - -- @extends Core.Menu#MENU_BASE - -- @usage - -- -- This demo creates a menu structure for the two groups of planes. - -- -- Each group will receive a different menu structure. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- -- And play with the Add and Remove menu options. - -- - -- -- Note that in multi player, this will only work after the DCS groups bug is solved. - -- - -- local function ShowStatus( PlaneGroup, StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- PlaneGroup:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus = {} - -- - -- local function RemoveStatusMenu( MenuGroup ) - -- local MenuGroupName = MenuGroup:GetName() - -- MenuStatus[MenuGroupName]:Remove() - -- end - -- - -- --- @param Wrapper.Group#GROUP MenuGroup - -- local function AddStatusMenu( MenuGroup ) - -- local MenuGroupName = MenuGroup:GetName() - -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. - -- MenuStatus[MenuGroupName] = MENU_GROUP:New( MenuGroup, "Status for Planes" ) - -- MENU_GROUP_COMMAND:New( MenuGroup, "Show Status", MenuStatus[MenuGroupName], ShowStatus, MenuGroup, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneGroup = GROUP:FindByName( "Plane 1" ) - -- if PlaneGroup and PlaneGroup:IsAlive() then - -- local MenuManage = MENU_GROUP:New( PlaneGroup, "Manage Menus" ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Add Status Menu Plane 1", MenuManage, AddStatusMenu, PlaneGroup ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Remove Status Menu Plane 1", MenuManage, RemoveStatusMenu, PlaneGroup ) - -- end - -- end, {}, 10, 10 ) - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneGroup = GROUP:FindByName( "Plane 2" ) - -- if PlaneGroup and PlaneGroup:IsAlive() then - -- local MenuManage = MENU_GROUP:New( PlaneGroup, "Manage Menus" ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Add Status Menu Plane 2", MenuManage, AddStatusMenu, PlaneGroup ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneGroup ) - -- end - -- end, {}, 10, 10 ) - -- - MENU_GROUP = { - ClassName = "MENU_GROUP" - } - - --- MENU_GROUP constructor. Creates a new radio menu item for a group. - -- @param #MENU_GROUP self - -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. - -- @return #MENU_GROUP self - function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) - - -- Determine if the menu was not already created and already visible at the group. - -- If it is visible, then return the cached self, otherwise, create self and cache it. - - MenuGroup._Menus = MenuGroup._Menus or {} - local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText - if MenuGroup._Menus[Path] then - self = MenuGroup._Menus[Path] - else - self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - MenuGroup._Menus[Path] = self - - self.MenuGroup = MenuGroup - self.Path = Path - self.MenuGroupID = MenuGroup:GetID() - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { "Adding Menu ", MenuText, self.MenuParentPath } ) - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, self.MenuParentPath ) - - if self.ParentMenu and self.ParentMenu.Menus then - self.ParentMenu.Menus[MenuText] = self - self:F( { self.ParentMenu.Menus, MenuText } ) - self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1 - end - end - - --self:F( { MenuGroup:GetName(), MenuText, ParentMenu.MenuPath } ) - - return self - end - - --- Removes the sub menus recursively of this MENU_GROUP. - -- @param #MENU_GROUP self - -- @param MenuTime - -- @return #MENU_GROUP self - function MENU_GROUP:RemoveSubMenus( MenuTime ) - self:F2( { self.MenuPath, MenuTime, self.MenuTime } ) - - self:T( { "Removing Group SubMenus:", self.MenuGroup:GetName(), self.MenuPath } ) - for MenuText, Menu in pairs( self.Menus ) do - Menu:Remove( MenuTime ) - end - - end - - - --- Removes the main menu and sub menus recursively of this MENU_GROUP. - -- @param #MENU_GROUP self - -- @param MenuTime - -- @return #nil - function MENU_GROUP:Remove( MenuTime ) - self:F( { self.MenuGroupID, self.MenuPath, MenuTime, self.MenuTime } ) - - self:RemoveSubMenus( MenuTime ) - - if not MenuTime or self.MenuTime ~= MenuTime then - if self.MenuGroup._Menus[self.Path] then - self = self.MenuGroup._Menus[self.Path] - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuText] = nil - self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1 - if self.ParentMenu.MenuCount == 0 then - if self.MenuRemoveParent == true then - self:T( "Removing Parent Menu " ) - self.ParentMenu:Remove() - end - end - end - self:T( { "Removing Group Menu:", self.MenuGroup:GetName(), self.MenuGroup._Menus[self.Path].Path } ) - self.MenuGroup._Menus[self.Path] = nil - self = nil - end - end - - return nil - end - - - --- The MENU_GROUP_COMMAND class - -- @type MENU_GROUP_COMMAND - -- @extends Core.Menu#MENU_BASE - MENU_GROUP_COMMAND = { - ClassName = "MENU_GROUP_COMMAND" - } - - --- Creates a new radio command item for a group - -- @param #MENU_GROUP_COMMAND self - -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. - -- @param MenuText The text for the menu. - -- @param ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. - -- @return #MENU_GROUP_COMMAND - function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, ... ) - - MenuGroup._Menus = MenuGroup._Menus or {} - local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText - if MenuGroup._Menus[Path] then - self = MenuGroup._Menus[Path] - self:T( { "Re-using Group Command Menu:", MenuGroup:GetName(), MenuText } ) - else - self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - MenuGroup._Menus[Path] = self - - self.Path = Path - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { "Adding Group Command Menu:", MenuGroup:GetName(), MenuText, self.MenuParentPath } ) - self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - - if self.ParentMenu and self.ParentMenu.Menus then - self.ParentMenu.Menus[MenuText] = self - self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1 - self:F( { ParentMenu.Menus, MenuText } ) - end - end - - return self - end - - --- Removes a menu structure for a group. - -- @param #MENU_GROUP_COMMAND self - -- @param MenuTime - -- @return #nil - function MENU_GROUP_COMMAND:Remove( MenuTime ) - self:F( { self.MenuGroupID, self.MenuPath, MenuTime, self.MenuTime } ) - - if not MenuTime or self.MenuTime ~= MenuTime then - if self.MenuGroup._Menus[self.Path] then - self = self.MenuGroup._Menus[self.Path] - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - self:T( { "Removing Group Command Menu:", self.MenuGroup:GetName(), self.MenuText, self.Path, self.MenuGroup._Menus[self.Path].Path } ) - - self.ParentMenu.Menus[self.MenuText] = nil - self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1 - if self.ParentMenu.MenuCount == 0 then - if self.MenuRemoveParent == true then - self:T( "Removing Parent Menu " ) - self.ParentMenu:Remove() - end - end - - self.MenuGroup._Menus[self.Path] = nil - self = nil - end - end - - return nil - end - -end - ---- **Core** - ZONE classes define **zones** within your mission of **various forms**, with **various capabilities**. --- --- ![Banner Image](..\Presentations\ZONE\Dia1.JPG) --- --- === --- --- There are essentially two core functions that zones accomodate: --- --- * Test if an object is within the zone boundaries. --- * Provide the zone behaviour. Some zones are static, while others are moveable. --- --- The object classes are using the zone classes to test the zone boundaries, which can take various forms: --- --- * Test if completely within the zone. --- * Test if partly within the zone (for @{Group#GROUP} objects). --- * Test if not in the zone. --- * Distance to the nearest intersecting point of the zone. --- * Distance to the center of the zone. --- * ... --- --- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: --- --- * @{Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. --- * @{Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. --- * @{Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. --- * @{Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Unit#UNIT} with a radius. --- * @{Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. --- * @{Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- --- === --- --- # 1) @{Zone#ZONE_BASE} class, extends @{Base#BASE} --- --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- --- ## 1.1) Each zone has a name: --- --- * @{#ZONE_BASE.GetName}(): Returns the name of the zone. --- --- ## 1.2) Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}: --- --- * @{#ZONE_BASE.IsVec2InZone}(): Returns if a Vec2 is within the zone. --- * @{#ZONE_BASE.IsVec3InZone}(): Returns if a Vec3 is within the zone. --- --- ## 1.3) A zone has a probability factor that can be set to randomize a selection between zones: --- --- * @{#ZONE_BASE.SetRandomizeProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% ) --- * @{#ZONE_BASE.GetRandomizeProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% ) --- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate. --- --- ## 1.4) A zone manages Vectors: --- --- * @{#ZONE_BASE.GetVec2}(): Returns the @{DCSTypes#Vec2} coordinate of the zone. --- * @{#ZONE_BASE.GetRandomVec2}(): Define a random @{DCSTypes#Vec2} within the zone. --- --- ## 1.5) A zone has a bounding square: --- --- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone. --- --- ## 1.6) A zone can be marked: --- --- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. --- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. --- --- === --- --- # 2) @{Zone#ZONE_RADIUS} class, extends @{Zone#ZONE_BASE} --- --- The ZONE_RADIUS class defined by a zone name, a location and a radius. --- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. --- --- ## 2.1) @{Zone#ZONE_RADIUS} constructor --- --- * @{#ZONE_RADIUS.New}(): Constructor. --- --- ## 2.2) Manage the radius of the zone --- --- * @{#ZONE_RADIUS.SetRadius}(): Sets the radius of the zone. --- * @{#ZONE_RADIUS.GetRadius}(): Returns the radius of the zone. --- --- ## 2.3) Manage the location of the zone --- --- * @{#ZONE_RADIUS.SetVec2}(): Sets the @{DCSTypes#Vec2} of the zone. --- * @{#ZONE_RADIUS.GetVec2}(): Returns the @{DCSTypes#Vec2} of the zone. --- * @{#ZONE_RADIUS.GetVec3}(): Returns the @{DCSTypes#Vec3} of the zone, taking an additional height parameter. --- --- ## 2.4) Zone point randomization --- --- Various functions exist to find random points within the zone. --- --- * @{#ZONE_RADIUS.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Point#POINT_VEC2} object representing a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. --- --- === --- --- # 3) @{Zone#ZONE} class, extends @{Zone#ZONE_RADIUS} --- --- The ZONE class, defined by the zone name as defined within the Mission Editor. --- This class implements the inherited functions from {Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- --- === --- --- # 4) @{Zone#ZONE_UNIT} class, extends @{Zone#ZONE_RADIUS} --- --- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- --- === --- --- # 5) @{Zone#ZONE_GROUP} class, extends @{Zone#ZONE_RADIUS} --- --- The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. The current leader of the group defines the center of the zone. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- --- === --- --- # 6) @{Zone#ZONE_POLYGON_BASE} class, extends @{Zone#ZONE_BASE} --- --- The ZONE_POLYGON_BASE class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- --- ## 6.1) Zone point randomization --- --- Various functions exist to find random points within the zone. --- --- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Point#POINT_VEC2} object representing a random 2D point within the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. --- --- --- === --- --- # 7) @{Zone#ZONE_POLYGON} class, extends @{Zone#ZONE_POLYGON_BASE} --- --- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- --- ==== --- --- **API CHANGE HISTORY** --- ====================== --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-02-28: ZONE\_BASE:**IsVec2InZone()** replaces ZONE\_BASE:_IsPointVec2InZone()_. --- 2017-02-28: ZONE\_BASE:**IsVec3InZone()** replaces ZONE\_BASE:_IsPointVec3InZone()_. --- 2017-02-28: ZONE\_RADIUS:**IsVec2InZone()** replaces ZONE\_RADIUS:_IsPointVec2InZone()_. --- 2017-02-28: ZONE\_RADIUS:**IsVec3InZone()** replaces ZONE\_RADIUS:_IsPointVec3InZone()_. --- 2017-02-28: ZONE\_POLYGON:**IsVec2InZone()** replaces ZONE\_POLYGON:_IsPointVec2InZone()_. --- 2017-02-28: ZONE\_POLYGON:**IsVec3InZone()** replaces ZONE\_POLYGON:_IsPointVec3InZone()_. --- --- 2017-02-18: ZONE\_POLYGON_BASE:**GetRandomPointVec2()** added. --- --- 2017-02-18: ZONE\_POLYGON_BASE:**GetRandomPointVec3()** added. --- --- 2017-02-18: ZONE\_RADIUS:**GetRandomPointVec3( inner, outer )** added. --- --- 2017-02-18: ZONE\_RADIUS:**GetRandomPointVec2( inner, outer )** added. --- --- 2016-08-15: ZONE\_BASE:**GetName()** added. --- --- 2016-08-15: ZONE\_BASE:**SetZoneProbability( ZoneProbability )** added. --- --- 2016-08-15: ZONE\_BASE:**GetZoneProbability()** added. --- --- 2016-08-15: ZONE\_BASE:**GetZoneMaybe()** added. --- --- === --- --- @module Zone - - ---- The ZONE_BASE class --- @type ZONE_BASE --- @field #string ZoneName Name of the zone. --- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. --- @extends Core.Base#BASE -ZONE_BASE = { - ClassName = "ZONE_BASE", - ZoneName = "", - ZoneProbability = 1, - } - - ---- The ZONE_BASE.BoundingSquare --- @type ZONE_BASE.BoundingSquare --- @field Dcs.DCSTypes#Distance x1 The lower x coordinate (left down) --- @field Dcs.DCSTypes#Distance y1 The lower y coordinate (left down) --- @field Dcs.DCSTypes#Distance x2 The higher x coordinate (right up) --- @field Dcs.DCSTypes#Distance y2 The higher y coordinate (right up) - - ---- ZONE_BASE constructor --- @param #ZONE_BASE self --- @param #string ZoneName Name of the zone. --- @return #ZONE_BASE self -function ZONE_BASE:New( ZoneName ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( ZoneName ) - - self.ZoneName = ZoneName - - return self -end - ---- Returns the name of the zone. --- @param #ZONE_BASE self --- @return #string The name of the zone. -function ZONE_BASE:GetName() - self:F2() - - return self.ZoneName -end ---- Returns if a location is within the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_BASE:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) - - return false -end - ---- Returns if a point is within the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. --- @return #boolean true if the point is within the zone. -function ZONE_BASE:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) - - local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) - - return InZone -end - ---- Returns the @{DCSTypes#Vec2} coordinate of the zone. --- @param #ZONE_BASE self --- @return #nil. -function ZONE_BASE:GetVec2() - self:F2( self.ZoneName ) - - return nil -end - ---- Returns a @{Point#POINT_VEC2} of the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. -function ZONE_BASE:GetPointVec2() - self:F2( self.ZoneName ) - - local Vec2 = self:GetVec2() - - local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - - self:T2( { PointVec2 } ) - - return PointVec2 -end - - ---- Returns the @{DCSTypes#Vec3} of the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The Vec3 of the zone. -function ZONE_BASE:GetVec3( Height ) - self:F2( self.ZoneName ) - - Height = Height or 0 - - local Vec2 = self:GetVec2() - - local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { Vec3 } ) - - return Vec3 -end - ---- Returns a @{Point#POINT_VEC3} of the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. -function ZONE_BASE:GetPointVec3( Height ) - self:F2( self.ZoneName ) - - local Vec3 = self:GetVec3( Height ) - - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - - self:T2( { PointVec3 } ) - - return PointVec3 -end - - ---- Define a random @{DCSTypes#Vec2} within the zone. --- @param #ZONE_BASE self --- @return Dcs.DCSTypes#Vec2 The Vec2 coordinates. -function ZONE_BASE:GetRandomVec2() - return nil -end - ---- Define a random @{Point#POINT_VEC2} within the zone. --- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. -function ZONE_BASE:GetRandomPointVec2() - return nil -end - ---- Define a random @{Point#POINT_VEC3} within the zone. --- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. -function ZONE_BASE:GetRandomPointVec3() - return nil -end - ---- Get the bounding square the zone. --- @param #ZONE_BASE self --- @return #nil The bounding square. -function ZONE_BASE:GetBoundingSquare() - --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } - return nil -end - ---- Bound the zone boundaries with a tires. --- @param #ZONE_BASE self -function ZONE_BASE:BoundZone() - self:F2() - -end - ---- Smokes the zone boundaries in a color. --- @param #ZONE_BASE self --- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -function ZONE_BASE:SmokeZone( SmokeColor ) - self:F2( SmokeColor ) - -end - ---- Set the randomization probability of a zone to be selected. --- @param #ZONE_BASE self --- @param ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -function ZONE_BASE:SetZoneProbability( ZoneProbability ) - self:F2( ZoneProbability ) - - self.ZoneProbability = ZoneProbability or 1 - return self -end - ---- Get the randomization probability of a zone to be selected. --- @param #ZONE_BASE self --- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. -function ZONE_BASE:GetZoneProbability() - self:F2() - - return self.ZoneProbability -end - ---- Get the zone taking into account the randomization probability of a zone to be selected. --- @param #ZONE_BASE self --- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. --- @return #nil The zone is not selected taking into account the randomization probability factor. -function ZONE_BASE:GetZoneMaybe() - self:F2() - - local Randomization = math.random() - if Randomization <= self.ZoneProbability then - return self - else - return nil - end -end - - ---- The ZONE_RADIUS class, defined by a zone name, a location and a radius. --- @type ZONE_RADIUS --- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone. --- @field Dcs.DCSTypes#Distance Radius The radius of the zone. --- @extends Core.Zone#ZONE_BASE -ZONE_RADIUS = { - ClassName="ZONE_RADIUS", - } - ---- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. --- @param #ZONE_RADIUS self --- @param #string ZoneName Name of the zone. --- @param Dcs.DCSTypes#Vec2 Vec2 The location of the zone. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) - local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS - self:F( { ZoneName, Vec2, Radius } ) - - self.Radius = Radius - self.Vec2 = Vec2 - - return self -end - ---- Bounds the zone with tires. --- @param #ZONE_RADIUS self --- @param #number Points (optional) The amount of points in the circle. --- @param #boolean UnBound If true the tyres will be destroyed. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) - - local Point = {} - local Vec2 = self:GetVec2() - - Points = Points and Points or 360 - - local Angle - local RadialBase = math.pi*2 - - -- - for Angle = 0, 360, (360 / Points ) do - local Radial = Angle * RadialBase / 360 - Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - - local CountryName = _DATABASE.COUNTRY_NAME[CountryID] - - local Tire = { - ["country"] = CountryName, - ["category"] = "Fortifications", - ["canCargo"] = false, - ["shape_name"] = "H-tyre_B_WF", - ["type"] = "Black_Tyre_WF", - --["unitId"] = Angle + 10000, - ["y"] = Point.y, - ["x"] = Point.x, - ["name"] = string.format( "%s-Tire #%0d", self:GetName(), Angle ), - ["heading"] = 0, - } -- end of ["group"] - - local Group = coalition.addStaticObject( CountryID, Tire ) - if UnBound and UnBound == true then - Group:destroy() - end - end - - return self -end - - ---- Smokes the zone boundaries in a color. --- @param #ZONE_RADIUS self --- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. --- @param #number Points (optional) The amount of points in the circle. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) - self:F2( SmokeColor ) - - local Point = {} - local Vec2 = self:GetVec2() - - Points = Points and Points or 360 - - local Angle - local RadialBase = math.pi*2 - - for Angle = 0, 360, 360 / Points do - local Radial = Angle * RadialBase / 360 - Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y ):Smoke( SmokeColor ) - end - - return self -end - - ---- Flares the zone boundaries in a color. --- @param #ZONE_RADIUS self --- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. --- @param #number Points (optional) The amount of points in the circle. --- @param Dcs.DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) - self:F2( { FlareColor, Azimuth } ) - - local Point = {} - local Vec2 = self:GetVec2() - - Points = Points and Points or 360 - - local Angle - local RadialBase = math.pi*2 - - for Angle = 0, 360, 360 / Points do - local Radial = Angle * RadialBase / 360 - Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y ):Flare( FlareColor, Azimuth ) - end - - return self -end - ---- Returns the radius of the zone. --- @param #ZONE_RADIUS self --- @return Dcs.DCSTypes#Distance The radius of the zone. -function ZONE_RADIUS:GetRadius() - self:F2( self.ZoneName ) - - self:T2( { self.Radius } ) - - return self.Radius -end - ---- Sets the radius of the zone. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return Dcs.DCSTypes#Distance The radius of the zone. -function ZONE_RADIUS:SetRadius( Radius ) - self:F2( self.ZoneName ) - - self.Radius = Radius - self:T2( { self.Radius } ) - - return self.Radius -end - ---- Returns the @{DCSTypes#Vec2} of the zone. --- @param #ZONE_RADIUS self --- @return Dcs.DCSTypes#Vec2 The location of the zone. -function ZONE_RADIUS:GetVec2() - self:F2( self.ZoneName ) - - self:T2( { self.Vec2 } ) - - return self.Vec2 -end - ---- Sets the @{DCSTypes#Vec2} of the zone. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec2 Vec2 The new location of the zone. --- @return Dcs.DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetVec2( Vec2 ) - self:F2( self.ZoneName ) - - self.Vec2 = Vec2 - - self:T2( { self.Vec2 } ) - - return self.Vec2 -end - ---- Returns the @{DCSTypes#Vec3} of the ZONE_RADIUS. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The point of the zone. -function ZONE_RADIUS:GetVec3( Height ) - self:F2( { self.ZoneName, Height } ) - - Height = Height or 0 - local Vec2 = self:GetVec2() - - local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { Vec3 } ) - - return Vec3 -end - - ---- Returns if a location is within the zone. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_RADIUS:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) - - local ZoneVec2 = self:GetVec2() - - if ZoneVec2 then - if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then - return true - end - end - - return false -end - ---- Returns if a point is within the zone. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. --- @return #boolean true if the point is within the zone. -function ZONE_RADIUS:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) - - local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) - - return InZone -end - ---- Returns a random Vec2 location within the zone. --- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Dcs.DCSTypes#Vec2 The random location within the zone. -function ZONE_RADIUS:GetRandomVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) - - local Point = {} - local Vec2 = self:GetVec2() - local _inner = inner or 0 - local _outer = outer or self:GetRadius() - - local angle = math.random() * math.pi * 2; - Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); - Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); - - self:T( { Point } ) - - return Point -end - ---- Returns a @{Point#POINT_VEC2} object reflecting a random 2D location within the zone. --- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC2 The @{Point#POINT_VEC2} object reflecting the random 3D location within the zone. -function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) - - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - - self:T3( { PointVec2 } ) - - return PointVec2 -end - ---- Returns a @{Point#POINT_VEC3} object reflecting a random 3D location within the zone. --- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC3 The @{Point#POINT_VEC3} object reflecting the random 3D location within the zone. -function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) - self:F( self.ZoneName, inner, outer ) - - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) - - self:T3( { PointVec3 } ) - - return PointVec3 -end - - - ---- The ZONE class, defined by the zone name as defined within the Mission Editor. The location and the radius are automatically collected from the mission settings. --- @type ZONE --- @extends Core.Zone#ZONE_RADIUS -ZONE = { - ClassName="ZONE", - } - - ---- Constructor of ZONE, taking the zone name. --- @param #ZONE self --- @param #string ZoneName The name of the zone as defined within the mission editor. --- @return #ZONE -function ZONE:New( ZoneName ) - - local Zone = trigger.misc.getZone( ZoneName ) - - if not Zone then - error( "Zone " .. ZoneName .. " does not exist." ) - return nil - end - - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, { x = Zone.point.x, y = Zone.point.z }, Zone.radius ) ) - self:F( ZoneName ) - - self.Zone = Zone - - return self -end - - ---- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. --- @type ZONE_UNIT --- @field Wrapper.Unit#UNIT ZoneUNIT --- @extends Core.Zone#ZONE_RADIUS -ZONE_UNIT = { - ClassName="ZONE_UNIT", - } - ---- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius. --- @param #ZONE_UNIT self --- @param #string ZoneName Name of the zone. --- @param Wrapper.Unit#UNIT ZoneUNIT The unit as the center of the zone. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_UNIT self -function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) - self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) - - self.ZoneUNIT = ZoneUNIT - self.LastVec2 = ZoneUNIT:GetVec2() - - return self -end - - ---- Returns the current location of the @{Unit#UNIT}. --- @param #ZONE_UNIT self --- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. -function ZONE_UNIT:GetVec2() - self:F( self.ZoneName ) - - local ZoneVec2 = self.ZoneUNIT:GetVec2() - if ZoneVec2 then - self.LastVec2 = ZoneVec2 - return ZoneVec2 - else - return self.LastVec2 - end - - self:T( { ZoneVec2 } ) - - return nil -end - ---- Returns a random location within the zone. --- @param #ZONE_UNIT self --- @return Dcs.DCSTypes#Vec2 The random location within the zone. -function ZONE_UNIT:GetRandomVec2() - self:F( self.ZoneName ) - - local RandomVec2 = {} - local Vec2 = self.ZoneUNIT:GetVec2() - - if not Vec2 then - Vec2 = self.LastVec2 - end - - local angle = math.random() * math.pi*2; - RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - - self:T( { RandomVec2 } ) - - return RandomVec2 -end - ---- Returns the @{DCSTypes#Vec3} of the ZONE_UNIT. --- @param #ZONE_UNIT self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The point of the zone. -function ZONE_UNIT:GetVec3( Height ) - self:F2( self.ZoneName ) - - Height = Height or 0 - - local Vec2 = self:GetVec2() - - local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { Vec3 } ) - - return Vec3 -end - ---- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. --- @type ZONE_GROUP --- @field Wrapper.Group#GROUP ZoneGROUP --- @extends Core.Zone#ZONE_RADIUS -ZONE_GROUP = { - ClassName="ZONE_GROUP", - } - ---- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Group#GROUP} and a radius. --- @param #ZONE_GROUP self --- @param #string ZoneName Name of the zone. --- @param Wrapper.Group#GROUP ZoneGROUP The @{Group} as the center of the zone. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_GROUP self -function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) ) - self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) - - self.ZoneGROUP = ZoneGROUP - - return self -end - - ---- Returns the current location of the @{Group}. --- @param #ZONE_GROUP self --- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Group} location. -function ZONE_GROUP:GetVec2() - self:F( self.ZoneName ) - - local ZoneVec2 = self.ZoneGROUP:GetVec2() - - self:T( { ZoneVec2 } ) - - return ZoneVec2 -end - ---- Returns a random location within the zone of the @{Group}. --- @param #ZONE_GROUP self --- @return Dcs.DCSTypes#Vec2 The random location of the zone based on the @{Group} location. -function ZONE_GROUP:GetRandomVec2() - self:F( self.ZoneName ) - - local Point = {} - local Vec2 = self.ZoneGROUP:GetVec2() - - local angle = math.random() * math.pi*2; - Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - - self:T( { Point } ) - - return Point -end - - - --- Polygons - ---- The ZONE_POLYGON_BASE class defined by an array of @{DCSTypes#Vec2}, forming a polygon. --- @type ZONE_POLYGON_BASE --- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}. --- @extends Core.Zone#ZONE_BASE -ZONE_POLYGON_BASE = { - ClassName="ZONE_POLYGON_BASE", - } - ---- A points array. --- @type ZONE_POLYGON_BASE.ListVec2 --- @list - ---- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCSTypes#Vec2}, forming a polygon. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. --- @param #ZONE_POLYGON_BASE self --- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCSTypes#Vec2}, forming a polygon.. --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) - local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, PointsArray } ) - - local i = 0 - - self.Polygon = {} - - for i = 1, #PointsArray do - self.Polygon[i] = {} - self.Polygon[i].x = PointsArray[i].x - self.Polygon[i].y = PointsArray[i].y - end - - return self -end - ---- Flush polygon coordinates as a table in DCS.log. --- @param #ZONE_POLYGON_BASE self --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:Flush() - self:F2() - - self:E( { Polygon = self.ZoneName, Coordinates = self.Polygon } ) - - return self -end - ---- Smokes the zone boundaries in a color. --- @param #ZONE_POLYGON_BASE self --- @param #boolean UnBound If true, the tyres will be destroyed. --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:BoundZone( UnBound ) - - local i - local j - local Segments = 10 - - i = 1 - j = #self.Polygon - - while i <= #self.Polygon do - self:T( { i, j, self.Polygon[i], self.Polygon[j] } ) - - local DeltaX = self.Polygon[j].x - self.Polygon[i].x - local DeltaY = self.Polygon[j].y - self.Polygon[i].y - - for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - local PointX = self.Polygon[i].x + ( Segment * DeltaX / Segments ) - local PointY = self.Polygon[i].y + ( Segment * DeltaY / Segments ) - local Tire = { - ["country"] = "USA", - ["category"] = "Fortifications", - ["canCargo"] = false, - ["shape_name"] = "H-tyre_B_WF", - ["type"] = "Black_Tyre_WF", - ["y"] = PointY, - ["x"] = PointX, - ["name"] = string.format( "%s-Tire #%0d", self:GetName(), ((i - 1) * Segments) + Segment ), - ["heading"] = 0, - } -- end of ["group"] - - local Group = coalition.addStaticObject( country.id.USA, Tire ) - if UnBound and UnBound == true then - Group:destroy() - end - - end - j = i - i = i + 1 - end - - return self -end - - - ---- Smokes the zone boundaries in a color. --- @param #ZONE_POLYGON_BASE self --- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:SmokeZone( SmokeColor ) - self:F2( SmokeColor ) - - local i - local j - local Segments = 10 - - i = 1 - j = #self.Polygon - - while i <= #self.Polygon do - self:T( { i, j, self.Polygon[i], self.Polygon[j] } ) - - local DeltaX = self.Polygon[j].x - self.Polygon[i].x - local DeltaY = self.Polygon[j].y - self.Polygon[i].y - - for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - local PointX = self.Polygon[i].x + ( Segment * DeltaX / Segments ) - local PointY = self.Polygon[i].y + ( Segment * DeltaY / Segments ) - POINT_VEC2:New( PointX, PointY ):Smoke( SmokeColor ) - end - j = i - i = i + 1 - end - - return self -end - - - - ---- Returns if a location is within the zone. --- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html --- @param #ZONE_POLYGON_BASE self --- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) - - local Next - local Prev - local InPolygon = false - - Next = 1 - Prev = #self.Polygon - - while Next <= #self.Polygon do - self:T( { Next, Prev, self.Polygon[Next], self.Polygon[Prev] } ) - if ( ( ( self.Polygon[Next].y > Vec2.y ) ~= ( self.Polygon[Prev].y > Vec2.y ) ) and - ( Vec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( Vec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) - ) then - InPolygon = not InPolygon - end - self:T2( { InPolygon = InPolygon } ) - Prev = Next - Next = Next + 1 - end - - self:T( { InPolygon = InPolygon } ) - return InPolygon -end - ---- Define a random @{DCSTypes#Vec2} within the zone. --- @param #ZONE_POLYGON_BASE self --- @return Dcs.DCSTypes#Vec2 The Vec2 coordinate. -function ZONE_POLYGON_BASE:GetRandomVec2() - self:F2() - - --- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... - local Vec2Found = false - local Vec2 - local BS = self:GetBoundingSquare() - - self:T2( BS ) - - while Vec2Found == false do - Vec2 = { x = math.random( BS.x1, BS.x2 ), y = math.random( BS.y1, BS.y2 ) } - self:T2( Vec2 ) - if self:IsVec2InZone( Vec2 ) then - Vec2Found = true - end - end - - self:T2( Vec2 ) - - return Vec2 -end - ---- Return a @{Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. --- @param #ZONE_POLYGON_BASE self --- @return @{Point#POINT_VEC2} -function ZONE_POLYGON_BASE:GetRandomPointVec2() - self:F2() - - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - - self:T2( PointVec2 ) - - return PointVec2 -end - ---- Return a @{Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. --- @param #ZONE_POLYGON_BASE self --- @return @{Point#POINT_VEC3} -function ZONE_POLYGON_BASE:GetRandomPointVec3() - self:F2() - - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) - - self:T2( PointVec3 ) - - return PointVec3 -end - - ---- Get the bounding square the zone. --- @param #ZONE_POLYGON_BASE self --- @return #ZONE_POLYGON_BASE.BoundingSquare The bounding square. -function ZONE_POLYGON_BASE:GetBoundingSquare() - - local x1 = self.Polygon[1].x - local y1 = self.Polygon[1].y - local x2 = self.Polygon[1].x - local y2 = self.Polygon[1].y - - for i = 2, #self.Polygon do - self:T2( { self.Polygon[i], x1, y1, x2, y2 } ) - x1 = ( x1 > self.Polygon[i].x ) and self.Polygon[i].x or x1 - x2 = ( x2 < self.Polygon[i].x ) and self.Polygon[i].x or x2 - y1 = ( y1 > self.Polygon[i].y ) and self.Polygon[i].y or y1 - y2 = ( y2 < self.Polygon[i].y ) and self.Polygon[i].y or y2 - - end - - return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } -end - - - - - ---- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- @type ZONE_POLYGON --- @extends Core.Zone#ZONE_POLYGON_BASE -ZONE_POLYGON = { - ClassName="ZONE_POLYGON", - } - ---- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Group#GROUP} defined within the Mission Editor. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. --- @param #ZONE_POLYGON self --- @param #string ZoneName Name of the zone. --- @param Wrapper.Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. --- @return #ZONE_POLYGON self -function ZONE_POLYGON:New( ZoneName, ZoneGroup ) - - local GroupPoints = ZoneGroup:GetTaskRoute() - - local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, GroupPoints ) ) - self:F( { ZoneName, ZoneGroup, self.Polygon } ) - - return self -end - ---- This module contains the DATABASE class, managing the database of mission objects. --- --- ==== --- --- 1) @{#DATABASE} class, extends @{Base#BASE} --- =================================================== --- Mission designers can use the DATABASE class to refer to: --- --- * UNITS --- * GROUPS --- * CLIENTS --- * AIRPORTS --- * PLAYERSJOINED --- * PLAYERS --- --- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. --- --- Moose will automatically create one instance of the DATABASE class into the **global** object _DATABASE. --- Moose refers to _DATABASE within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. --- --- 1.1) DATABASE iterators --- ----------------------- --- You can iterate the database with the available iterator methods. --- The iterator methods will walk the DATABASE set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the DATABASE: --- --- * @{#DATABASE.ForEachUnit}: Calls a function for each @{UNIT} it finds within the DATABASE. --- * @{#DATABASE.ForEachGroup}: Calls a function for each @{GROUP} it finds within the DATABASE. --- * @{#DATABASE.ForEachPlayer}: Calls a function for each alive player it finds within the DATABASE. --- * @{#DATABASE.ForEachPlayerJoined}: Calls a function for each joined player it finds within the DATABASE. --- * @{#DATABASE.ForEachClient}: Calls a function for each @{CLIENT} it finds within the DATABASE. --- * @{#DATABASE.ForEachClientAlive}: Calls a function for each alive @{CLIENT} it finds within the DATABASE. --- --- === --- --- @module Database --- @author FlightControl - ---- DATABASE class --- @type DATABASE --- @extends Core.Base#BASE -DATABASE = { - ClassName = "DATABASE", - Templates = { - Units = {}, - Groups = {}, - ClientsByName = {}, - ClientsByID = {}, - }, - UNITS = {}, - STATICS = {}, - GROUPS = {}, - PLAYERS = {}, - PLAYERSJOINED = {}, - CLIENTS = {}, - AIRBASES = {}, - COUNTRY_ID = {}, - COUNTRY_NAME = {}, - NavPoints = {}, -} - -local _DATABASECoalition = - { - [1] = "Red", - [2] = "Blue", - } - -local _DATABASECategory = - { - ["plane"] = Unit.Category.AIRPLANE, - ["helicopter"] = Unit.Category.HELICOPTER, - ["vehicle"] = Unit.Category.GROUND_UNIT, - ["ship"] = Unit.Category.SHIP, - ["static"] = Unit.Category.STRUCTURE, - } - - ---- Creates a new DATABASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #DATABASE self --- @return #DATABASE --- @usage --- -- Define a new DATABASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE. --- DBObject = DATABASE:New() -function DATABASE:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self:SetEventPriority( 1 ) - - self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) - self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) - - -- Follow alive players and clients - self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - - self:_RegisterTemplates() - self:_RegisterGroupsAndUnits() - self:_RegisterClients() - self:_RegisterStatics() - self:_RegisterPlayers() - self:_RegisterAirbases() - - return self -end - ---- Finds a Unit based on the Unit Name. --- @param #DATABASE self --- @param #string UnitName --- @return Wrapper.Unit#UNIT The found Unit. -function DATABASE:FindUnit( UnitName ) - - local UnitFound = self.UNITS[UnitName] - return UnitFound -end - - ---- Adds a Unit based on the Unit Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddUnit( DCSUnitName ) - - if not self.UNITS[DCSUnitName] then - local UnitRegister = UNIT:Register( DCSUnitName ) - self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) - end - - return self.UNITS[DCSUnitName] -end - - ---- Deletes a Unit from the DATABASE based on the Unit Name. --- @param #DATABASE self -function DATABASE:DeleteUnit( DCSUnitName ) - - --self.UNITS[DCSUnitName] = nil -end - ---- Adds a Static based on the Static Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddStatic( DCSStaticName ) - - if not self.STATICS[DCSStaticName] then - self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) - end -end - - ---- Deletes a Static from the DATABASE based on the Static Name. --- @param #DATABASE self -function DATABASE:DeleteStatic( DCSStaticName ) - - --self.STATICS[DCSStaticName] = nil -end - ---- Finds a STATIC based on the StaticName. --- @param #DATABASE self --- @param #string StaticName --- @return Wrapper.Static#STATIC The found STATIC. -function DATABASE:FindStatic( StaticName ) - - local StaticFound = self.STATICS[StaticName] - return StaticFound -end - ---- Adds a Airbase based on the Airbase Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddAirbase( DCSAirbaseName ) - - if not self.AIRBASES[DCSAirbaseName] then - self.AIRBASES[DCSAirbaseName] = AIRBASE:Register( DCSAirbaseName ) - end -end - - ---- Deletes a Airbase from the DATABASE based on the Airbase Name. --- @param #DATABASE self -function DATABASE:DeleteAirbase( DCSAirbaseName ) - - --self.AIRBASES[DCSAirbaseName] = nil -end - ---- Finds a AIRBASE based on the AirbaseName. --- @param #DATABASE self --- @param #string AirbaseName --- @return Wrapper.Airbase#AIRBASE The found AIRBASE. -function DATABASE:FindAirbase( AirbaseName ) - - local AirbaseFound = self.AIRBASES[AirbaseName] - return AirbaseFound -end - - ---- Finds a CLIENT based on the ClientName. --- @param #DATABASE self --- @param #string ClientName --- @return Wrapper.Client#CLIENT The found CLIENT. -function DATABASE:FindClient( ClientName ) - - local ClientFound = self.CLIENTS[ClientName] - return ClientFound -end - - ---- Adds a CLIENT based on the ClientName in the DATABASE. --- @param #DATABASE self -function DATABASE:AddClient( ClientName ) - - if not self.CLIENTS[ClientName] then - self.CLIENTS[ClientName] = CLIENT:Register( ClientName ) - end - - return self.CLIENTS[ClientName] -end - - ---- Finds a GROUP based on the GroupName. --- @param #DATABASE self --- @param #string GroupName --- @return Wrapper.Group#GROUP The found GROUP. -function DATABASE:FindGroup( GroupName ) - - local GroupFound = self.GROUPS[GroupName] - return GroupFound -end - - ---- Adds a GROUP based on the GroupName in the DATABASE. --- @param #DATABASE self -function DATABASE:AddGroup( GroupName ) - - if not self.GROUPS[GroupName] then - self:E( { "Add GROUP:", GroupName } ) - self.GROUPS[GroupName] = GROUP:Register( GroupName ) - end - - return self.GROUPS[GroupName] -end - ---- Adds a player based on the Player Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddPlayer( UnitName, PlayerName ) - - if PlayerName then - self:E( { "Add player for unit:", UnitName, PlayerName } ) - self.PLAYERS[PlayerName] = self:FindUnit( UnitName ) - self.PLAYERSJOINED[PlayerName] = PlayerName - end -end - ---- Deletes a player from the DATABASE based on the Player Name. --- @param #DATABASE self -function DATABASE:DeletePlayer( PlayerName ) - - if PlayerName then - self:E( { "Clean player:", PlayerName } ) - self.PLAYERS[PlayerName] = nil - end -end - - ---- Instantiate new Groups within the DCSRTE. --- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined: --- SpawnCountryID, SpawnCategoryID --- This method is used by the SPAWN class. --- @param #DATABASE self --- @param #table SpawnTemplate --- @return #DATABASE self -function DATABASE:Spawn( SpawnTemplate ) - self:F( SpawnTemplate.name ) - - self:T( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } ) - - -- Copy the spawn variables of the template in temporary storage, nullify, and restore the spawn variables. - local SpawnCoalitionID = SpawnTemplate.CoalitionID - local SpawnCountryID = SpawnTemplate.CountryID - local SpawnCategoryID = SpawnTemplate.CategoryID - - -- Nullify - SpawnTemplate.CoalitionID = nil - SpawnTemplate.CountryID = nil - SpawnTemplate.CategoryID = nil - - self:_RegisterTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) - - self:T3( SpawnTemplate ) - coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) - - -- Restore - SpawnTemplate.CoalitionID = SpawnCoalitionID - SpawnTemplate.CountryID = SpawnCountryID - SpawnTemplate.CategoryID = SpawnCategoryID - - local SpawnGroup = self:AddGroup( SpawnTemplate.name ) - return SpawnGroup -end - ---- Set a status to a Group within the Database, this to check crossing events for example. -function DATABASE:SetStatusGroup( GroupName, Status ) - self:F2( Status ) - - self.Templates.Groups[GroupName].Status = Status -end - ---- Get a status to a Group within the Database, this to check crossing events for example. -function DATABASE:GetStatusGroup( GroupName ) - self:F2( Status ) - - if self.Templates.Groups[GroupName] then - return self.Templates.Groups[GroupName].Status - else - return "" - end -end - ---- Private method that registers new Group Templates within the DATABASE Object. --- @param #DATABASE self --- @param #table GroupTemplate --- @return #DATABASE self -function DATABASE:_RegisterTemplate( GroupTemplate, CoalitionID, CategoryID, CountryID ) - - local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name) - - local TraceTable = {} - - if not self.Templates.Groups[GroupTemplateName] then - self.Templates.Groups[GroupTemplateName] = {} - self.Templates.Groups[GroupTemplateName].Status = nil - end - - -- Delete the spans from the route, it is not needed and takes memory. - if GroupTemplate.route and GroupTemplate.route.spans then - GroupTemplate.route.spans = nil - end - - GroupTemplate.CategoryID = CategoryID - GroupTemplate.CoalitionID = CoalitionID - GroupTemplate.CountryID = CountryID - - self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName - self.Templates.Groups[GroupTemplateName].Template = GroupTemplate - self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId - self.Templates.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units - self.Templates.Groups[GroupTemplateName].Units = GroupTemplate.units - self.Templates.Groups[GroupTemplateName].CategoryID = CategoryID - self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionID - self.Templates.Groups[GroupTemplateName].CountryID = CountryID - - - TraceTable[#TraceTable+1] = "Group" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].GroupName - - TraceTable[#TraceTable+1] = "Coalition" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CoalitionID - TraceTable[#TraceTable+1] = "Category" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CategoryID - TraceTable[#TraceTable+1] = "Country" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CountryID - - TraceTable[#TraceTable+1] = "Units" - - for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do - - UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) - - self.Templates.Units[UnitTemplate.name] = {} - self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name - self.Templates.Units[UnitTemplate.name].Template = UnitTemplate - self.Templates.Units[UnitTemplate.name].GroupName = GroupTemplateName - self.Templates.Units[UnitTemplate.name].GroupTemplate = GroupTemplate - self.Templates.Units[UnitTemplate.name].GroupId = GroupTemplate.groupId - self.Templates.Units[UnitTemplate.name].CategoryID = CategoryID - self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionID - self.Templates.Units[UnitTemplate.name].CountryID = CountryID - - if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then - self.Templates.ClientsByName[UnitTemplate.name] = UnitTemplate - self.Templates.ClientsByName[UnitTemplate.name].CategoryID = CategoryID - self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionID - self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID - self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate - end - - TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplate.name].UnitName - end - - self:E( TraceTable ) -end - -function DATABASE:GetGroupTemplate( GroupName ) - local GroupTemplate = self.Templates.Groups[GroupName].Template - GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID - GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID - GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID - return GroupTemplate -end - -function DATABASE:GetGroupNameFromUnitName( UnitName ) - return self.Templates.Units[UnitName].GroupName -end - -function DATABASE:GetGroupTemplateFromUnitName( UnitName ) - return self.Templates.Units[UnitName].GroupTemplate -end - -function DATABASE:GetCoalitionFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CoalitionID -end - -function DATABASE:GetCategoryFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CategoryID -end - -function DATABASE:GetCountryFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CountryID -end - ---- Airbase - -function DATABASE:GetCoalitionFromAirbase( AirbaseName ) - return self.AIRBASES[AirbaseName]:GetCoalition() -end - -function DATABASE:GetCategoryFromAirbase( AirbaseName ) - return self.AIRBASES[AirbaseName]:GetCategory() -end - - - ---- Private method that registers all alive players in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterPlayers() - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - local UnitName = UnitData:getName() - local PlayerName = UnitData:getPlayerName() - if not self.PLAYERS[PlayerName] then - self:E( { "Add player for unit:", UnitName, PlayerName } ) - self:AddPlayer( UnitName, PlayerName ) - end - end - end - end - - return self -end - - ---- Private method that registers all Groups and Units within in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterGroupsAndUnits() - - local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSGroupId, DCSGroup in pairs( CoalitionData ) do - - if DCSGroup:isExist() then - local DCSGroupName = DCSGroup:getName() - - self:E( { "Register Group:", DCSGroupName } ) - self:AddGroup( DCSGroupName ) - - for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do - - local DCSUnitName = DCSUnit:getName() - self:E( { "Register Unit:", DCSUnitName } ) - self:AddUnit( DCSUnitName ) - end - else - self:E( { "Group does not exist: ", DCSGroup } ) - end - - end - end - - return self -end - ---- Private method that registers all Units of skill Client or Player within in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterClients() - - for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do - self:E( { "Register Client:", ClientName } ) - self:AddClient( ClientName ) - end - - return self -end - ---- @param #DATABASE self -function DATABASE:_RegisterStatics() - - local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSStaticId, DCSStatic in pairs( CoalitionData ) do - - if DCSStatic:isExist() then - local DCSStaticName = DCSStatic:getName() - - self:E( { "Register Static:", DCSStaticName } ) - self:AddStatic( DCSStaticName ) - else - self:E( { "Static does not exist: ", DCSStatic } ) - end - end - end - - return self -end - ---- @param #DATABASE self -function DATABASE:_RegisterAirbases() - - local CoalitionsData = { AirbasesRed = coalition.getAirbases( coalition.side.RED ), AirbasesBlue = coalition.getAirbases( coalition.side.BLUE ), AirbasesNeutral = coalition.getAirbases( coalition.side.NEUTRAL ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSAirbaseId, DCSAirbase in pairs( CoalitionData ) do - - local DCSAirbaseName = DCSAirbase:getName() - - self:E( { "Register Airbase:", DCSAirbaseName } ) - self:AddAirbase( DCSAirbaseName ) - end - end - - return self -end - - ---- Events - ---- Handles the OnBirth event for the alive units set. --- @param #DATABASE self --- @param Core.Event#EVENTDATA Event -function DATABASE:_EventOnBirth( Event ) - self:F2( { Event } ) - - if Event.IniDCSUnit then - if Event.IniObjectCategory == 3 then - self:AddStatic( Event.IniDCSUnitName ) - else - if Event.IniObjectCategory == 1 then - self:AddUnit( Event.IniDCSUnitName ) - self:AddGroup( Event.IniDCSGroupName ) - end - end - self:_EventOnPlayerEnterUnit( Event ) - end -end - - ---- Handles the OnDead or OnCrash event for alive units set. --- @param #DATABASE self --- @param Core.Event#EVENTDATA Event -function DATABASE:_EventOnDeadOrCrash( Event ) - self:F2( { Event } ) - - if Event.IniDCSUnit then - if Event.IniObjectCategory == 3 then - if self.STATICS[Event.IniDCSUnitName] then - self:DeleteStatic( Event.IniDCSUnitName ) - end - else - if Event.IniObjectCategory == 1 then - if self.UNITS[Event.IniDCSUnitName] then - self:DeleteUnit( Event.IniDCSUnitName ) - end - end - end - end -end - - ---- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). --- @param #DATABASE self --- @param Core.Event#EVENTDATA Event -function DATABASE:_EventOnPlayerEnterUnit( Event ) - self:F2( { Event } ) - - if Event.IniUnit then - if Event.IniObjectCategory == 1 then - self:AddUnit( Event.IniDCSUnitName ) - self:AddGroup( Event.IniDCSGroupName ) - local PlayerName = Event.IniUnit:GetPlayerName() - if not self.PLAYERS[PlayerName] then - self:AddPlayer( Event.IniUnitName, PlayerName ) - end - end - end -end - - ---- Handles the OnPlayerLeaveUnit event to clean the active players table. --- @param #DATABASE self --- @param Core.Event#EVENTDATA Event -function DATABASE:_EventOnPlayerLeaveUnit( Event ) - self:F2( { Event } ) - - if Event.IniUnit then - if Event.IniObjectCategory == 1 then - local PlayerName = Event.IniUnit:GetPlayerName() - if self.PLAYERS[PlayerName] then - self:DeletePlayer( PlayerName ) - end - end - end -end - ---- Iterators - ---- Iterate the DATABASE and call an iterator function for the given set, providing the Object for each element within the set and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive player in the database. --- @return #DATABASE self -function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) - self:F2( arg ) - - local function CoRoutine() - local Count = 0 - for ObjectID, Object in pairs( Set ) do - self:T2( Object ) - IteratorFunction( Object, unpack( arg ) ) - Count = Count + 1 --- if Count % 100 == 0 then --- coroutine.yield( false ) --- end - end - return true - end - --- local co = coroutine.create( CoRoutine ) - local co = CoRoutine - - local function Schedule() - --- local status, res = coroutine.resume( co ) - local status, res = co() - self:T3( { status, res } ) - - if status == false then - error( res ) - end - if res == false then - return true -- resume next time the loop - end - if FinalizeFunction then - FinalizeFunction( unpack( arg ) ) - end - return false - end - - local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the database. The function needs to accept a UNIT parameter. --- @return #DATABASE self -function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, FinalizeFunction, arg, self.UNITS ) - - return self -end - ---- Iterate the DATABASE and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the database. The function needs to accept a GROUP parameter. --- @return #DATABASE self -function DATABASE:ForEachGroup( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.GROUPS ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each **ALIVE** player, providing the player name and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an player in the database. The function needs to accept the player name. --- @return #DATABASE self -function DATABASE:ForEachPlayer( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.PLAYERS ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each player who has joined the mission, providing the Unit of the player and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is was a player in the database. The function needs to accept a UNIT parameter. --- @return #DATABASE self -function DATABASE:ForEachPlayerJoined( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.PLAYERSJOINED ) - - return self -end - ---- Iterate the DATABASE and call an iterator function for each CLIENT, providing the CLIENT to the function and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a CLIENT parameter. --- @return #DATABASE self -function DATABASE:ForEachClient( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.CLIENTS ) - - return self -end - - -function DATABASE:_RegisterTemplates() - self:F2() - - self.Navpoints = {} - self.UNITS = {} - --Build routines.db.units and self.Navpoints - for CoalitionName, coa_data in pairs(env.mission.coalition) do - - if (CoalitionName == 'red' or CoalitionName == 'blue') and type(coa_data) == 'table' then - --self.Units[coa_name] = {} - - local CoalitionSide = coalition.side[string.upper(CoalitionName)] - - ---------------------------------------------- - -- build nav points DB - self.Navpoints[CoalitionName] = {} - if coa_data.nav_points then --navpoints - for nav_ind, nav_data in pairs(coa_data.nav_points) do - - if type(nav_data) == 'table' then - self.Navpoints[CoalitionName][nav_ind] = routines.utils.deepCopy(nav_data) - - self.Navpoints[CoalitionName][nav_ind]['name'] = nav_data.callsignStr -- name is a little bit more self-explanatory. - self.Navpoints[CoalitionName][nav_ind]['point'] = {} -- point is used by SSE, support it. - self.Navpoints[CoalitionName][nav_ind]['point']['x'] = nav_data.x - self.Navpoints[CoalitionName][nav_ind]['point']['y'] = 0 - self.Navpoints[CoalitionName][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.upper(cntry_data.name) - local CountryID = cntry_data.id - - self.COUNTRY_ID[CountryName] = CountryID - self.COUNTRY_NAME[CountryID] = CountryName - - --self.Units[coa_name][countryName] = {} - --self.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 CategoryName = 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! - - --self.Units[coa_name][countryName][category] = {} - - for group_num, GroupTemplate in pairs(obj_type_data.group) do - - if GroupTemplate and GroupTemplate.units and type(GroupTemplate.units) == 'table' then --making sure again- this is a valid group - self:_RegisterTemplate( - GroupTemplate, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID - ) - end --if GroupTemplate and GroupTemplate.units then - end --for group_num, GroupTemplate 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 - - return self -end - - - - ---- **Core** - SET classes define **collections** of objects to perform **bulk actions** and logically **group** objects. --- --- === --- --- 1) @{Set#SET_BASE} class, extends @{Base#BASE} --- ============================================== --- The @{Set#SET_BASE} class defines the core functions that define a collection of objects. --- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. --- In this way, large loops can be done while not blocking the simulator main processing loop. --- The default **"yield interval"** is after 10 objects processed. --- The default **"time interval"** is after 0.001 seconds. --- --- 1.1) Add or remove objects from the SET --- --------------------------------------- --- Some key core functions are @{Set#SET_BASE.Add} and @{Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. --- --- 1.2) Define the SET iterator **"yield interval"** and the **"time interval"** --- ----------------------------------------------------------------------------- --- Modify the iterator intervals with the @{Set#SET_BASE.SetInteratorIntervals} method. --- You can set the **"yield interval"**, and the **"time interval"**. (See above). --- --- === --- --- 2) @{Set#SET_GROUP} class, extends @{Set#SET_BASE} --- ================================================== --- Mission designers can use the @{Set#SET_GROUP} class to build sets of groups belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Starting with certain prefix strings. --- --- 2.1) SET_GROUP construction method: --- ----------------------------------- --- Create a new SET_GROUP object with the @{#SET_GROUP.New} method: --- --- * @{#SET_GROUP.New}: Creates a new SET_GROUP object. --- --- 2.2) Add or Remove GROUP(s) from SET_GROUP: --- ------------------------------------------- --- GROUPS can be added and removed using the @{Set#SET_GROUP.AddGroupsByName} and @{Set#SET_GROUP.RemoveGroupsByName} respectively. --- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_GROUP. --- --- 2.3) SET_GROUP filter criteria: --- ------------------------------- --- You can set filter criteria to define the set of groups within the SET_GROUP. --- Filter criteria are defined by: --- --- * @{#SET_GROUP.FilterCoalitions}: Builds the SET_GROUP with the groups belonging to the coalition(s). --- * @{#SET_GROUP.FilterCategories}: Builds the SET_GROUP with the groups belonging to the category(ies). --- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the gruops belonging to the country(ies). --- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_GROUP, you can start filtering using: --- --- * @{#SET_GROUP.FilterStart}: Starts the filtering of the groups within the SET_GROUP and add or remove GROUP objects **dynamically**. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Zone#ZONE}. --- --- 2.4) SET_GROUP iterators: --- ------------------------- --- Once the filters have been defined and the SET_GROUP has been built, you can iterate the SET_GROUP with the available iterator methods. --- The iterator methods will walk the SET_GROUP set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_GROUP: --- --- * @{#SET_GROUP.ForEachGroup}: Calls a function for each alive group it finds within the SET_GROUP. --- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupPartlyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- --- ==== --- --- 3) @{Set#SET_UNIT} class, extends @{Set#SET_BASE} --- =================================================== --- Mission designers can use the @{Set#SET_UNIT} class to build sets of units belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Unit types --- * Starting with certain prefix strings. --- --- 3.1) SET_UNIT construction method: --- ---------------------------------- --- Create a new SET_UNIT object with the @{#SET_UNIT.New} method: --- --- * @{#SET_UNIT.New}: Creates a new SET_UNIT object. --- --- 3.2) Add or Remove UNIT(s) from SET_UNIT: --- ----------------------------------------- --- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. --- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. --- --- 3.3) SET_UNIT filter criteria: --- ------------------------------ --- You can set filter criteria to define the set of units within the SET_UNIT. --- Filter criteria are defined by: --- --- * @{#SET_UNIT.FilterCoalitions}: Builds the SET_UNIT with the units belonging to the coalition(s). --- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies). --- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s). --- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). --- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: --- --- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units within the SET_UNIT. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. --- --- 3.4) SET_UNIT iterators: --- ------------------------ --- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. --- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_UNIT: --- --- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT. --- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- --- Planned iterators methods in development are (so these are not yet available): --- --- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT. --- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. --- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. --- --- === --- --- 4) @{Set#SET_CLIENT} class, extends @{Set#SET_BASE} --- =================================================== --- Mission designers can use the @{Set#SET_CLIENT} class to build sets of units belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Client types --- * Starting with certain prefix strings. --- --- 4.1) SET_CLIENT construction method: --- ---------------------------------- --- Create a new SET_CLIENT object with the @{#SET_CLIENT.New} method: --- --- * @{#SET_CLIENT.New}: Creates a new SET_CLIENT object. --- --- 4.2) Add or Remove CLIENT(s) from SET_CLIENT: --- ----------------------------------------- --- CLIENTs can be added and removed using the @{Set#SET_CLIENT.AddClientsByName} and @{Set#SET_CLIENT.RemoveClientsByName} respectively. --- These methods take a single CLIENT name or an array of CLIENT names to be added or removed from SET_CLIENT. --- --- 4.3) SET_CLIENT filter criteria: --- ------------------------------ --- You can set filter criteria to define the set of clients within the SET_CLIENT. --- Filter criteria are defined by: --- --- * @{#SET_CLIENT.FilterCoalitions}: Builds the SET_CLIENT with the clients belonging to the coalition(s). --- * @{#SET_CLIENT.FilterCategories}: Builds the SET_CLIENT with the clients belonging to the category(ies). --- * @{#SET_CLIENT.FilterTypes}: Builds the SET_CLIENT with the clients belonging to the client type(s). --- * @{#SET_CLIENT.FilterCountries}: Builds the SET_CLIENT with the clients belonging to the country(ies). --- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_CLIENT, you can start filtering using: --- --- * @{#SET_CLIENT.FilterStart}: Starts the filtering of the clients within the SET_CLIENT. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Zone#ZONE}. --- --- 4.4) SET_CLIENT iterators: --- ------------------------ --- Once the filters have been defined and the SET_CLIENT has been built, you can iterate the SET_CLIENT with the available iterator methods. --- The iterator methods will walk the SET_CLIENT set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_CLIENT: --- --- * @{#SET_CLIENT.ForEachClient}: Calls a function for each alive client it finds within the SET_CLIENT. --- --- ==== --- --- 5) @{Set#SET_AIRBASE} class, extends @{Set#SET_BASE} --- ==================================================== --- Mission designers can use the @{Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: --- --- * Coalitions --- --- 5.1) SET_AIRBASE construction --- ----------------------------- --- Create a new SET_AIRBASE object with the @{#SET_AIRBASE.New} method: --- --- * @{#SET_AIRBASE.New}: Creates a new SET_AIRBASE object. --- --- 5.2) Add or Remove AIRBASEs from SET_AIRBASE --- -------------------------------------------- --- AIRBASEs can be added and removed using the @{Set#SET_AIRBASE.AddAirbasesByName} and @{Set#SET_AIRBASE.RemoveAirbasesByName} respectively. --- These methods take a single AIRBASE name or an array of AIRBASE names to be added or removed from SET_AIRBASE. --- --- 5.3) SET_AIRBASE filter criteria --- -------------------------------- --- You can set filter criteria to define the set of clients within the SET_AIRBASE. --- Filter criteria are defined by: --- --- * @{#SET_AIRBASE.FilterCoalitions}: Builds the SET_AIRBASE with the airbases belonging to the coalition(s). --- --- Once the filter criteria have been set for the SET_AIRBASE, you can start filtering using: --- --- * @{#SET_AIRBASE.FilterStart}: Starts the filtering of the airbases within the SET_AIRBASE. --- --- 5.4) SET_AIRBASE iterators: --- --------------------------- --- Once the filters have been defined and the SET_AIRBASE has been built, you can iterate the SET_AIRBASE with the available iterator methods. --- The iterator methods will walk the SET_AIRBASE set, and call for each airbase within the set a function that you provide. --- The following iterator methods are currently available within the SET_AIRBASE: --- --- * @{#SET_AIRBASE.ForEachAirbase}: Calls a function for each airbase it finds within the SET_AIRBASE. --- --- ==== --- --- ### Authors: --- --- * FlightControl : Design & Programming --- --- ### Contributions: --- --- --- @module Set - - ---- SET_BASE class --- @type SET_BASE --- @field #table Filter --- @field #table Set --- @field #table List --- @field Core.Scheduler#SCHEDULER CallScheduler --- @extends Core.Base#BASE -SET_BASE = { - ClassName = "SET_BASE", - Filter = {}, - Set = {}, - List = {}, - Index = {}, -} - ---- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_BASE self --- @return #SET_BASE --- @usage --- -- Define a new SET_BASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE. --- DBObject = SET_BASE:New() -function SET_BASE:New( Database ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Core.Set#SET_BASE - - self.Database = Database - - self.YieldInterval = 10 - self.TimeInterval = 0.001 - - self.Set = {} - - self.List = {} - self.List.__index = self.List - self.List = setmetatable( { Count = 0 }, self.List ) - - self.Index = {} - - self.CallScheduler = SCHEDULER:New( self ) - - self:SetEventPriority( 2 ) - - return self -end - ---- Finds an @{Base#BASE} object based on the object Name. --- @param #SET_BASE self --- @param #string ObjectName --- @return Core.Base#BASE The Object found. -function SET_BASE:_Find( ObjectName ) - - local ObjectFound = self.Set[ObjectName] - return ObjectFound -end - - ---- Gets the Set. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:GetSet() - self:F2() - - return self.Set -end - ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using a given ObjectName as the index. --- @param #SET_BASE self --- @param #string ObjectName --- @param Core.Base#BASE Object --- @return Core.Base#BASE The added BASE Object. -function SET_BASE:Add( ObjectName, Object ) - self:F2( ObjectName ) - - local t = { _ = Object } - - if self.List.last then - self.List.last._next = t - t._prev = self.List.last - self.List.last = t - else - -- this is the first node - self.List.first = t - self.List.last = t - end - - self.List.Count = self.List.Count + 1 - - self.Set[ObjectName] = t._ - - table.insert( self.Index, ObjectName ) - -end - ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using the Object Name as the index. --- @param #SET_BASE self --- @param Wrapper.Object#OBJECT Object --- @return Core.Base#BASE The added BASE Object. -function SET_BASE:AddObject( Object ) - self:F2( Object.ObjectName ) - - self:T( Object.UnitName ) - self:T( Object.ObjectName ) - self:Add( Object.ObjectName, Object ) - -end - - - ---- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. --- @param #SET_BASE self --- @param #string ObjectName -function SET_BASE:Remove( ObjectName ) - self:F( ObjectName ) - - local t = self.Set[ObjectName] - - self:E( { ObjectName, t } ) - - if t then - if t._next then - if t._prev then - t._next._prev = t._prev - t._prev._next = t._next - else - -- this was the first node - t._next._prev = nil - self.List._first = t._next - end - elseif t._prev then - -- this was the last node - t._prev._next = nil - self.List._last = t._prev - else - -- this was the only node - self.List._first = nil - self.List._last = nil - end - - t._next = nil - t._prev = nil - self.List.Count = self.List.Count - 1 - - for Index, Key in ipairs( self.Index ) do - if Key == ObjectName then - table.remove( self.Index, Index ) - break - end - end - - self.Set[ObjectName] = nil - - end - -end - ---- Gets a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. --- @param #SET_BASE self --- @param #string ObjectName --- @return Core.Base#BASE -function SET_BASE:Get( ObjectName ) - self:F( ObjectName ) - - local t = self.Set[ObjectName] - - self:T3( { ObjectName, t } ) - - return t - -end - ---- Gets the first object from the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return Core.Base#BASE -function SET_BASE:GetFirst() - self:F() - - local ObjectName = self.Index[1] - local FirstObject = self.Set[ObjectName] - self:T3( { FirstObject } ) - return FirstObject -end - ---- Gets the last object from the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return Core.Base#BASE -function SET_BASE:GetLast() - self:F() - - local ObjectName = self.Index[#self.Index] - local LastObject = self.Set[ObjectName] - self:T3( { LastObject } ) - return LastObject -end - ---- Gets a random object from the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return Core.Base#BASE -function SET_BASE:GetRandom() - self:F() - - local RandomItem = self.Set[self.Index[math.random(#self.Index)]] - - self:T3( { RandomItem } ) - - return RandomItem -end - - ---- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return #number Count -function SET_BASE:Count() - - return #self.Index -end - - - ---- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). --- @param #SET_BASE self --- @param #SET_BASE BaseSet --- @return #SET_BASE -function SET_BASE:SetDatabase( BaseSet ) - - -- Copy the filter criteria of the BaseSet - local OtherFilter = routines.utils.deepCopy( BaseSet.Filter ) - self.Filter = OtherFilter - - -- Now base the new Set on the BaseSet - self.Database = BaseSet:GetSet() - return self -end - - - ---- Define the SET iterator **"yield interval"** and the **"time interval"**. --- @param #SET_BASE self --- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. --- @param #number TimeInterval Sets the time in seconds when the main logic will resume the iterator loop. The default time is 0.001 seconds. --- @return #SET_BASE self -function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) - - self.YieldInterval = YieldInterval - self.TimeInterval = TimeInterval - - return self -end - - ---- Filters for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:FilterOnce() - - for ObjectName, Object in pairs( self.Database ) do - - if self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - end - end - - return self -end - ---- Starts the filtering for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:_FilterStart() - - for ObjectName, Object in pairs( self.Database ) do - - if self:IsIncludeObject( Object ) then - self:E( { "Adding Object:", ObjectName } ) - self:Add( ObjectName, Object ) - end - end - - self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) - self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) - - -- Follow alive players and clients - self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - - - return self -end - ---- Stops the filtering for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:FilterStop() - - self:UnHandleEvent( EVENTS.Birth ) - self:UnHandleEvent( EVENTS.Dead ) - self:UnHandleEvent( EVENTS.Crash ) - - return self -end - ---- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. --- @param #SET_BASE self --- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. --- @return Core.Base#BASE The closest object. -function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) - - local NearestObject = nil - local ClosestDistance = nil - - for ObjectID, ObjectData in pairs( self.Set ) do - if NearestObject == nil then - NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) - else - local Distance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) - if Distance < ClosestDistance then - NearestObject = ObjectData - ClosestDistance = Distance - end - end - end - - return NearestObject -end - - - ------ Private method that registers all alive players in the mission. ----- @param #SET_BASE self ----- @return #SET_BASE self ---function SET_BASE:_RegisterPlayers() --- --- local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } --- for CoalitionId, CoalitionData in pairs( CoalitionsData ) do --- for UnitId, UnitData in pairs( CoalitionData ) do --- self:T3( { "UnitData:", UnitData } ) --- if UnitData and UnitData:isExist() then --- local UnitName = UnitData:getName() --- if not self.PlayersAlive[UnitName] then --- self:E( { "Add player for unit:", UnitName, UnitData:getPlayerName() } ) --- self.PlayersAlive[UnitName] = UnitData:getPlayerName() --- end --- end --- end --- end --- --- return self ---end - ---- Events - ---- Handles the OnBirth event for the Set. --- @param #SET_BASE self --- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnBirth( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:AddInDatabase( Event ) - self:T3( ObjectName, Object ) - if Object and self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) - end - end -end - ---- Handles the OnDead or OnCrash event for alive units set. --- @param #SET_BASE self --- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnDeadOrCrash( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:FindInDatabase( Event ) - if ObjectName and Object ~= nil then - self:Remove( ObjectName ) - end - end -end - ---- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). --- @param #SET_BASE self --- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnPlayerEnterUnit( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:AddInDatabase( Event ) - self:T3( ObjectName, Object ) - if self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) - end - end -end - ---- Handles the OnPlayerLeaveUnit event to clean the active players table. --- @param #SET_BASE self --- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnPlayerLeaveUnit( Event ) - self:F3( { Event } ) - - local ObjectName = Event.IniDCSUnit - if Event.IniDCSUnit then - if Event.IniDCSGroup then - local GroupUnits = Event.IniDCSGroup:getUnits() - local PlayerCount = 0 - for _, DCSUnit in pairs( GroupUnits ) do - if DCSUnit ~= Event.IniDCSUnit then - if DCSUnit:getPlayer() ~= nil then - PlayerCount = PlayerCount + 1 - end - end - end - self:E(PlayerCount) - if PlayerCount == 0 then - self:Remove( Event.IniDCSGroupName ) - end - end - end -end - --- Iterators - ---- Iterate the SET_BASE and derived classes and call an iterator function for the given SET_BASE, providing the Object for each element within the set and optional parameters. --- @param #SET_BASE self --- @param #function IteratorFunction The function that will be called. --- @return #SET_BASE self -function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) - self:F3( arg ) - - Set = Set or self:GetSet() - arg = arg or {} - - local function CoRoutine() - local Count = 0 - for ObjectID, ObjectData in pairs( Set ) do - local Object = ObjectData - self:T3( Object ) - if Function then - if Function( unpack( FunctionArguments ), Object ) == true then - IteratorFunction( Object, unpack( arg ) ) - end - else - IteratorFunction( Object, unpack( arg ) ) - end - Count = Count + 1 --- if Count % self.YieldInterval == 0 then --- coroutine.yield( false ) --- end - end - return true - end - --- local co = coroutine.create( CoRoutine ) - local co = CoRoutine - - local function Schedule() - --- local status, res = coroutine.resume( co ) - local status, res = co() - self:T3( { status, res } ) - - if status == false then - error( res ) - end - if res == false then - return true -- resume next time the loop - end - - return false - end - - --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) - Schedule() - - return self -end - - ------ Iterate the SET_BASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) --- --- return self ---end --- ------ Iterate the SET_BASE and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachPlayer( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_BASE and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachClient( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- Decides whether to include the Object --- @param #SET_BASE self --- @param #table Object --- @return #SET_BASE self -function SET_BASE:IsIncludeObject( Object ) - self:F3( Object ) - - return true -end - ---- Flushes the current SET_BASE contents in the log ... (for debugging reasons). --- @param #SET_BASE self --- @return #string A string with the names of the objects. -function SET_BASE:Flush() - self:F3() - - local ObjectNames = "" - for ObjectName, Object in pairs( self.Set ) do - ObjectNames = ObjectNames .. ObjectName .. ", " - end - self:E( { "Objects in Set:", ObjectNames } ) - - return ObjectNames -end - --- SET_GROUP - ---- SET_GROUP class --- @type SET_GROUP --- @extends Core.Set#SET_BASE -SET_GROUP = { - ClassName = "SET_GROUP", - Filter = { - Coalitions = nil, - Categories = nil, - Countries = nil, - GroupPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Group.Category.AIRPLANE, - helicopter = Group.Category.HELICOPTER, - ground = Group.Category.GROUND_UNIT, - ship = Group.Category.SHIP, - structure = Group.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_GROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_GROUP self --- @return #SET_GROUP --- @usage --- -- Define a new SET_GROUP Object. This DBObject will contain a reference to all alive GROUPS. --- DBObject = SET_GROUP:New() -function SET_GROUP:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.GROUPS ) ) - - return self -end - ---- Add GROUP(s) to SET_GROUP. --- @param Core.Set#SET_GROUP self --- @param #string AddGroupNames A single name or an array of GROUP names. --- @return self -function SET_GROUP:AddGroupsByName( AddGroupNames ) - - local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } - - for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do - self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) - end - - return self -end - ---- Remove GROUP(s) from SET_GROUP. --- @param Core.Set#SET_GROUP self --- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. --- @return self -function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) - - local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } - - for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do - self:Remove( RemoveGroupName.GroupName ) - end - - return self -end - - - - ---- Finds a Group based on the Group Name. --- @param #SET_GROUP self --- @param #string GroupName --- @return Wrapper.Group#GROUP The found Group. -function SET_GROUP:FindGroup( GroupName ) - - local GroupFound = self.Set[GroupName] - return GroupFound -end - - - ---- Builds a set of groups of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_GROUP self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_GROUP self -function SET_GROUP:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of groups out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_GROUP self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_GROUP self -function SET_GROUP:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - ---- Builds a set of groups of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_GROUP self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_GROUP self -function SET_GROUP:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of groups of defined GROUP prefixes. --- All the groups starting with the given prefixes will be included within the set. --- @param #SET_GROUP self --- @param #string Prefixes The prefix of which the group name starts with. --- @return #SET_GROUP self -function SET_GROUP:FilterPrefixes( Prefixes ) - if not self.Filter.GroupPrefixes then - self.Filter.GroupPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.GroupPrefixes[Prefix] = Prefix - end - return self -end - - ---- Starts the filtering. --- @param #SET_GROUP self --- @return #SET_GROUP self -function SET_GROUP:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_GROUP self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the GROUP --- @return #table The GROUP -function SET_GROUP:AddInDatabase( Event ) - self:F3( { Event } ) - - if Event.IniObjectCategory == 1 then - if not self.Database[Event.IniDCSGroupName] then - self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) - self:T3( self.Database[Event.IniDCSGroupName] ) - end - end - - return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_GROUP self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the GROUP --- @return #table The GROUP -function SET_GROUP:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. --- @param #SET_GROUP self --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroup( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsCompletelyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsPartlyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - - ------ Iterate the SET_GROUP and call an interator function for each **alive** player, providing the Group of the player and optional parameters. ----- @param #SET_GROUP self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ----- @return #SET_GROUP self ---function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_GROUP and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_GROUP self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ----- @return #SET_GROUP self ---function SET_GROUP:ForEachClient( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- --- @param #SET_GROUP self --- @param Wrapper.Group#GROUP MooseGroup --- @return #SET_GROUP self -function SET_GROUP:IsIncludeObject( MooseGroup ) - self:F2( MooseGroup ) - local MooseGroupInclude = true - - if self.Filter.Coalitions then - local MooseGroupCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MooseGroup:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MooseGroup:GetCoalition() then - MooseGroupCoalition = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCoalition - end - - if self.Filter.Categories then - local MooseGroupCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MooseGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MooseGroup:GetCategory() then - MooseGroupCategory = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCategory - end - - if self.Filter.Countries then - local MooseGroupCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MooseGroup:GetCountry(), CountryName } ) - if country.id[CountryName] == MooseGroup:GetCountry() then - MooseGroupCountry = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCountry - end - - if self.Filter.GroupPrefixes then - local MooseGroupPrefix = false - for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do - self:T3( { "Prefix:", string.find( MooseGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MooseGroup:GetName(), GroupPrefix, 1 ) then - MooseGroupPrefix = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupPrefix - end - - self:T2( MooseGroupInclude ) - return MooseGroupInclude -end - ---- SET_UNIT class --- @type SET_UNIT --- @extends Core.Set#SET_BASE -SET_UNIT = { - ClassName = "SET_UNIT", - Units = {}, - Filter = { - Coalitions = nil, - Categories = nil, - Types = nil, - Countries = nil, - UnitPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Unit.Category.AIRPLANE, - helicopter = Unit.Category.HELICOPTER, - ground = Unit.Category.GROUND_UNIT, - ship = Unit.Category.SHIP, - structure = Unit.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_UNIT object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_UNIT self --- @return #SET_UNIT --- @usage --- -- Define a new SET_UNIT Object. This DBObject will contain a reference to all alive Units. --- DBObject = SET_UNIT:New() -function SET_UNIT:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) - - return self -end - ---- Add UNIT(s) to SET_UNIT. --- @param #SET_UNIT self --- @param #string AddUnit A single UNIT. --- @return #SET_UNIT self -function SET_UNIT:AddUnit( AddUnit ) - self:F2( AddUnit:GetName() ) - - self:Add( AddUnit:GetName(), AddUnit ) - - return self -end - - ---- Add UNIT(s) to SET_UNIT. --- @param #SET_UNIT self --- @param #string AddUnitNames A single name or an array of UNIT names. --- @return #SET_UNIT self -function SET_UNIT:AddUnitsByName( AddUnitNames ) - - local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } - - self:T( AddUnitNamesArray ) - for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do - self:Add( AddUnitName, UNIT:FindByName( AddUnitName ) ) - end - - return self -end - ---- Remove UNIT(s) from SET_UNIT. --- @param Core.Set#SET_UNIT self --- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. --- @return self -function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - - local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } - - for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do - self:Remove( RemoveUnitName ) - end - - return self -end - - ---- Finds a Unit based on the Unit Name. --- @param #SET_UNIT self --- @param #string UnitName --- @return Wrapper.Unit#UNIT The found Unit. -function SET_UNIT:FindUnit( UnitName ) - - local UnitFound = self.Set[UnitName] - return UnitFound -end - - - ---- Builds a set of units of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_UNIT self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_UNIT self -function SET_UNIT:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of units out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_UNIT self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_UNIT self -function SET_UNIT:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - - ---- Builds a set of units of defined unit types. --- Possible current types are those types known within DCS world. --- @param #SET_UNIT self --- @param #string Types Can take those type strings known within DCS world. --- @return #SET_UNIT self -function SET_UNIT:FilterTypes( Types ) - if not self.Filter.Types then - self.Filter.Types = {} - end - if type( Types ) ~= "table" then - Types = { Types } - end - for TypeID, Type in pairs( Types ) do - self.Filter.Types[Type] = Type - end - return self -end - - ---- Builds a set of units of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_UNIT self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_UNIT self -function SET_UNIT:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of units of defined unit prefixes. --- All the units starting with the given prefixes will be included within the set. --- @param #SET_UNIT self --- @param #string Prefixes The prefix of which the unit name starts with. --- @return #SET_UNIT self -function SET_UNIT:FilterPrefixes( Prefixes ) - if not self.Filter.UnitPrefixes then - self.Filter.UnitPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.UnitPrefixes[Prefix] = Prefix - end - return self -end - ---- Builds a set of units having a radar of give types. --- All the units having a radar of a given type will be included within the set. --- @param #SET_UNIT self --- @param #table RadarTypes The radar types. --- @return #SET_UNIT self -function SET_UNIT:FilterHasRadar( RadarTypes ) - - self.Filter.RadarTypes = self.Filter.RadarTypes or {} - if type( RadarTypes ) ~= "table" then - RadarTypes = { RadarTypes } - end - for RadarTypeID, RadarType in pairs( RadarTypes ) do - self.Filter.RadarTypes[RadarType] = RadarType - end - return self -end - ---- Builds a set of SEADable units. --- @param #SET_UNIT self --- @return #SET_UNIT self -function SET_UNIT:FilterHasSEAD() - - self.Filter.SEAD = true - return self -end - - - ---- Starts the filtering. --- @param #SET_UNIT self --- @return #SET_UNIT self -function SET_UNIT:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_UNIT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the UNIT --- @return #table The UNIT -function SET_UNIT:AddInDatabase( Event ) - self:F3( { Event } ) - - if Event.IniObjectCategory == 1 then - if not self.Database[Event.IniDCSUnitName] then - self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName ) - self:T3( self.Database[Event.IniDCSUnitName] ) - end - end - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_UNIT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the UNIT --- @return #table The UNIT -function SET_UNIT:FindInDatabase( Event ) - self:E( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - - - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] -end - ---- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. --- @param #SET_UNIT self --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnit( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. --- @param #SET_UNIT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsCompletelyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. --- @param #SET_UNIT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Returns map of unit types. --- @param #SET_UNIT self --- @return #map<#string,#number> A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. -function SET_UNIT:GetUnitTypes() - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for UnitID, UnitData in pairs( self:GetSet() ) do - local TextUnit = UnitData -- Wrapper.Unit#UNIT - if TextUnit:IsAlive() then - local UnitType = TextUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return UnitTypes -end - - ---- Returns a comma separated string of the unit types with a count in the @{Set}. --- @param #SET_UNIT self --- @return #string The unit types string -function SET_UNIT:GetUnitTypesText() - self:F2() - - local MT = {} -- Message Text - local UnitTypes = self:GetUnitTypes() - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) -end - ---- Returns map of unit threat levels. --- @param #SET_UNIT self --- @return #table. -function SET_UNIT:GetUnitThreatLevels() - self:F2() - - local UnitThreatLevels = {} - - for UnitID, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - if ThreatUnit:IsAlive() then - local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() - local ThreatUnitName = ThreatUnit:GetName() - - UnitThreatLevels[UnitThreatLevel] = UnitThreatLevels[UnitThreatLevel] or {} - UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText = UnitThreatLevelText - UnitThreatLevels[UnitThreatLevel].Units = UnitThreatLevels[UnitThreatLevel].Units or {} - UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName] = ThreatUnit - end - end - - return UnitThreatLevels -end - ---- Calculate the maxium A2G threat level of the SET_UNIT. --- @param #SET_UNIT self -function SET_UNIT:CalculateThreatLevelA2G() - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - return MaxThreatLevelA2G - -end - - ---- Returns if the @{Set} has targets having a radar (of a given type). --- @param #SET_UNIT self --- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType --- @return #number The amount of radars in the Set with the given type -function SET_UNIT:HasRadar( RadarType ) - self:F2( RadarType ) - - local RadarCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT - local HasSensors - if RadarType then - HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) - else - HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) - end - self:T3(HasSensors) - if HasSensors then - RadarCount = RadarCount + 1 - end - end - - return RadarCount -end - ---- Returns if the @{Set} has targets that can be SEADed. --- @param #SET_UNIT self --- @return #number The amount of SEADable units in the Set -function SET_UNIT:HasSEAD() - self:F2() - - local SEADCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSEAD = UnitData -- Wrapper.Unit#UNIT - if UnitSEAD:IsAlive() then - local UnitSEADAttributes = UnitSEAD:GetDesc().attributes - - local HasSEAD = UnitSEAD:HasSEAD() - - self:T3(HasSEAD) - if HasSEAD then - SEADCount = SEADCount + 1 - end - end - end - - return SEADCount -end - ---- Returns if the @{Set} has ground targets. --- @param #SET_UNIT self --- @return #number The amount of ground targets in the Set. -function SET_UNIT:HasGroundUnits() - self:F2() - - local GroundUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Wrapper.Unit#UNIT - if UnitTest:IsGround() then - GroundUnitCount = GroundUnitCount + 1 - end - end - - return GroundUnitCount -end - ---- Returns if the @{Set} has friendly ground units. --- @param #SET_UNIT self --- @return #number The amount of ground targets in the Set. -function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) - self:F2() - - local FriendlyUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Wrapper.Unit#UNIT - if UnitTest:IsFriendly( FriendlyCoalition ) then - FriendlyUnitCount = FriendlyUnitCount + 1 - end - end - - return FriendlyUnitCount -end - - - ------ Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ----- @param #SET_UNIT self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ----- @return #SET_UNIT self ---function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_UNIT self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ----- @return #SET_UNIT self ---function SET_UNIT:ForEachClient( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- --- @param #SET_UNIT self --- @param Wrapper.Unit#UNIT MUnit --- @return #SET_UNIT self -function SET_UNIT:IsIncludeObject( MUnit ) - self:F2( MUnit ) - local MUnitInclude = true - - if self.Filter.Coalitions then - local MUnitCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then - MUnitCoalition = true - end - end - MUnitInclude = MUnitInclude and MUnitCoalition - end - - if self.Filter.Categories then - local MUnitCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then - MUnitCategory = true - end - end - MUnitInclude = MUnitInclude and MUnitCategory - end - - if self.Filter.Types then - local MUnitType = false - for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) - if TypeName == MUnit:GetTypeName() then - MUnitType = true - end - end - MUnitInclude = MUnitInclude and MUnitType - end - - if self.Filter.Countries then - local MUnitCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) - if country.id[CountryName] == MUnit:GetCountry() then - MUnitCountry = true - end - end - MUnitInclude = MUnitInclude and MUnitCountry - end - - if self.Filter.UnitPrefixes then - local MUnitPrefix = false - for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do - self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) - if string.find( MUnit:GetName(), UnitPrefix, 1 ) then - MUnitPrefix = true - end - end - MUnitInclude = MUnitInclude and MUnitPrefix - end - - if self.Filter.RadarTypes then - local MUnitRadar = false - for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do - self:T3( { "Radar:", RadarType } ) - if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then - if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. - self:T3( "RADAR Found" ) - end - MUnitRadar = true - end - end - MUnitInclude = MUnitInclude and MUnitRadar - end - - if self.Filter.SEAD then - local MUnitSEAD = false - if MUnit:HasSEAD() == true then - self:T3( "SEAD Found" ) - MUnitSEAD = true - end - MUnitInclude = MUnitInclude and MUnitSEAD - end - - self:T2( MUnitInclude ) - return MUnitInclude -end - - ---- SET_CLIENT - ---- SET_CLIENT class --- @type SET_CLIENT --- @extends Core.Set#SET_BASE -SET_CLIENT = { - ClassName = "SET_CLIENT", - Clients = {}, - Filter = { - Coalitions = nil, - Categories = nil, - Types = nil, - Countries = nil, - ClientPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Unit.Category.AIRPLANE, - helicopter = Unit.Category.HELICOPTER, - ground = Unit.Category.GROUND_UNIT, - ship = Unit.Category.SHIP, - structure = Unit.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_CLIENT object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_CLIENT self --- @return #SET_CLIENT --- @usage --- -- Define a new SET_CLIENT Object. This DBObject will contain a reference to all Clients. --- DBObject = SET_CLIENT:New() -function SET_CLIENT:New() - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CLIENTS ) ) - - return self -end - ---- Add CLIENT(s) to SET_CLIENT. --- @param Core.Set#SET_CLIENT self --- @param #string AddClientNames A single name or an array of CLIENT names. --- @return self -function SET_CLIENT:AddClientsByName( AddClientNames ) - - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } - - for AddClientID, AddClientName in pairs( AddClientNamesArray ) do - self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) - end - - return self -end - ---- Remove CLIENT(s) from SET_CLIENT. --- @param Core.Set#SET_CLIENT self --- @param Wrapper.Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. --- @return self -function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) - - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } - - for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do - self:Remove( RemoveClientName.ClientName ) - end - - return self -end - - ---- Finds a Client based on the Client Name. --- @param #SET_CLIENT self --- @param #string ClientName --- @return Wrapper.Client#CLIENT The found Client. -function SET_CLIENT:FindClient( ClientName ) - - local ClientFound = self.Set[ClientName] - return ClientFound -end - - - ---- Builds a set of clients of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_CLIENT self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_CLIENT self -function SET_CLIENT:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of clients out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_CLIENT self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_CLIENT self -function SET_CLIENT:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - - ---- Builds a set of clients of defined client types. --- Possible current types are those types known within DCS world. --- @param #SET_CLIENT self --- @param #string Types Can take those type strings known within DCS world. --- @return #SET_CLIENT self -function SET_CLIENT:FilterTypes( Types ) - if not self.Filter.Types then - self.Filter.Types = {} - end - if type( Types ) ~= "table" then - Types = { Types } - end - for TypeID, Type in pairs( Types ) do - self.Filter.Types[Type] = Type - end - return self -end - - ---- Builds a set of clients of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_CLIENT self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_CLIENT self -function SET_CLIENT:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of clients of defined client prefixes. --- All the clients starting with the given prefixes will be included within the set. --- @param #SET_CLIENT self --- @param #string Prefixes The prefix of which the client name starts with. --- @return #SET_CLIENT self -function SET_CLIENT:FilterPrefixes( Prefixes ) - if not self.Filter.ClientPrefixes then - self.Filter.ClientPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.ClientPrefixes[Prefix] = Prefix - end - return self -end - - - - ---- Starts the filtering. --- @param #SET_CLIENT self --- @return #SET_CLIENT self -function SET_CLIENT:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_CLIENT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the CLIENT --- @return #table The CLIENT -function SET_CLIENT:AddInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_CLIENT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the CLIENT --- @return #table The CLIENT -function SET_CLIENT:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Iterate the SET_CLIENT and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. --- @param #SET_CLIENT self --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClient( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. --- @param #SET_CLIENT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. --- @param #SET_CLIENT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- --- @param #SET_CLIENT self --- @param Wrapper.Client#CLIENT MClient --- @return #SET_CLIENT self -function SET_CLIENT:IsIncludeObject( MClient ) - self:F2( MClient ) - - local MClientInclude = true - - if MClient then - local MClientName = MClient.UnitName - - if self.Filter.Coalitions then - local MClientCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - local ClientCoalitionID = _DATABASE:GetCoalitionFromClientTemplate( MClientName ) - self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == ClientCoalitionID then - MClientCoalition = true - end - end - self:T( { "Evaluated Coalition", MClientCoalition } ) - MClientInclude = MClientInclude and MClientCoalition - end - - if self.Filter.Categories then - local MClientCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - local ClientCategoryID = _DATABASE:GetCategoryFromClientTemplate( MClientName ) - self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then - MClientCategory = true - end - end - self:T( { "Evaluated Category", MClientCategory } ) - MClientInclude = MClientInclude and MClientCategory - end - - if self.Filter.Types then - local MClientType = false - for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) - if TypeName == MClient:GetTypeName() then - MClientType = true - end - end - self:T( { "Evaluated Type", MClientType } ) - MClientInclude = MClientInclude and MClientType - end - - if self.Filter.Countries then - local MClientCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) - self:T3( { "Country:", ClientCountryID, country.id[CountryName], CountryName } ) - if country.id[CountryName] and country.id[CountryName] == ClientCountryID then - MClientCountry = true - end - end - self:T( { "Evaluated Country", MClientCountry } ) - MClientInclude = MClientInclude and MClientCountry - end - - if self.Filter.ClientPrefixes then - local MClientPrefix = false - for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do - self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) - if string.find( MClient.UnitName, ClientPrefix, 1 ) then - MClientPrefix = true - end - end - self:T( { "Evaluated Prefix", MClientPrefix } ) - MClientInclude = MClientInclude and MClientPrefix - end - end - - self:T2( MClientInclude ) - return MClientInclude -end - ---- SET_AIRBASE - ---- SET_AIRBASE class --- @type SET_AIRBASE --- @extends Core.Set#SET_BASE -SET_AIRBASE = { - ClassName = "SET_AIRBASE", - Airbases = {}, - Filter = { - Coalitions = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - airdrome = Airbase.Category.AIRDROME, - helipad = Airbase.Category.HELIPAD, - ship = Airbase.Category.SHIP, - }, - }, -} - - ---- Creates a new SET_AIRBASE object, building a set of airbases belonging to a coalitions and categories. --- @param #SET_AIRBASE self --- @return #SET_AIRBASE self --- @usage --- -- Define a new SET_AIRBASE Object. The DatabaseSet will contain a reference to all Airbases. --- DatabaseSet = SET_AIRBASE:New() -function SET_AIRBASE:New() - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.AIRBASES ) ) - - return self -end - ---- Add AIRBASEs to SET_AIRBASE. --- @param Core.Set#SET_AIRBASE self --- @param #string AddAirbaseNames A single name or an array of AIRBASE names. --- @return self -function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) - - local AddAirbaseNamesArray = ( type( AddAirbaseNames ) == "table" ) and AddAirbaseNames or { AddAirbaseNames } - - for AddAirbaseID, AddAirbaseName in pairs( AddAirbaseNamesArray ) do - self:Add( AddAirbaseName, AIRBASE:FindByName( AddAirbaseName ) ) - end - - return self -end - ---- Remove AIRBASEs from SET_AIRBASE. --- @param Core.Set#SET_AIRBASE self --- @param Wrapper.Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. --- @return self -function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) - - local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } - - for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do - self:Remove( RemoveAirbaseName.AirbaseName ) - end - - return self -end - - ---- Finds a Airbase based on the Airbase Name. --- @param #SET_AIRBASE self --- @param #string AirbaseName --- @return Wrapper.Airbase#AIRBASE The found Airbase. -function SET_AIRBASE:FindAirbase( AirbaseName ) - - local AirbaseFound = self.Set[AirbaseName] - return AirbaseFound -end - - - ---- Builds a set of airbases of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_AIRBASE self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of airbases out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_AIRBASE self --- @param #string Categories Can take the following values: "airdrome", "helipad", "ship". --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - ---- Starts the filtering. --- @param #SET_AIRBASE self --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_AIRBASE self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the AIRBASE --- @return #table The AIRBASE -function SET_AIRBASE:AddInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_AIRBASE self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the AIRBASE --- @return #table The AIRBASE -function SET_AIRBASE:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Iterate the SET_AIRBASE and call an interator function for each AIRBASE, providing the AIRBASE and optional parameters. --- @param #SET_AIRBASE self --- @param #function IteratorFunction The function that will be called when there is an alive AIRBASE in the SET_AIRBASE. The function needs to accept a AIRBASE parameter. --- @return #SET_AIRBASE self -function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_AIRBASE while identifying the nearest @{Airbase#AIRBASE} from a @{Point#POINT_VEC2}. --- @param #SET_AIRBASE self --- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Airbase#AIRBASE}. --- @return Wrapper.Airbase#AIRBASE The closest @{Airbase#AIRBASE}. -function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) - - local NearestAirbase = self:FindNearestObjectFromPointVec2( PointVec2 ) - return NearestAirbase -end - - - ---- --- @param #SET_AIRBASE self --- @param Wrapper.Airbase#AIRBASE MAirbase --- @return #SET_AIRBASE self -function SET_AIRBASE:IsIncludeObject( MAirbase ) - self:F2( MAirbase ) - - local MAirbaseInclude = true - - if MAirbase then - local MAirbaseName = MAirbase:GetName() - - if self.Filter.Coalitions then - local MAirbaseCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - local AirbaseCoalitionID = _DATABASE:GetCoalitionFromAirbase( MAirbaseName ) - self:T3( { "Coalition:", AirbaseCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == AirbaseCoalitionID then - MAirbaseCoalition = true - end - end - self:T( { "Evaluated Coalition", MAirbaseCoalition } ) - MAirbaseInclude = MAirbaseInclude and MAirbaseCoalition - end - - if self.Filter.Categories then - local MAirbaseCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - local AirbaseCategoryID = _DATABASE:GetCategoryFromAirbase( MAirbaseName ) - self:T3( { "Category:", AirbaseCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == AirbaseCategoryID then - MAirbaseCategory = true - end - end - self:T( { "Evaluated Category", MAirbaseCategory } ) - MAirbaseInclude = MAirbaseInclude and MAirbaseCategory - end - end - - self:T2( MAirbaseInclude ) - return MAirbaseInclude -end ---- **Core** - **POINT\_VEC** classes define an **extensive API** to **manage 3D points** in the simulation space. --- --- 1) @{Point#POINT_VEC3} class, extends @{Base#BASE} --- ================================================== --- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. --- --- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. --- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. --- --- ## 1.1) POINT_VEC3 constructor --- --- A new POINT_VEC3 instance can be created with: --- --- * @{Point#POINT_VEC3.New}(): a 3D point. --- * @{Point#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCSTypes#Vec3}. --- --- ## 1.2) Manupulate the X, Y, Z coordinates of the point --- --- A POINT_VEC3 class works in 3D space. It contains internally an X, Y, Z coordinate. --- Methods exist to manupulate these coordinates. --- --- The current X, Y, Z axis can be retrieved with the methods @{#POINT_VEC3.GetX}(), @{#POINT_VEC3.GetY}(), @{#POINT_VEC3.GetZ}() respectively. --- The methods @{#POINT_VEC3.SetX}(), @{#POINT_VEC3.SetY}(), @{#POINT_VEC3.SetZ}() change the respective axis with a new value. --- The current axis values can be changed by using the methods @{#POINT_VEC3.AddX}(), @{#POINT_VEC3.AddY}(), @{#POINT_VEC3.AddZ}() --- to add or substract a value from the current respective axis value. --- Note that the Set and Add methods return the current POINT_VEC3 object, so these manipulation methods can be chained... For example: --- --- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3() --- --- ## 1.3) Create waypoints for routes --- --- A POINT_VEC3 can prepare waypoints for Ground, Air and Naval groups to be embedded into a Route. --- --- --- ## 1.5) Smoke, flare, explode, illuminate --- --- At the point a smoke, flare, explosion and illumination bomb can be triggered. Use the following methods: --- --- ### 1.5.1) Smoke --- --- * @{#POINT_VEC3.Smoke}(): To smoke the point in a certain color. --- * @{#POINT_VEC3.SmokeBlue}(): To smoke the point in blue. --- * @{#POINT_VEC3.SmokeRed}(): To smoke the point in red. --- * @{#POINT_VEC3.SmokeOrange}(): To smoke the point in orange. --- * @{#POINT_VEC3.SmokeWhite}(): To smoke the point in white. --- * @{#POINT_VEC3.SmokeGreen}(): To smoke the point in green. --- --- ### 1.5.2) Flare --- --- * @{#POINT_VEC3.Flare}(): To flare the point in a certain color. --- * @{#POINT_VEC3.FlareRed}(): To flare the point in red. --- * @{#POINT_VEC3.FlareYellow}(): To flare the point in yellow. --- * @{#POINT_VEC3.FlareWhite}(): To flare the point in white. --- * @{#POINT_VEC3.FlareGreen}(): To flare the point in green. --- --- ### 1.5.3) Explode --- --- * @{#POINT_VEC3.Explosion}(): To explode the point with a certain intensity. --- --- ### 1.5.4) Illuminate --- --- * @{#POINT_VEC3.IlluminationBomb}(): To illuminate the point. --- --- --- 2) @{Point#POINT_VEC2} class, extends @{Point#POINT_VEC3} --- ========================================================= --- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. --- --- 2.1) POINT_VEC2 constructor --- --------------------------- --- A new POINT_VEC2 instance can be created with: --- --- * @{Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter. --- * @{Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCSTypes#Vec2}. --- --- ## 1.2) Manupulate the X, Altitude, Y coordinates of the 2D point --- --- A POINT_VEC2 class works in 2D space, with an altitude setting. It contains internally an X, Altitude, Y coordinate. --- Methods exist to manupulate these coordinates. --- --- The current X, Altitude, Y axis can be retrieved with the methods @{#POINT_VEC2.GetX}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetY}() respectively. --- The methods @{#POINT_VEC2.SetX}(), @{#POINT_VEC2.SetAlt}(), @{#POINT_VEC2.SetY}() change the respective axis with a new value. --- The current Lat(itude), Alt(itude), Lon(gitude) values can also be retrieved with the methods @{#POINT_VEC2.GetLat}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetLon}() respectively. --- The current axis values can be changed by using the methods @{#POINT_VEC2.AddX}(), @{#POINT_VEC2.AddAlt}(), @{#POINT_VEC2.AddY}() --- to add or substract a value from the current respective axis value. --- Note that the Set and Add methods return the current POINT_VEC2 object, so these manipulation methods can be chained... For example: --- --- local Vec2 = PointVec2:AddX( 100 ):AddY( 2000 ):GetVec2() --- --- === --- --- **API CHANGE HISTORY** --- ====================== --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-03: POINT\_VEC3:**Explosion( ExplosionIntensity )** added. --- 2017-03-03: POINT\_VEC3:**IlluminationBomb()** added. --- --- 2017-02-18: POINT\_VEC3:**NewFromVec2( Vec2, LandHeightAdd )** added. --- --- 2016-08-12: POINT\_VEC3:**Translate( Distance, Angle )** added. --- --- 2016-08-06: Made PointVec3 and Vec3, PointVec2 and Vec2 terminology used in the code consistent. --- --- * Replaced method _Point_Vec3() to **Vec3**() where the code manages a Vec3. Replaced all references to the method. --- * Replaced method _Point_Vec2() to **Vec2**() where the code manages a Vec2. Replaced all references to the method. --- * Replaced method Random_Point_Vec3() to **RandomVec3**() where the code manages a Vec3. Replaced all references to the method. --- . --- === --- --- ### Authors: --- --- * FlightControl : Design & Programming --- --- ### Contributions: --- --- @module Point - ---- The POINT_VEC3 class --- @type POINT_VEC3 --- @field #number x The x coordinate in 3D space. --- @field #number y The y coordinate in 3D space. --- @field #number z The z coordiante in 3D space. --- @field Utilities.Utils#SMOKECOLOR SmokeColor --- @field Utilities.Utils#FLARECOLOR FlareColor --- @field #POINT_VEC3.RoutePointAltType RoutePointAltType --- @field #POINT_VEC3.RoutePointType RoutePointType --- @field #POINT_VEC3.RoutePointAction RoutePointAction --- @extends Core.Base#BASE -POINT_VEC3 = { - ClassName = "POINT_VEC3", - Metric = true, - RoutePointAltType = { - BARO = "BARO", - }, - RoutePointType = { - TakeOffParking = "TakeOffParking", - TurningPoint = "Turning Point", - }, - RoutePointAction = { - FromParkingArea = "From Parking Area", - TurningPoint = "Turning Point", - }, -} - ---- The POINT_VEC2 class --- @type POINT_VEC2 --- @field Dcs.DCSTypes#Distance x The x coordinate in meters. --- @field Dcs.DCSTypes#Distance y the y coordinate in meters. --- @extends Core.Point#POINT_VEC3 -POINT_VEC2 = { - ClassName = "POINT_VEC2", -} - - -do -- POINT_VEC3 - ---- RoutePoint AltTypes --- @type POINT_VEC3.RoutePointAltType --- @field BARO "BARO" - ---- RoutePoint Types --- @type POINT_VEC3.RoutePointType --- @field TakeOffParking "TakeOffParking" --- @field TurningPoint "Turning Point" - ---- RoutePoint Actions --- @type POINT_VEC3.RoutePointAction --- @field FromParkingArea "From Parking Area" --- @field TurningPoint "Turning Point" - --- Constructor. - ---- Create a new POINT_VEC3 object. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. --- @param Dcs.DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. --- @return Core.Point#POINT_VEC3 self -function POINT_VEC3:New( x, y, z ) - - local self = BASE:Inherit( self, BASE:New() ) - self.x = x - self.y = y - self.z = z - - return self -end - ---- Create a new POINT_VEC3 object from Vec2 coordinates. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. --- @return Core.Point#POINT_VEC3 self -function POINT_VEC3:NewFromVec2( Vec2, LandHeightAdd ) - - local LandHeight = land.getHeight( Vec2 ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - self = self:New( Vec2.x, LandHeight, Vec2.y ) - - self:F2( self ) - - return self -end - ---- Create a new POINT_VEC3 object from Vec3 coordinates. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Core.Point#POINT_VEC3 self -function POINT_VEC3:NewFromVec3( Vec3 ) - - self = self:New( Vec3.x, Vec3.y, Vec3.z ) - self:F2( self ) - return self -end - - ---- Return the coordinates of the POINT_VEC3 in Vec3 format. --- @param #POINT_VEC3 self --- @return Dcs.DCSTypes#Vec3 The Vec3 coodinate. -function POINT_VEC3:GetVec3() - return { x = self.x, y = self.y, z = self.z } -end - ---- Return the coordinates of the POINT_VEC3 in Vec2 format. --- @param #POINT_VEC3 self --- @return Dcs.DCSTypes#Vec2 The Vec2 coodinate. -function POINT_VEC3:GetVec2() - return { x = self.x, y = self.z } -end - - ---- Return the x coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The x coodinate. -function POINT_VEC3:GetX() - return self.x -end - ---- Return the y coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The y coodinate. -function POINT_VEC3:GetY() - return self.y -end - ---- Return the z coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The z coodinate. -function POINT_VEC3:GetZ() - return self.z -end - ---- Set the x coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number x The x coordinate. --- @return #POINT_VEC3 -function POINT_VEC3:SetX( x ) - self.x = x - return self -end - ---- Set the y coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number y The y coordinate. --- @return #POINT_VEC3 -function POINT_VEC3:SetY( y ) - self.y = y - return self -end - ---- Set the z coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number z The z coordinate. --- @return #POINT_VEC3 -function POINT_VEC3:SetZ( z ) - self.z = z - return self -end - ---- Add to the x coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number x The x coordinate value to add to the current x coodinate. --- @return #POINT_VEC3 -function POINT_VEC3:AddX( x ) - self.x = self.x + x - return self -end - ---- Add to the y coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number y The y coordinate value to add to the current y coodinate. --- @return #POINT_VEC3 -function POINT_VEC3:AddY( y ) - self.y = self.y + y - return self -end - ---- Add to the z coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number z The z coordinate value to add to the current z coodinate. --- @return #POINT_VEC3 -function POINT_VEC3:AddZ( z ) - self.z = self.z +z - return self -end - ---- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance OuterRadius --- @param Dcs.DCSTypes#Distance InnerRadius --- @return Dcs.DCSTypes#Vec2 Vec2 -function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { OuterRadius, InnerRadius } ) - - local Theta = 2 * math.pi * math.random() - local Radials = math.random() + math.random() - if Radials > 1 then - Radials = 2 - Radials - end - - local RadialMultiplier - if InnerRadius and InnerRadius <= OuterRadius then - RadialMultiplier = ( OuterRadius - InnerRadius ) * Radials + InnerRadius - else - RadialMultiplier = OuterRadius * Radials - end - - local RandomVec2 - if OuterRadius > 0 then - RandomVec2 = { x = math.cos( Theta ) * RadialMultiplier + self:GetX(), y = math.sin( Theta ) * RadialMultiplier + self:GetZ() } - else - RandomVec2 = { x = self:GetX(), y = self:GetZ() } - end - - return RandomVec2 -end - ---- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance OuterRadius --- @param Dcs.DCSTypes#Distance InnerRadius --- @return #POINT_VEC2 -function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { OuterRadius, InnerRadius } ) - - return POINT_VEC2:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) -end - ---- Return a random Vec3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance OuterRadius --- @param Dcs.DCSTypes#Distance InnerRadius --- @return Dcs.DCSTypes#Vec3 Vec3 -function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) - - local RandomVec2 = self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - local y = self:GetY() + math.random( InnerRadius, OuterRadius ) - local RandomVec3 = { x = RandomVec2.x, y = y, z = RandomVec2.y } - - return RandomVec3 -end - ---- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance OuterRadius --- @param Dcs.DCSTypes#Distance InnerRadius --- @return #POINT_VEC3 -function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) - - return POINT_VEC3:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) -end - - ---- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. -function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) - return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } -end - ---- Get a correction in radians of the real magnetic north of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number CorrectionRadians The correction in radians. -function POINT_VEC3:GetNorthCorrectionRadians() - local TargetVec3 = self:GetVec3() - local lat, lon = coord.LOtoLL(TargetVec3) - local north_posit = coord.LLtoLO(lat + 1, lon) - return math.atan2( north_posit.z - TargetVec3.z, north_posit.x - TargetVec3.x ) -end - - ---- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. --- @return #number DirectionRadians The direction in radians. -function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) - local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) - --DirectionRadians = DirectionRadians + self:GetNorthCorrectionRadians() - if DirectionRadians < 0 then - DirectionRadians = DirectionRadians + 2 * math.pi -- put dir in range of 0 to 2*pi ( the full circle ) - end - return DirectionRadians -end - ---- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return Dcs.DCSTypes#Distance Distance The distance in meters. -function POINT_VEC3:Get2DDistance( TargetPointVec3 ) - local TargetVec3 = TargetPointVec3:GetVec3() - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 -end - ---- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return Dcs.DCSTypes#Distance Distance The distance in meters. -function POINT_VEC3:Get3DDistance( TargetPointVec3 ) - local TargetVec3 = TargetPointVec3:GetVec3() - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 -end - ---- Provides a Bearing / Range string --- @param #POINT_VEC3 self --- @param #number AngleRadians The angle in randians --- @param #number Distance The distance --- @return #string The BR Text -function POINT_VEC3:ToStringBR( AngleRadians, Distance ) - - AngleRadians = UTILS.Round( UTILS.ToDegree( AngleRadians ), 0 ) - if self:IsMetric() then - Distance = UTILS.Round( Distance / 1000, 2 ) - else - Distance = UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) - end - - local s = string.format( '%03d', AngleRadians ) .. ' for ' .. Distance - - s = s .. self:GetAltitudeText() -- When the POINT is a VEC2, there will be no altitude shown. - - return s -end - ---- Provides a Bearing / Range string --- @param #POINT_VEC3 self --- @param #number AngleRadians The angle in randians --- @param #number Distance The distance --- @return #string The BR Text -function POINT_VEC3:ToStringLL( acc, DMS ) - - acc = acc or 3 - local lat, lon = coord.LOtoLL( self:GetVec3() ) - return UTILS.tostringLL(lat, lon, acc, DMS) -end - ---- Return the altitude text of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #string Altitude text. -function POINT_VEC3:GetAltitudeText() - if self:IsMetric() then - return ' at ' .. UTILS.Round( self:GetY(), 0 ) - else - return ' at ' .. UTILS.Round( UTILS.MetersToFeet( self:GetY() ), 0 ) - end -end - ---- Return a BR string from a POINT_VEC3 to the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return #string The BR text. -function POINT_VEC3:GetBRText( TargetPointVec3 ) - local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 ) - local AngleRadians = self:GetDirectionRadians( DirectionVec3 ) - local Distance = self:Get2DDistance( TargetPointVec3 ) - return self:ToStringBR( AngleRadians, Distance ) -end - ---- Sets the POINT_VEC3 metric or NM. --- @param #POINT_VEC3 self --- @param #boolean Metric true means metric, false means NM. -function POINT_VEC3:SetMetric( Metric ) - self.Metric = Metric -end - ---- Gets if the POINT_VEC3 is metric or NM. --- @param #POINT_VEC3 self --- @return #boolean Metric true means metric, false means NM. -function POINT_VEC3:IsMetric() - return self.Metric -end - ---- Add a Distance in meters from the POINT_VEC3 horizontal plane, with the given angle, and calculate the new POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. --- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. --- @return #POINT_VEC3 The new calculated POINT_VEC3. -function POINT_VEC3:Translate( Distance, Angle ) - local SX = self:GetX() - local SZ = self:GetZ() - local Radians = Angle / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TZ = Distance * math.sin( Radians ) + SZ - - return POINT_VEC3:New( TX, self:GetY(), TZ ) -end - - - ---- Build an air type route point. --- @param #POINT_VEC3 self --- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. --- @param #POINT_VEC3.RoutePointType Type The route point type. --- @param #POINT_VEC3.RoutePointAction Action The route point action. --- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. --- @param #boolean SpeedLocked true means the speed is locked. --- @return #table The route point. -function POINT_VEC3:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked ) - self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) - - local RoutePoint = {} - RoutePoint.x = self.x - RoutePoint.y = self.z - RoutePoint.alt = self.y - RoutePoint.alt_type = AltType - - RoutePoint.type = Type - RoutePoint.action = Action - - RoutePoint.speed = Speed / 3.6 - RoutePoint.speed_locked = true - --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] - - - RoutePoint.task = {} - RoutePoint.task.id = "ComboTask" - RoutePoint.task.params = {} - RoutePoint.task.params.tasks = {} - - - return RoutePoint -end - ---- Build an ground type route point. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Speed Speed Speed in km/h. --- @param #POINT_VEC3.RoutePointAction Formation The route point Formation. --- @return #table The route point. -function POINT_VEC3:RoutePointGround( Speed, Formation ) - self:F2( { Formation, Speed } ) - - local RoutePoint = {} - RoutePoint.x = self.x - RoutePoint.y = self.z - - RoutePoint.action = Formation or "" - - - RoutePoint.speed = Speed / 3.6 - RoutePoint.speed_locked = true - --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] - - - RoutePoint.task = {} - RoutePoint.task.id = "ComboTask" - RoutePoint.task.params = {} - RoutePoint.task.params.tasks = {} - - - return RoutePoint -end - ---- Creates an explosion at the point of a certain intensity. --- @param #POINT_VEC3 self --- @param #number ExplosionIntensity -function POINT_VEC3:Explosion( ExplosionIntensity ) - self:F2( { ExplosionIntensity } ) - trigger.action.explosion( self:GetVec3(), ExplosionIntensity ) -end - ---- Creates an illumination bomb at the point. --- @param #POINT_VEC3 self -function POINT_VEC3:IlluminationBomb() - self:F2() - trigger.action.illuminationBomb( self:GetVec3() ) -end - - ---- Smokes the point in a color. --- @param #POINT_VEC3 self --- @param Utilities.Utils#SMOKECOLOR SmokeColor -function POINT_VEC3:Smoke( SmokeColor ) - self:F2( { SmokeColor } ) - trigger.action.smoke( self:GetVec3(), SmokeColor ) -end - ---- Smoke the POINT_VEC3 Green. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeGreen() - self:F2() - self:Smoke( SMOKECOLOR.Green ) -end - ---- Smoke the POINT_VEC3 Red. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeRed() - self:F2() - self:Smoke( SMOKECOLOR.Red ) -end - ---- Smoke the POINT_VEC3 White. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeWhite() - self:F2() - self:Smoke( SMOKECOLOR.White ) -end - ---- Smoke the POINT_VEC3 Orange. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeOrange() - self:F2() - self:Smoke( SMOKECOLOR.Orange ) -end - ---- Smoke the POINT_VEC3 Blue. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeBlue() - self:F2() - self:Smoke( SMOKECOLOR.Blue ) -end - ---- Flares the point in a color. --- @param #POINT_VEC3 self --- @param Utilities.Utils#FLARECOLOR FlareColor --- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:Flare( FlareColor, Azimuth ) - self:F2( { FlareColor } ) - trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) -end - ---- Flare the POINT_VEC3 White. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareWhite( Azimuth ) - self:F2( Azimuth ) - self:Flare( FLARECOLOR.White, Azimuth ) -end - ---- Flare the POINT_VEC3 Yellow. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareYellow( Azimuth ) - self:F2( Azimuth ) - self:Flare( FLARECOLOR.Yellow, Azimuth ) -end - ---- Flare the POINT_VEC3 Green. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareGreen( Azimuth ) - self:F2( Azimuth ) - self:Flare( FLARECOLOR.Green, Azimuth ) -end - ---- Flare the POINT_VEC3 Red. --- @param #POINT_VEC3 self -function POINT_VEC3:FlareRed( Azimuth ) - self:F2( Azimuth ) - self:Flare( FLARECOLOR.Red, Azimuth ) -end - -end - -do -- POINT_VEC2 - - - ---- POINT_VEC2 constructor. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. --- @param Dcs.DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. --- @return Core.Point#POINT_VEC2 -function POINT_VEC2:New( x, y, LandHeightAdd ) - - local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) - self:F2( self ) - - return self -end - ---- Create a new POINT_VEC2 object from Vec2 coordinates. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. --- @return Core.Point#POINT_VEC2 self -function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) - - local LandHeight = land.getHeight( Vec2 ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( self ) - - return self -end - ---- Create a new POINT_VEC2 object from Vec3 coordinates. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Core.Point#POINT_VEC2 self -function POINT_VEC2:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, BASE:New() ) - local Vec2 = { x = Vec3.x, y = Vec3.z } - - local LandHeight = land.getHeight( Vec2 ) - - self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( self ) - - return self -end - ---- Return the x coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #number The x coodinate. -function POINT_VEC2:GetX() - return self.x -end - ---- Return the y coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #number The y coodinate. -function POINT_VEC2:GetY() - return self.z -end - ---- Return the altitude (height) of the land at the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #number The land altitude. -function POINT_VEC2:GetAlt() - return land.getHeight( { x = self.x, y = self.z } ) -end - ---- Return Return the Lat(itude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.x). --- @param #POINT_VEC2 self --- @return #number The x coodinate. -function POINT_VEC2:GetLat() - return self.x -end - ---- Return the Lon(gitude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.z). --- @param #POINT_VEC2 self --- @return #number The y coodinate. -function POINT_VEC2:GetLon() - return self.z -end - ---- Set the x coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number x The x coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:SetX( x ) - self.x = x - return self -end - ---- Set the y coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number y The y coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:SetY( y ) - self.z = y - return self -end - ---- Set the Lat(itude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.x). --- @param #POINT_VEC2 self --- @param #number x The x coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:SetLat( x ) - self.x = x - return self -end - ---- Set the altitude of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number Altitude The land altitude. If nothing (nil) is given, then the current land altitude is set. --- @return #POINT_VEC2 -function POINT_VEC2:SetAlt( Altitude ) - self.y = Altitude or land.getHeight( { x = self.x, y = self.z } ) - return self -end - ---- Set the Lon(gitude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.z). --- @param #POINT_VEC2 self --- @param #number y The y coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:SetLon( z ) - self.z = z - return self -end - ---- Add to the x coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number x The x coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:AddX( x ) - self.x = self.x + x - return self -end - ---- Add to the y coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number y The y coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:AddY( y ) - self.z = self.z + y - return self -end - ---- Add to the current land height an altitude. --- @param #POINT_VEC2 self --- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. --- @return #POINT_VEC2 -function POINT_VEC2:AddAlt( Altitude ) - self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 - return self -end - - - ---- Calculate the distance from a reference @{#POINT_VEC2}. --- @param #POINT_VEC2 self --- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. --- @return Dcs.DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. -function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) - self:F2( PointVec2Reference ) - - local Distance = ( ( PointVec2Reference:GetX() - self:GetX() ) ^ 2 + ( PointVec2Reference:GetY() - self:GetY() ) ^2 ) ^0.5 - - self:T2( Distance ) - return Distance -end - ---- Calculate the distance from a reference @{DCSTypes#Vec2}. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}. --- @return Dcs.DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters. -function POINT_VEC2:DistanceFromVec2( Vec2Reference ) - self:F2( Vec2Reference ) - - local Distance = ( ( Vec2Reference.x - self:GetX() ) ^ 2 + ( Vec2Reference.y - self:GetY() ) ^2 ) ^0.5 - - self:T2( Distance ) - return Distance -end - - ---- Return no text for the altitude of the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #string Empty string. -function POINT_VEC2:GetAltitudeText() - return '' -end - ---- Add a Distance in meters from the POINT_VEC2 orthonormal plane, with the given angle, and calculate the new POINT_VEC2. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. --- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. --- @return #POINT_VEC2 The new calculated POINT_VEC2. -function POINT_VEC2:Translate( Distance, Angle ) - local SX = self:GetX() - local SY = self:GetY() - local Radians = Angle / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TY = Distance * math.sin( Radians ) + SY - - return POINT_VEC2:New( TX, TY ) -end - -end - - ---- **Core** - MESSAGE class takes are of the **real-time notifications** and **messages to players** during a simulation. --- --- ![Banner Image](..\Presentations\MESSAGE\Dia1.JPG) --- --- === --- --- # 1) @{Message#MESSAGE} class, extends @{Base#BASE} --- --- Message System to display Messages to Clients, Coalitions or All. --- Messages are shown on the display panel for an amount of seconds, and will then disappear. --- Messages can contain a category which is indicating the category of the message. --- --- ## 1.1) MESSAGE construction --- --- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. --- To send messages, you need to use the To functions. --- --- ## 1.2) Send messages to an audience --- --- Messages are sent: --- --- * To a @{Client} using @{Message#MESSAGE.ToClient}(). --- * To a @{Group} using @{Message#MESSAGE.ToGroup}() --- * To a coalition using @{Message#MESSAGE.ToCoalition}(). --- * To the red coalition using @{Message#MESSAGE.ToRed}(). --- * To the blue coalition using @{Message#MESSAGE.ToBlue}(). --- * To all Players using @{Message#MESSAGE.ToAll}(). --- --- ## 1.3) Send conditionally to an audience --- --- Messages can be sent conditionally to an audience (when a condition is true): --- --- * To all players using @{Message#MESSAGE.ToAllIf}(). --- * To a coalition using @{Message#MESSAGE.ToCoalitionIf}(). --- --- --- @module Message - ---- The MESSAGE class --- @type MESSAGE --- @extends Core.Base#BASE -MESSAGE = { - ClassName = "MESSAGE", - MessageCategory = 0, - MessageID = 0, -} - - ---- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. --- @param self --- @param #string MessageText is the text of the Message. --- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. --- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". --- @return #MESSAGE --- @usage --- -- Create a series of new Messages. --- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". --- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) - - -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - if MessageCategory:sub(-1) ~= "\n" then - self.MessageCategory = MessageCategory .. ": " - else - self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" - end - else - self.MessageCategory = "" - end - - self.MessageDuration = MessageDuration or 5 - self.MessageTime = timer.getTime() - self.MessageText = MessageText - - self.MessageSent = false - self.MessageGroup = false - self.MessageCoalition = false - - return self -end - ---- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". --- @param #MESSAGE self --- @param Wrapper.Client#CLIENT Client is the Group of the Client. --- @return #MESSAGE --- @usage --- -- Send the 2 messages created with the @{New} method to the Client Group. --- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. --- ClientGroup = Group.getByName( "ClientGroup" ) --- --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) --- MessageClient1:ToClient( ClientGroup ) --- MessageClient2:ToClient( ClientGroup ) -function MESSAGE:ToClient( Client ) - self:F( Client ) - - if Client and Client:GetClientGroupID() then - - local ClientGroupID = Client:GetClientGroupID() - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to a Group. --- @param #MESSAGE self --- @param Wrapper.Group#GROUP Group is the Group. --- @return #MESSAGE -function MESSAGE:ToGroup( Group ) - self:F( Group.GroupName ) - - if Group then - - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end ---- Sends a MESSAGE to the Blue coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the BLUE coalition. --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageBLUE:ToBlue() -function MESSAGE:ToBlue() - self:F() - - self:ToCoalition( coalition.side.BLUE ) - - return self -end - ---- Sends a MESSAGE to the Red Coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToRed() -function MESSAGE:ToRed( ) - self:F() - - self:ToCoalition( coalition.side.RED ) - - return self -end - ---- Sends a MESSAGE to a Coalition. --- @param #MESSAGE self --- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToCoalition( coalition.side.RED ) -function MESSAGE:ToCoalition( CoalitionSide ) - self:F( CoalitionSide ) - - if CoalitionSide then - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to a Coalition if the given Condition is true. --- @param #MESSAGE self --- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @return #MESSAGE -function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) - self:F( CoalitionSide ) - - if Condition and Condition == true then - self:ToCoalition( CoalitionSide ) - end - - return self -end - ---- Sends a MESSAGE to all players. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created to all players. --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) --- MessageAll:ToAll() -function MESSAGE:ToAll() - self:F() - - self:ToCoalition( coalition.side.RED ) - self:ToCoalition( coalition.side.BLUE ) - - return self -end - - ---- Sends a MESSAGE to all players if the given Condition is true. --- @param #MESSAGE self --- @return #MESSAGE -function MESSAGE:ToAllIf( Condition ) - - if Condition and Condition == true then - self:ToCoalition( coalition.side.RED ) - self:ToCoalition( coalition.side.BLUE ) - end - - return self -end ---- **Core** - The **FSM** (**F**inite **S**tate **M**achine) class and derived **FSM\_** classes --- are design patterns allowing efficient (long-lasting) processes and workflows. --- --- ![Banner Image](..\Presentations\FSM\Dia1.JPG) --- --- === --- --- A FSM can only be in one of a finite number of states. --- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. --- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. --- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. --- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. --- --- The FSM class supports a **hierarchical implementation of a Finite State Machine**, --- that is, it allows to **embed existing FSM implementations in a master FSM**. --- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes. --- --- ![Workflow Example](..\Presentations\FSM\Dia2.JPG) --- --- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone, --- orders him to destroy x targets and account the results. --- Other examples of ready made FSM could be: --- --- * route a plane to a zone flown by a human --- * detect targets by an AI and report to humans --- * account for destroyed targets by human players --- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle --- * let an AI patrol a zone --- --- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, --- because **the goal of MOOSE is to simplify mission design complexity for mission building**. --- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. --- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, --- and tailored** by mission designers through **the implementation of Transition Handlers**. --- Each of these FSM implementation classes start either with: --- --- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. --- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. --- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. --- --- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections. --- --- ##__Dislaimer:__ --- The FSM class development is based on a finite state machine implementation made by Conroy Kyle. --- The state machine can be found on [github](https://github.com/kyleconroy/lua-state-machine) --- I've reworked this development (taken the concept), and created a **hierarchical state machine** out of it, embedded within the DCS simulator. --- Additionally, I've added extendability and created an API that allows seamless FSM implementation. --- --- === --- --- # 1) @{#FSM} class, extends @{Base#BASE} --- --- ![Transition Rules and Transition Handlers and Event Triggers](..\Presentations\FSM\Dia3.JPG) --- --- The FSM class is the base class of all FSM\_ derived classes. It implements the main functionality to define and execute Finite State Machines. --- The derived FSM\_ classes extend the Finite State Machine functionality to run a workflow process for a specific purpose or component. --- --- Finite State Machines have **Transition Rules**, **Transition Handlers** and **Event Triggers**. --- --- The **Transition Rules** define the "Process Flow Boundaries", that is, --- the path that can be followed hopping from state to state upon triggered events. --- If an event is triggered, and there is no valid path found for that event, --- an error will be raised and the FSM will stop functioning. --- --- The **Transition Handlers** are special methods that can be defined by the mission designer, following a defined syntax. --- If the FSM object finds a method of such a handler, then the method will be called by the FSM, passing specific parameters. --- The method can then define its own custom logic to implement the FSM workflow, and to conduct other actions. --- --- The **Event Triggers** are methods that are defined by the FSM, which the mission designer can use to implement the workflow. --- Most of the time, these Event Triggers are used within the Transition Handler methods, so that a workflow is created running through the state machine. --- --- As explained above, a FSM supports **Linear State Transitions** and **Hierarchical State Transitions**, and both can be mixed to make a comprehensive FSM implementation. --- The below documentation has a seperate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**. --- --- ## 1.1) FSM Linear Transitions --- --- Linear Transitions are Transition Rules allowing an FSM to transition from one or multiple possible **From** state(s) towards a **To** state upon a Triggered **Event**. --- The Lineair transition rule evaluation will always be done from the **current state** of the FSM. --- If no valid Transition Rule can be found in the FSM, the FSM will log an error and stop. --- --- ### 1.1.1) FSM Transition Rules --- --- The FSM has transition rules that it follows and validates, as it walks the process. --- These rules define when an FSM can transition from a specific state towards an other specific state upon a triggered event. --- --- The method @{#FSM.AddTransition}() specifies a new possible Transition Rule for the FSM. --- --- The initial state can be defined using the method @{#FSM.SetStartState}(). The default start state of an FSM is "None". --- --- Find below an example of a Linear Transition Rule definition for an FSM. --- --- local Fsm3Switch = FSM:New() -- #FsmDemo --- FsmSwitch:SetStartState( "Off" ) --- FsmSwitch:AddTransition( "Off", "SwitchOn", "On" ) --- FsmSwitch:AddTransition( "Off", "SwitchMiddle", "Middle" ) --- FsmSwitch:AddTransition( "On", "SwitchOff", "Off" ) --- FsmSwitch:AddTransition( "Middle", "SwitchOff", "Off" ) --- --- The above code snippet models a 3-way switch Linear Transition: --- --- * It can be switched **On** by triggering event **SwitchOn**. --- * It can be switched to the **Middle** position, by triggering event **SwitchMiddle**. --- * It can be switched **Off** by triggering event **SwitchOff**. --- * Note that once the Switch is **On** or **Middle**, it can only be switched **Off**. --- --- ### Some additional comments: --- --- Note that Linear Transition Rules **can be declared in a few variations**: --- --- * The From states can be **a table of strings**, indicating that the transition rule will be valid **if the current state** of the FSM will be **one of the given From states**. --- * The From state can be a **"*"**, indicating that **the transition rule will always be valid**, regardless of the current state of the FSM. --- --- The below code snippet shows how the two last lines can be rewritten and consensed. --- --- FsmSwitch:AddTransition( { "On", "Middle" }, "SwitchOff", "Off" ) --- --- ### 1.1.2) Transition Handling --- --- ![Transition Handlers](..\Presentations\FSM\Dia4.JPG) --- --- An FSM transitions in **4 moments** when an Event is being triggered and processed. --- The mission designer can define for each moment specific logic within methods implementations following a defined API syntax. --- These methods define the flow of the FSM process; because in those methods the FSM Internal Events will be triggered. --- --- * To handle **State** transition moments, create methods starting with OnLeave or OnEnter concatenated with the State name. --- * To handle **Event** transition moments, create methods starting with OnBefore or OnAfter concatenated with the Event name. --- --- **The OnLeave and OnBefore transition methods may return false, which will cancel the transition!** --- --- Transition Handler methods need to follow the above specified naming convention, but are also passed parameters from the FSM. --- These parameters are on the correct order: From, Event, To: --- --- * From = A string containing the From state. --- * Event = A string containing the Event name that was triggered. --- * To = A string containing the To state. --- --- On top, each of these methods can have a variable amount of parameters passed. See the example in section [1.1.3](#1.1.3\)-event-triggers). --- --- ### 1.1.3) Event Triggers --- --- ![Event Triggers](..\Presentations\FSM\Dia5.JPG) --- --- The FSM creates for each Event two **Event Trigger methods**. --- There are two modes how Events can be triggered, which is **synchronous** and **asynchronous**: --- --- * The method **FSM:Event()** triggers an Event that will be processed **synchronously** or **immediately**. --- * The method **FSM:__Event( __seconds__ )** triggers an Event that will be processed **asynchronously** over time, waiting __x seconds__. --- --- The destinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time. --- Processing will just continue. Synchronous Event Trigger methods are useful to change states of the FSM immediately, but may have a larger processing impact. --- --- The following example provides a little demonstration on the difference between synchronous and asynchronous Event Triggering. --- --- function FSM:OnAfterEvent( From, Event, To, Amount ) --- self:T( { Amount = Amount } ) --- end --- --- local Amount = 1 --- FSM:__Event( 5, Amount ) --- --- Amount = Amount + 1 --- FSM:Event( Text, Amount ) --- --- In this example, the **:OnAfterEvent**() Transition Handler implementation will get called when **Event** is being triggered. --- Before we go into more detail, let's look at the last 4 lines of the example. --- The last line triggers synchronously the **Event**, and passes Amount as a parameter. --- The 3rd last line of the example triggers asynchronously **Event**. --- Event will be processed after 5 seconds, and Amount is given as a parameter. --- --- The output of this little code fragment will be: --- --- * Amount = 2 --- * Amount = 2 --- --- Because ... When Event was asynchronously processed after 5 seconds, Amount was set to 2. So be careful when processing and passing values and objects in asynchronous processing! --- --- ### 1.1.4) Linear Transition Example --- --- This example is fully implemented in the MOOSE test mission on GITHUB: [FSM-100 - Transition Explanation](https://github.com/FlightControl-Master/MOOSE/blob/master/Moose%20Test%20Missions/FSM%20-%20Finite%20State%20Machine/FSM-100%20-%20Transition%20Explanation/FSM-100%20-%20Transition%20Explanation.lua) --- --- It models a unit standing still near Batumi, and flaring every 5 seconds while switching between a Green flare and a Red flare. --- The purpose of this example is not to show how exciting flaring is, but it demonstrates how a Linear Transition FSM can be build. --- Have a look at the source code. The source code is also further explained below in this section. --- --- The example creates a new FsmDemo object from class FSM. --- It will set the start state of FsmDemo to state **Green**. --- Two Linear Transition Rules are created, where upon the event **Switch**, --- the FsmDemo will transition from state **Green** to **Red** and from **Red** back to **Green**. --- --- ![Transition Example](..\Presentations\FSM\Dia6.JPG) --- --- local FsmDemo = FSM:New() -- #FsmDemo --- FsmDemo:SetStartState( "Green" ) --- FsmDemo:AddTransition( "Green", "Switch", "Red" ) --- FsmDemo:AddTransition( "Red", "Switch", "Green" ) --- --- In the above example, the FsmDemo could flare every 5 seconds a Green or a Red flare into the air. --- The next code implements this through the event handling method **OnAfterSwitch**. --- --- ![Transition Flow](..\Presentations\FSM\Dia7.JPG) --- --- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) --- self:T( { From, Event, To, FsmUnit } ) --- --- if From == "Green" then --- FsmUnit:Flare(FLARECOLOR.Green) --- else --- if From == "Red" then --- FsmUnit:Flare(FLARECOLOR.Red) --- end --- end --- self:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. --- end --- --- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the first Switch event to happen in 5 seconds. --- --- The OnAfterSwitch implements a loop. The last line of the code fragment triggers the Switch Event within 5 seconds. --- Upon the event execution (after 5 seconds), the OnAfterSwitch method is called of FsmDemo (cfr. the double point notation!!! ":"). --- The OnAfterSwitch method receives from the FSM the 3 transition parameter details ( From, Event, To ), --- and one additional parameter that was given when the event was triggered, which is in this case the Unit that is used within OnSwitchAfter. --- --- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) --- --- For debugging reasons the received parameters are traced within the DCS.log. --- --- self:T( { From, Event, To, FsmUnit } ) --- --- The method will check if the From state received is either "Green" or "Red" and will flare the respective color from the FsmUnit. --- --- if From == "Green" then --- FsmUnit:Flare(FLARECOLOR.Green) --- else --- if From == "Red" then --- FsmUnit:Flare(FLARECOLOR.Red) --- end --- end --- --- It is important that the Switch event is again triggered, otherwise, the FsmDemo would stop working after having the first Event being handled. --- --- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. --- --- The below code fragment extends the FsmDemo, demonstrating multiple **From states declared as a table**, adding a **Linear Transition Rule**. --- The new event **Stop** will cancel the Switching process. --- The transition for event Stop can be executed if the current state of the FSM is either "Red" or "Green". --- --- local FsmDemo = FSM:New() -- #FsmDemo --- FsmDemo:SetStartState( "Green" ) --- FsmDemo:AddTransition( "Green", "Switch", "Red" ) --- FsmDemo:AddTransition( "Red", "Switch", "Green" ) --- FsmDemo:AddTransition( { "Red", "Green" }, "Stop", "Stopped" ) --- --- The transition for event Stop can also be simplified, as any current state of the FSM is valid. --- --- FsmDemo:AddTransition( "*", "Stop", "Stopped" ) --- --- So... When FsmDemo:Stop() is being triggered, the state of FsmDemo will transition from Red or Green to Stopped. --- And there is no transition handling method defined for that transition, thus, no new event is being triggered causing the FsmDemo process flow to halt. --- --- ## 1.5) FSM Hierarchical Transitions --- --- Hierarchical Transitions allow to re-use readily available and implemented FSMs. --- This becomes in very useful for mission building, where mission designers build complex processes and workflows, --- combining smaller FSMs to one single FSM. --- --- The FSM can embed **Sub-FSMs** that will execute and return **multiple possible Return (End) States**. --- Depending upon **which state is returned**, the main FSM can continue the flow **triggering specific events**. --- --- The method @{#FSM.AddProcess}() adds a new Sub-FSM to the FSM. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- YYYY-MM-DD: CLASS:**NewFunction**( Params ) replaces CLASS:_OldFunction_( Params ) --- YYYY-MM-DD: CLASS:**NewFunction( Params )** added --- --- Hereby the change log: --- --- * 2016-12-18: Released. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * [**Pikey**](https://forums.eagle.ru/member.php?u=62835): Review of documentation & advice for improvements. --- --- ### Authors: --- --- * [**FlightControl**](https://forums.eagle.ru/member.php?u=89536): Design & Programming & documentation. --- --- @module Fsm - -do -- FSM - - --- FSM class - -- @type FSM - -- @extends Core.Base#BASE - FSM = { - ClassName = "FSM", - } - - --- Creates a new FSM object. - -- @param #FSM self - -- @return #FSM - function FSM:New( FsmT ) - - -- Inherits from BASE - self = BASE:Inherit( self, BASE:New() ) - - self.options = options or {} - self.options.subs = self.options.subs or {} - self.current = self.options.initial or 'none' - self.Events = {} - self.subs = {} - self.endstates = {} - - self.Scores = {} - - self._StartState = "none" - self._Transitions = {} - self._Processes = {} - self._EndStates = {} - self._Scores = {} - self._EventSchedules = {} - - self.CallScheduler = SCHEDULER:New( self ) - - - return self - end - - - --- Sets the start state of the FSM. - -- @param #FSM self - -- @param #string State A string defining the start state. - function FSM:SetStartState( State ) - - self._StartState = State - self.current = State - end - - - --- Returns the start state of the FSM. - -- @param #FSM self - -- @return #string A string containing the start state. - function FSM:GetStartState() - - return self._StartState or {} - end - - --- Add a new transition rule to the FSM. - -- A transition rule defines when and if the FSM can transition from a state towards another state upon a triggered event. - -- @param #FSM self - -- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states. - -- @param #string Event The Event name. - -- @param #string To The To state. - function FSM:AddTransition( From, Event, To ) - - local Transition = {} - Transition.From = From - Transition.Event = Event - Transition.To = To - - self:T( Transition ) - - self._Transitions[Transition] = Transition - self:_eventmap( self.Events, Transition ) - end - - - --- Returns a table of the transition rules defined within the FSM. - -- @return #table - function FSM:GetTransitions() - - return self._Transitions or {} - end - - --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Controllable} by the task. - -- @param #FSM self - -- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states. - -- @param #string Event The Event name. - -- @param Core.Fsm#FSM_PROCESS Process An sub-process FSM. - -- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM. - -- @return Core.Fsm#FSM_PROCESS The SubFSM. - function FSM:AddProcess( From, Event, Process, ReturnEvents ) - self:T( { From, Event, Process, ReturnEvents } ) - - local Sub = {} - Sub.From = From - Sub.Event = Event - Sub.fsm = Process - Sub.StartEvent = "Start" - Sub.ReturnEvents = ReturnEvents - - self._Processes[Sub] = Sub - - self:_submap( self.subs, Sub, nil ) - - self:AddTransition( From, Event, From ) - - return Process - end - - - --- Returns a table of the SubFSM rules defined within the FSM. - -- @return #table - function FSM:GetProcesses() - - return self._Processes or {} - end - - function FSM:GetProcess( From, Event ) - - for ProcessID, Process in pairs( self:GetProcesses() ) do - if Process.From == From and Process.Event == Event then - self:T( Process ) - return Process.fsm - end - end - - error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) - end - - --- Adds an End state. - function FSM:AddEndState( State ) - - self._EndStates[State] = State - self.endstates[State] = State - end - - --- Returns the End states. - function FSM:GetEndStates() - - return self._EndStates or {} - end - - - --- Adds a score for the FSM to be achieved. - -- @param #FSM self - -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). - -- @param #string ScoreText is a text describing the score that is given according the status. - -- @param #number Score is a number providing the score of the status. - -- @return #FSM self - function FSM:AddScore( State, ScoreText, Score ) - self:F2( { State, ScoreText, Score } ) - - self._Scores[State] = self._Scores[State] or {} - self._Scores[State].ScoreText = ScoreText - self._Scores[State].Score = Score - - return self - end - - --- Adds a score for the FSM_PROCESS to be achieved. - -- @param #FSM self - -- @param #string From is the From State of the main process. - -- @param #string Event is the Event of the main process. - -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). - -- @param #string ScoreText is a text describing the score that is given according the status. - -- @param #number Score is a number providing the score of the status. - -- @return #FSM self - function FSM:AddScoreProcess( From, Event, State, ScoreText, Score ) - self:F2( { Event, State, ScoreText, Score } ) - - local Process = self:GetProcess( From, Event ) - - self:T( { Process = Process._Name, Scores = Process._Scores, State = State, ScoreText = ScoreText, Score = Score } ) - Process._Scores[State] = Process._Scores[State] or {} - Process._Scores[State].ScoreText = ScoreText - Process._Scores[State].Score = Score - - return Process - end - - --- Returns a table with the scores defined. - function FSM:GetScores() - - return self._Scores or {} - end - - --- Returns a table with the Subs defined. - function FSM:GetSubs() - - return self.options.subs - end - - - function FSM:LoadCallBacks( CallBackTable ) - - for name, callback in pairs( CallBackTable or {} ) do - self[name] = callback - end - - end - - function FSM:_eventmap( Events, EventStructure ) - - local Event = EventStructure.Event - local __Event = "__" .. EventStructure.Event - self[Event] = self[Event] or self:_create_transition(Event) - self[__Event] = self[__Event] or self:_delayed_transition(Event) - self:T( "Added methods: " .. Event .. ", " .. __Event ) - Events[Event] = self.Events[Event] or { map = {} } - self:_add_to_map( Events[Event].map, EventStructure ) - - end - - function FSM:_submap( subs, sub, name ) - self:F( { sub = sub, name = name } ) - subs[sub.From] = subs[sub.From] or {} - subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} - - -- Make the reference table weak. - -- setmetatable( subs[sub.From][sub.Event], { __mode = "k" } ) - - subs[sub.From][sub.Event][sub] = {} - subs[sub.From][sub.Event][sub].fsm = sub.fsm - subs[sub.From][sub.Event][sub].StartEvent = sub.StartEvent - subs[sub.From][sub.Event][sub].ReturnEvents = sub.ReturnEvents or {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. - subs[sub.From][sub.Event][sub].name = name - subs[sub.From][sub.Event][sub].fsmparent = self - end - - - function FSM:_call_handler( handler, params, EventName ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - if self[handler] then - self:T( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) - return Value - end - end - - function FSM._handler( self, EventName, ... ) - - local Can, to = self:can( EventName ) - - if to == "*" then - to = self.current - end - - if Can then - local from = self.current - local params = { from, EventName, to, ... } - - if self.Controllable then - self:T( "FSM Transition for " .. self.Controllable.ControllableName .. " :" .. self.current .. " --> " .. EventName .. " --> " .. to ) - else - self:T( "FSM Transition:" .. self.current .. " --> " .. EventName .. " --> " .. to ) - end - - if ( self:_call_handler("onbefore" .. EventName, params, EventName ) == false ) - or ( self:_call_handler("OnBefore" .. EventName, params, EventName ) == false ) - or ( self:_call_handler("onleave" .. from, params, EventName ) == false ) - or ( self:_call_handler("OnLeave" .. from, params, EventName ) == false ) then - self:T( "Cancel Transition" ) - return false - end - - self.current = to - - local execute = true - - local subtable = self:_gosub( from, EventName ) - for _, sub in pairs( subtable ) do - --if sub.nextevent then - -- self:F2( "nextevent = " .. sub.nextevent ) - -- self[sub.nextevent]( self ) - --end - self:T( "calling sub start event: " .. sub.StartEvent ) - sub.fsm.fsmparent = self - sub.fsm.ReturnEvents = sub.ReturnEvents - sub.fsm[sub.StartEvent]( sub.fsm ) - execute = false - end - - local fsmparent, Event = self:_isendstate( to ) - if fsmparent and Event then - self:F2( { "end state: ", fsmparent, Event } ) - self:_call_handler("onenter" .. to, params, EventName ) - self:_call_handler("OnEnter" .. to, params, EventName ) - self:_call_handler("onafter" .. EventName, params, EventName ) - self:_call_handler("OnAfter" .. EventName, params, EventName ) - self:_call_handler("onstatechange", params, EventName ) - fsmparent[Event]( fsmparent ) - execute = false - end - - if execute then - -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! - --if from ~= to then - self:_call_handler("onenter" .. to, params, EventName ) - self:_call_handler("OnEnter" .. to, params, EventName ) - --end - - self:_call_handler("onafter" .. EventName, params, EventName ) - self:_call_handler("OnAfter" .. EventName, params, EventName ) - - self:_call_handler("onstatechange", params, EventName ) - end - else - self:T( "Cannot execute transition." ) - self:T( { From = self.current, Event = EventName, To = to, Can = Can } ) - end - - return nil - end - - function FSM:_delayed_transition( EventName ) - return function( self, DelaySeconds, ... ) - self:T2( "Delayed Event: " .. EventName ) - local CallID = 0 - if DelaySeconds ~= nil then - if DelaySeconds < 0 then -- Only call the event ONCE! - DelaySeconds = math.abs( DelaySeconds ) - if not self._EventSchedules[EventName] then - CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) - self._EventSchedules[EventName] = CallID - else - -- reschedule - end - else - CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) - end - else - error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." ) - end - self:T2( { CallID = CallID } ) - end - end - - function FSM:_create_transition( EventName ) - return function( self, ... ) return self._handler( self, EventName , ... ) end - end - - function FSM:_gosub( ParentFrom, ParentEvent ) - local fsmtable = {} - if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then - self:T( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) - return self.subs[ParentFrom][ParentEvent] - else - return {} - end - end - - function FSM:_isendstate( Current ) - local FSMParent = self.fsmparent - if FSMParent and self.endstates[Current] then - self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) - FSMParent.current = Current - local ParentFrom = FSMParent.current - self:T( ParentFrom ) - self:T( self.ReturnEvents ) - local Event = self.ReturnEvents[Current] - self:T( { ParentFrom, Event, self.ReturnEvents } ) - if Event then - return FSMParent, Event - else - self:T( { "Could not find parent event name for state ", ParentFrom } ) - end - end - - return nil - end - - function FSM:_add_to_map( Map, Event ) - self:F3( { Map, Event } ) - if type(Event.From) == 'string' then - Map[Event.From] = Event.To - else - for _, From in ipairs(Event.From) do - Map[From] = Event.To - end - end - self:T3( { Map, Event } ) - end - - function FSM:GetState() - return self.current - end - - - function FSM:Is( State ) - return self.current == State - end - - function FSM:is(state) - return self.current == state - end - - function FSM:can(e) - local Event = self.Events[e] - self:F3( { self.current, Event } ) - local To = Event and Event.map[self.current] or Event.map['*'] - return To ~= nil, To - end - - function FSM:cannot(e) - return not self:can(e) - end - -end - -do -- FSM_CONTROLLABLE - - --- FSM_CONTROLLABLE class - -- @type FSM_CONTROLLABLE - -- @field Wrapper.Controllable#CONTROLLABLE Controllable - -- @extends Core.Fsm#FSM - FSM_CONTROLLABLE = { - ClassName = "FSM_CONTROLLABLE", - } - - --- Creates a new FSM_CONTROLLABLE object. - -- @param #FSM_CONTROLLABLE self - -- @param #table FSMT Finite State Machine Table - -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. - -- @return #FSM_CONTROLLABLE - function FSM_CONTROLLABLE:New( FSMT, Controllable ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New( FSMT ) ) -- Core.Fsm#FSM_CONTROLLABLE - - if Controllable then - self:SetControllable( Controllable ) - end - - self:AddTransition( "*", "Stop", "Stopped" ) - - --- OnBefore Transition Handler for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] OnBeforeStop - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] OnAfterStop - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] Stop - -- @param #FSM_CONTROLLABLE self - - --- Asynchronous Event Trigger for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] __Stop - -- @param #FSM_CONTROLLABLE self - -- @param #number Delay The delay in seconds. - - --- OnLeave Transition Handler for State Stopped. - -- @function [parent=#FSM_CONTROLLABLE] OnLeaveStopped - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Stopped. - -- @function [parent=#FSM_CONTROLLABLE] OnEnterStopped - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - return self - end - - --- OnAfter Transition Handler for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] OnAfterStop - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To) - - -- Clear all pending schedules - self.CallScheduler:Clear() - end - - --- Sets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE FSMControllable - -- @return #FSM_CONTROLLABLE - function FSM_CONTROLLABLE:SetControllable( FSMControllable ) - self:F( FSMControllable ) - self.Controllable = FSMControllable - end - - --- Gets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. - -- @param #FSM_CONTROLLABLE self - -- @return Wrapper.Controllable#CONTROLLABLE - function FSM_CONTROLLABLE:GetControllable() - return self.Controllable - end - - function FSM_CONTROLLABLE:_call_handler( handler, params, EventName ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - - if self[handler] then - self:F3( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler ) - return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) - end - end - -end - -do -- FSM_PROCESS - - --- FSM_PROCESS class - -- @type FSM_PROCESS - -- @field Tasking.Task#TASK Task - -- @extends Core.Fsm#FSM_CONTROLLABLE - FSM_PROCESS = { - ClassName = "FSM_PROCESS", - } - - --- Creates a new FSM_PROCESS object. - -- @param #FSM_PROCESS self - -- @return #FSM_PROCESS - function FSM_PROCESS:New( Controllable, Task ) - - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS - - self:F( Controllable, Task ) - - self:Assign( Controllable, Task ) - - return self - end - - function FSM_PROCESS:Init( FsmProcess ) - self:T( "No Initialisation" ) - end - - function FSM_PROCESS:_call_handler( handler, params, EventName ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in FSM_PROCESS call handler:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - - if self[handler] then - self:F3( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler ) - return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) - end - end - - --- Creates a new FSM_PROCESS object based on this FSM_PROCESS. - -- @param #FSM_PROCESS self - -- @return #FSM_PROCESS - function FSM_PROCESS:Copy( Controllable, Task ) - self:T( { self:GetClassNameAndID() } ) - - - local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS - - NewFsm:Assign( Controllable, Task ) - - -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS - NewFsm:Init( self ) - - -- Set Start State - NewFsm:SetStartState( self:GetStartState() ) - - -- Copy Transitions - for TransitionID, Transition in pairs( self:GetTransitions() ) do - NewFsm:AddTransition( Transition.From, Transition.Event, Transition.To ) - end - - -- Copy Processes - for ProcessID, Process in pairs( self:GetProcesses() ) do - self:E( { Process} ) - local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) - end - - -- Copy End States - for EndStateID, EndState in pairs( self:GetEndStates() ) do - self:T( EndState ) - NewFsm:AddEndState( EndState ) - end - - -- Copy the score tables - for ScoreID, Score in pairs( self:GetScores() ) do - self:T( Score ) - NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) - end - - return NewFsm - end - - --- Removes an FSM_PROCESS object. - -- @param #FSM_PROCESS self - -- @return #FSM_PROCESS - function FSM_PROCESS:Remove() - self:T( { self:GetClassNameAndID() } ) - - -- Copy Processes - for ProcessID, Process in pairs( self:GetProcesses() ) do - self:E( { Process} ) - Process.fsm:Remove() - Process.fsm = nil - end - - return self - end - - --- Sets the task of the process. - -- @param #FSM_PROCESS self - -- @param Tasking.Task#TASK Task - -- @return #FSM_PROCESS - function FSM_PROCESS:SetTask( Task ) - - self.Task = Task - - return self - end - - --- Gets the task of the process. - -- @param #FSM_PROCESS self - -- @return Tasking.Task#TASK - function FSM_PROCESS:GetTask() - - return self.Task - end - - --- Gets the mission of the process. - -- @param #FSM_PROCESS self - -- @return Tasking.Mission#MISSION - function FSM_PROCESS:GetMission() - - return self.Task.Mission - end - - --- Gets the mission of the process. - -- @param #FSM_PROCESS self - -- @return Tasking.CommandCenter#COMMANDCENTER - function FSM_PROCESS:GetCommandCenter() - - return self:GetTask():GetMission():GetCommandCenter() - end - --- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. - - --- Send a message of the @{Task} to the Group of the Unit. --- @param #FSM_PROCESS self -function FSM_PROCESS:Message( Message ) - self:F( { Message = Message } ) - - local CC = self:GetCommandCenter() - local TaskGroup = self.Controllable:GetGroup() - - local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit - PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets. - local Callsign = self.Controllable:GetCallsign() - local Prefix = Callsign and " @ " .. Callsign .. PlayerName or "" - - Message = Prefix .. ": " .. Message - CC:MessageToGroup( Message, TaskGroup ) -end - - - - - --- Assign the process to a @{Unit} and activate the process. - -- @param #FSM_PROCESS self - -- @param Task.Tasking#TASK Task - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @return #FSM_PROCESS self - function FSM_PROCESS:Assign( ProcessUnit, Task ) - self:T( { Task, ProcessUnit } ) - - self:SetControllable( ProcessUnit ) - self:SetTask( Task ) - - --self.ProcessGroup = ProcessUnit:GetGroup() - - return self - end - - function FSM_PROCESS:onenterAssigned( ProcessUnit ) - self:T( "Assign" ) - - self.Task:Assign() - end - - function FSM_PROCESS:onenterFailed( ProcessUnit ) - self:T( "Failed" ) - - self.Task:Fail() - end - - function FSM_PROCESS:onenterSuccess( ProcessUnit ) - self:T( "Success" ) - - self.Task:Success() - end - - --- StateMachine callback function for a FSM_PROCESS - -- @param #FSM_PROCESS self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function FSM_PROCESS:onstatechange( ProcessUnit, From, Event, To, Dummy ) - self:T( { ProcessUnit, From, Event, To, Dummy, self:IsTrace() } ) - - if self:IsTrace() then - MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() - end - - self:T( self._Scores[To] ) - -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... - if self._Scores[To] then - - local Task = self.Task - local Scoring = Task:GetScoring() - if Scoring then - Scoring:_AddMissionTaskScore( Task.Mission, ProcessUnit, self._Scores[To].ScoreText, self._Scores[To].Score ) - end - end - end - -end - -do -- FSM_TASK - - --- FSM_TASK class - -- @type FSM_TASK - -- @field Tasking.Task#TASK Task - -- @extends Core.Fsm#FSM - FSM_TASK = { - ClassName = "FSM_TASK", - } - - --- Creates a new FSM_TASK object. - -- @param #FSM_TASK self - -- @param #table FSMT - -- @param Tasking.Task#TASK Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #FSM_TASK - function FSM_TASK:New( FSMT ) - - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( FSMT ) ) -- Core.Fsm#FSM_TASK - - self["onstatechange"] = self.OnStateChange - - return self - end - - function FSM_TASK:_call_handler( handler, params, EventName ) - if self[handler] then - self:T( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - return self[handler]( self, unpack( params ) ) - end - end - -end -- FSM_TASK - -do -- FSM_SET - - --- FSM_SET class - -- @type FSM_SET - -- @field Core.Set#SET_BASE Set - -- @extends Core.Fsm#FSM - FSM_SET = { - ClassName = "FSM_SET", - } - - --- Creates a new FSM_SET object. - -- @param #FSM_SET self - -- @param #table FSMT Finite State Machine Table - -- @param Set_SET_BASE FSMSet (optional) The Set object that the FSM_SET governs. - -- @return #FSM_SET - function FSM_SET:New( FSMSet ) - - -- Inherits from BASE - self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_SET - - if FSMSet then - self:Set( FSMSet ) - end - - return self - end - - --- Sets the SET_BASE object that the FSM_SET governs. - -- @param #FSM_SET self - -- @param Core.Set#SET_BASE FSMSet - -- @return #FSM_SET - function FSM_SET:Set( FSMSet ) - self:F( FSMSet ) - self.Set = FSMSet - end - - --- Gets the SET_BASE object that the FSM_SET governs. - -- @param #FSM_SET self - -- @return Core.Set#SET_BASE - function FSM_SET:Get() - return self.Controllable - end - - function FSM_SET:_call_handler( handler, params, EventName ) - if self[handler] then - self:T( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - return self[handler]( self, self.Set, unpack( params ) ) - end - end - -end -- FSM_SET - ---- This module contains the OBJECT class. --- --- 1) @{Object#OBJECT} class, extends @{Base#BASE} --- =========================================================== --- The @{Object#OBJECT} class is a wrapper class to handle the DCS Object objects: --- --- * Support all DCS Object APIs. --- * Enhance with Object specific APIs not in the DCS Object API set. --- * Manage the "state" of the DCS Object. --- --- 1.1) OBJECT constructor: --- ------------------------------ --- The OBJECT class provides the following functions to construct a OBJECT instance: --- --- * @{Object#OBJECT.New}(): Create a OBJECT instance. --- --- 1.2) OBJECT methods: --- -------------------------- --- The following methods can be used to identify an Object object: --- --- * @{Object#OBJECT.GetID}(): Returns the ID of the Object object. --- --- === --- --- @module Object - ---- The OBJECT class --- @type OBJECT --- @extends Core.Base#BASE --- @field #string ObjectName The name of the Object. -OBJECT = { - ClassName = "OBJECT", - ObjectName = "", -} - ---- A DCSObject --- @type DCSObject --- @field id_ The ID of the controllable in DCS - ---- Create a new OBJECT from a DCSObject --- @param #OBJECT self --- @param Dcs.DCSWrapper.Object#Object ObjectName The Object name --- @return #OBJECT self -function OBJECT:New( ObjectName, Test ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( ObjectName ) - self.ObjectName = ObjectName - - return self -end - - ---- Returns the unit's unique identifier. --- @param Wrapper.Object#OBJECT self --- @return Dcs.DCSWrapper.Object#Object.ID ObjectID --- @return #nil The DCS Object is not existing or alive. -function OBJECT:GetID() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - local ObjectID = DCSObject:getID() - return ObjectID - end - - return nil -end - ---- Destroys the OBJECT. --- @param #OBJECT self --- @return #nil The DCS Unit is not existing or alive. -function OBJECT:Destroy() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - - DCSObject:destroy() - end - - return nil -end - - - - ---- This module contains the IDENTIFIABLE class. --- --- 1) @{#IDENTIFIABLE} class, extends @{Object#OBJECT} --- =============================================================== --- The @{#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: --- --- * Support all DCS Identifiable APIs. --- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. --- * Manage the "state" of the DCS Identifiable. --- --- 1.1) IDENTIFIABLE constructor: --- ------------------------------ --- The IDENTIFIABLE class provides the following functions to construct a IDENTIFIABLE instance: --- --- * @{#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. --- --- 1.2) IDENTIFIABLE methods: --- -------------------------- --- The following methods can be used to identify an identifiable object: --- --- * @{#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. --- * @{#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. --- * @{#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. --- * @{#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. --- * @{#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. --- * @{#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. --- --- --- === --- --- @module Identifiable - ---- The IDENTIFIABLE class --- @type IDENTIFIABLE --- @extends Wrapper.Object#OBJECT --- @field #string IdentifiableName The name of the identifiable. -IDENTIFIABLE = { - ClassName = "IDENTIFIABLE", - IdentifiableName = "", -} - -local _CategoryName = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicoper", - [Unit.Category.GROUND_UNIT] = "Ground Identifiable", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - ---- Create a new IDENTIFIABLE from a DCSIdentifiable --- @param #IDENTIFIABLE self --- @param Dcs.DCSWrapper.Identifiable#Identifiable IdentifiableName The DCS Identifiable name --- @return #IDENTIFIABLE self -function IDENTIFIABLE:New( IdentifiableName ) - local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) - self:F2( IdentifiableName ) - self.IdentifiableName = IdentifiableName - return self -end - ---- Returns if the Identifiable is alive. --- @param #IDENTIFIABLE self --- @return #boolean true if Identifiable is alive. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:IsAlive() - self:F3( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableIsAlive = DCSIdentifiable:isExist() - return IdentifiableIsAlive - end - - return false -end - - - - ---- Returns DCS Identifiable object name. --- The function provides access to non-activated objects too. --- @param #IDENTIFIABLE self --- @return #string The name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableName = self.IdentifiableName - return IdentifiableName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns the type name of the DCS Identifiable. --- @param #IDENTIFIABLE self --- @return #string The type name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetTypeName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableTypeName = DCSIdentifiable:getTypeName() - self:T3( IdentifiableTypeName ) - return IdentifiableTypeName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns category of the DCS Identifiable. --- @param #IDENTIFIABLE self --- @return Dcs.DCSWrapper.Object#Object.Category The category ID -function IDENTIFIABLE:GetCategory() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - local ObjectCategory = DCSObject:getCategory() - self:T3( ObjectCategory ) - return ObjectCategory - end - - return nil -end - - ---- Returns the DCS Identifiable category name as defined within the DCS Identifiable Descriptor. --- @param #IDENTIFIABLE self --- @return #string The DCS Identifiable Category Name -function IDENTIFIABLE:GetCategoryName() - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCategoryName = _CategoryName[ self:GetDesc().category ] - return IdentifiableCategoryName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns coalition of the Identifiable. --- @param #IDENTIFIABLE self --- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The side of the coalition. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCoalition() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCoalition = DCSIdentifiable:getCoalition() - self:T3( IdentifiableCoalition ) - return IdentifiableCoalition - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns country of the Identifiable. --- @param #IDENTIFIABLE self --- @return Dcs.DCScountry#country.id The country identifier. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCountry() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCountry = DCSIdentifiable:getCountry() - self:T3( IdentifiableCountry ) - return IdentifiableCountry - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - - ---- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. --- @param #IDENTIFIABLE self --- @return Dcs.DCSWrapper.Identifiable#Identifiable.Desc The Identifiable descriptor. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetDesc() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableDesc = DCSIdentifiable:getDesc() - self:T2( IdentifiableDesc ) - return IdentifiableDesc - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Gets the CallSign of the IDENTIFIABLE, which is a blank by default. --- @param #IDENTIFIABLE self --- @return #string The CallSign of the IDENTIFIABLE. -function IDENTIFIABLE:GetCallsign() - return '' -end - - -function IDENTIFIABLE:GetThreatLevel() - - return 0, "Scenery" -end ---- This module contains the POSITIONABLE class. --- --- 1) @{Positionable#POSITIONABLE} class, extends @{Identifiable#IDENTIFIABLE} --- =========================================================== --- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the POSITIONABLE objects: --- --- * Support all DCS APIs. --- * Enhance with POSITIONABLE specific APIs not in the DCS API set. --- * Manage the "state" of the POSITIONABLE. --- --- 1.1) POSITIONABLE constructor: --- ------------------------------ --- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: --- --- * @{Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. --- --- 1.2) POSITIONABLE methods: --- -------------------------- --- The following methods can be used to identify an measurable object: --- --- * @{Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. --- * @{Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. --- --- === --- --- @module Positionable - ---- The POSITIONABLE class --- @type POSITIONABLE --- @extends Wrapper.Identifiable#IDENTIFIABLE --- @field #string PositionableName The name of the measurable. -POSITIONABLE = { - ClassName = "POSITIONABLE", - PositionableName = "", -} - ---- A DCSPositionable --- @type DCSPositionable --- @field id_ The ID of the controllable in DCS - ---- Create a new POSITIONABLE from a DCSPositionable --- @param #POSITIONABLE self --- @param Dcs.DCSWrapper.Positionable#Positionable PositionableName The POSITIONABLE name --- @return #POSITIONABLE self -function POSITIONABLE:New( PositionableName ) - local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) - - self.PositionableName = PositionableName - return self -end - ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPositionVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePosition = DCSPositionable:getPosition().p - self:T3( PositionablePosition ) - return PositionablePosition - end - - return nil -end - ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionableVec2 = {} - PositionableVec2.x = PositionableVec3.x - PositionableVec2.y = PositionableVec3.z - - self:T2( PositionableVec2 ) - return PositionableVec2 - end - - return nil -end - ---- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPointVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) - - self:T2( PositionablePointVec2 ) - return PositionablePointVec2 - end - - return nil -end - ---- Returns a POINT_VEC3 object indicating the point in 3D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPointVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = self:GetPositionVec3() - - local PositionablePointVec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) - - self:T2( PositionablePointVec3 ) - return PositionablePointVec3 - end - - return nil -end - - ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetRandomVec3( Radius ) - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p - local PositionableRandomVec3 = {} - local angle = math.random() * math.pi*2; - PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; - PositionableRandomVec3.y = PositionablePointVec3.y - PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; - - self:T3( PositionableRandomVec3 ) - return PositionableRandomVec3 - end - - return nil -end - ---- Returns the @{DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - self:T3( PositionableVec3 ) - return PositionableVec3 - end - - return nil -end - ---- Returns the altitude of the POSITIONABLE. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Distance The altitude of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetAltitude() - self:F2() - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --Dcs.DCSTypes#Vec3 - return PositionablePointVec3.y - end - - return nil -end - ---- Returns if the Positionable is located above a runway. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #boolean true if Positionable is above a runway. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:IsAboveRunway() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local Vec2 = self:GetVec2() - local SurfaceType = land.getSurfaceType( Vec2 ) - local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY - - self:T2( IsAboveRunway ) - return IsAboveRunway - end - - return nil -end - - - ---- Returns the POSITIONABLE heading in degrees. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The POSTIONABLE heading -function POSITIONABLE:GetHeading() - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local PositionablePosition = DCSPositionable:getPosition() - if PositionablePosition then - local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) - if PositionableHeading < 0 then - PositionableHeading = PositionableHeading + 2 * math.pi - end - PositionableHeading = PositionableHeading * 180 / math.pi - self:T2( PositionableHeading ) - return PositionableHeading - end - end - - return nil -end - - ---- Returns true if the POSITIONABLE is in the air. --- Polymorphic, is overridden in GROUP and UNIT. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #boolean true if in the air. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:InAir() - self:F2( self.PositionableName ) - - return nil -end - - ---- Returns the POSITIONABLE velocity vector. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The velocity vector --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocity() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVelocityVec3 = DCSPositionable:getVelocity() - self:T3( PositionableVelocityVec3 ) - return PositionableVelocityVec3 - end - - return nil -end - ---- Returns the POSITIONABLE velocity in km/h. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The velocity in km/h --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocityKMH() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local VelocityVec3 = self:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - self:T3( Velocity ) - return Velocity - end - - return nil -end - ---- Returns a message with the callsign embedded (if there is one). --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. --- @return Core.Message#MESSAGE -function POSITIONABLE:GetMessage( Message, Duration, Name ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - Name = Name or self:GetTypeName() - return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. Name .. ")" ) - end - - return nil -end - ---- Send a message to all coalitions. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToAll( Message, Duration, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToAll() - end - - return nil -end - ---- Send a message to a coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTYpes#Duration Duration The duration of the message. --- @param Dcs.DCScoalition#coalition MessageCoalition The Coalition receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToCoalition( MessageCoalition ) - end - - return nil -end - - ---- Send a message to the red coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTYpes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToRed( Message, Duration, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToRed() - end - - return nil -end - ---- Send a message to the blue coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToBlue( Message, Duration, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToBlue() - end - - return nil -end - ---- Send a message to a client. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param Wrapper.Client#CLIENT Client The client object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToClient( Client ) - end - - return nil -end - ---- Send a message to a @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - if DCSObject:isExist() then - self:GetMessage( Message, Duration, Name ):ToGroup( MessageGroup ) - end - end - - return nil -end - ---- Send a message to the players in the @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:Message( Message, Duration, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToGroup( self ) - end - - return nil -end - - - - - ---- This module contains the CONTROLLABLE class. --- --- 1) @{Controllable#CONTROLLABLE} class, extends @{Positionable#POSITIONABLE} --- =========================================================== --- The @{Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: --- --- * Support all DCS Controllable APIs. --- * Enhance with Controllable specific APIs not in the DCS Controllable API set. --- * Handle local Controllable Controller. --- * Manage the "state" of the DCS Controllable. --- --- 1.1) CONTROLLABLE constructor --- ----------------------------- --- The CONTROLLABLE class provides the following functions to construct a CONTROLLABLE instance: --- --- * @{#CONTROLLABLE.New}(): Create a CONTROLLABLE instance. --- --- 1.2) CONTROLLABLE task methods --- ------------------------------ --- Several controllable task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#SetTask} method to assign the task to the CONTROLLABLE. --- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. --- Each task description where applicable indicates for which controllable category the task is valid. --- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- --- ### 1.2.1) Assigned task methods --- --- Assigned task methods make the controllable execute the task where the location of the (possible) targets of the task are known before being detected. --- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- --- Find below a list of the **assigned task** methods: --- --- * @{#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Controllable. --- * @{#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.TaskBombing}: (AIR) Delivering weapon at the point on the ground. --- * @{#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. --- * @{#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire some or all ammunition at a VEC2 point. --- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. --- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. --- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. --- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. --- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. --- --- ### 1.2.2) EnRoute task methods --- --- EnRoute tasks require the targets of the task need to be detected by the controllable (using its sensors) before the task can be executed: --- --- * @{#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{#CONTROLLABLE.EnRouteTaskEngageControllable}: (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskFAC_EngageControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- --- ### 1.2.3) Preparation task methods --- --- There are certain task methods that allow to tailor the task behaviour: --- --- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- --- ### 1.2.4) Obtain the mission from controllable templates --- --- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another: --- --- * @{#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- 1.3) CONTROLLABLE Command methods --- -------------------------- --- Controllable **command methods** prepare the execution of commands using the @{#CONTROLLABLE.SetCommand} method: --- --- * @{#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- --- 1.4) CONTROLLABLE Option methods --- ------------------------- --- Controllable **Option methods** change the behaviour of the Controllable while being alive. --- --- ### 1.4.1) Rule of Engagement: --- --- * @{#CONTROLLABLE.OptionROEWeaponFree} --- * @{#CONTROLLABLE.OptionROEOpenFire} --- * @{#CONTROLLABLE.OptionROEReturnFire} --- * @{#CONTROLLABLE.OptionROEEvadeFire} --- --- To check whether an ROE option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} --- --- ### 1.4.2) Rule on thread: --- --- * @{#CONTROLLABLE.OptionROTNoReaction} --- * @{#CONTROLLABLE.OptionROTPassiveDefense} --- * @{#CONTROLLABLE.OptionROTEvadeFire} --- * @{#CONTROLLABLE.OptionROTVertical} --- --- To test whether an ROT option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{#CONTROLLABLE.OptionROTVerticalPossible} --- --- === --- --- @module Controllable - ---- The CONTROLLABLE class --- @type CONTROLLABLE --- @extends Wrapper.Positionable#POSITIONABLE --- @field Dcs.DCSWrapper.Controllable#Controllable DCSControllable The DCS controllable class. --- @field #string ControllableName The name of the controllable. -CONTROLLABLE = { - ClassName = "CONTROLLABLE", - ControllableName = "", - WayPointFunctions = {}, -} - ---- Create a new CONTROLLABLE from a DCSControllable --- @param #CONTROLLABLE self --- @param Dcs.DCSWrapper.Controllable#Controllable ControllableName The DCS Controllable name --- @return #CONTROLLABLE self -function CONTROLLABLE:New( ControllableName ) - local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) - self:F2( ControllableName ) - self.ControllableName = ControllableName - - self.TaskScheduler = SCHEDULER:New( self ) - return self -end - --- DCS Controllable methods support. - ---- Get the controller for the CONTROLLABLE. --- @param #CONTROLLABLE self --- @return Dcs.DCSController#Controller -function CONTROLLABLE:_GetController() - self:F2( { self.ControllableName } ) - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllableController = DCSControllable:getController() - self:T3( ControllableController ) - return ControllableController - end - - return nil -end - --- Get methods - ---- Returns the UNITs wrappers of the DCS Units of the Controllable (default is a GROUP). --- @param #CONTROLLABLE self --- @return #list The UNITs wrappers. -function CONTROLLABLE:GetUnits() - self:F2( { self.ControllableName } ) - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local DCSUnits = DCSControllable:getUnits() - local Units = {} - for Index, UnitData in pairs( DCSUnits ) do - Units[#Units+1] = UNIT:Find( UnitData ) - end - self:T3( Units ) - return Units - end - - return nil -end - - ---- Returns the health. Dead controllables have health <= 1.0. --- @param #CONTROLLABLE self --- @return #number The controllable health value (unit or group average). --- @return #nil The controllable is not existing or alive. -function CONTROLLABLE:GetLife() - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local UnitLife = 0 - local Units = self:GetUnits() - if #Units == 1 then - local Unit = Units[1] -- Wrapper.Unit#UNIT - UnitLife = Unit:GetLife() - else - local UnitLifeTotal = 0 - for UnitID, Unit in pairs( Units ) do - local Unit = Unit -- Wrapper.Unit#UNIT - UnitLifeTotal = UnitLifeTotal + Unit:GetLife() - end - UnitLife = UnitLifeTotal / #Units - end - return UnitLife - end - - return nil -end - - - --- Tasks - ---- Clear all tasks from the controllable. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE -function CONTROLLABLE:ClearTasks() - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:resetTask() - return self - end - - return nil -end - - ---- Popping current Task from the controllable. --- @param #CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:PopCurrentTask() - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:popTask() - return self - end - - return nil -end - ---- Pushing Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:PushTask( DCSTask, WaitTime ) - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller:pushTask( DCSTask ) - - if WaitTime then - self.TaskScheduler:Schedule( Controller, Controller.pushTask, { DCSTask }, WaitTime ) - else - Controller:pushTask( DCSTask ) - end - - return self - end - - return nil -end - ---- Clearing the Task Queue and Setting the Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:SetTask( DCSTask, WaitTime ) - self:F2( { DCSTask } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local Controller = self:_GetController() - self:T3( Controller ) - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller.setTask( Controller, DCSTask ) - - if not WaitTime then - Controller:setTask( DCSTask ) - else - self.TaskScheduler:Schedule( Controller, Controller.setTask, { DCSTask }, WaitTime ) - end - - return self - end - - return nil -end - - ---- Return a condition section for a controlled task. --- @param #CONTROLLABLE self --- @param Dcs.DCSTime#Time time --- @param #string userFlag --- @param #boolean userFlagValue --- @param #string condition --- @param Dcs.DCSTime#Time duration --- @param #number lastWayPoint --- return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) - self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) - - local DCSStopCondition = {} - DCSStopCondition.time = time - DCSStopCondition.userFlag = userFlag - DCSStopCondition.userFlagValue = userFlagValue - DCSStopCondition.condition = condition - DCSStopCondition.duration = duration - DCSStopCondition.lastWayPoint = lastWayPoint - - self:T3( { DCSStopCondition } ) - return DCSStopCondition -end - ---- Return a Controlled Task taking a Task and a TaskCondition. --- @param #CONTROLLABLE self --- @param Dcs.DCSTasking.Task#Task DCSTask --- @param #DCSStopCondition DCSStopCondition --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) - self:F2( { DCSTask, DCSStopCondition } ) - - local DCSTaskControlled - - DCSTaskControlled = { - id = 'ControlledTask', - params = { - task = DCSTask, - stopCondition = DCSStopCondition - } - } - - self:T3( { DCSTaskControlled } ) - return DCSTaskControlled -end - ---- Return a Combo Task taking an array of Tasks. --- @param #CONTROLLABLE self --- @param Dcs.DCSTasking.Task#TaskArray DCSTasks Array of @{DCSTasking.Task#Task} --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskCombo( DCSTasks ) - self:F2( { DCSTasks } ) - - local DCSTaskCombo - - DCSTaskCombo = { - id = 'ComboTask', - params = { - tasks = DCSTasks - } - } - - for TaskID, Task in ipairs( DCSTasks ) do - self:E( Task ) - end - - self:T3( { DCSTaskCombo } ) - return DCSTaskCombo -end - ---- Return a WrappedAction Task taking a Command. --- @param #CONTROLLABLE self --- @param Dcs.DCSCommand#Command DCSCommand --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) - self:F2( { DCSCommand } ) - - local DCSTaskWrappedAction - - DCSTaskWrappedAction = { - id = "WrappedAction", - enabled = true, - number = Index, - auto = false, - params = { - action = DCSCommand, - }, - } - - self:T3( { DCSTaskWrappedAction } ) - return DCSTaskWrappedAction -end - ---- Executes a command action --- @param #CONTROLLABLE self --- @param Dcs.DCSCommand#Command DCSCommand --- @return #CONTROLLABLE self -function CONTROLLABLE:SetCommand( DCSCommand ) - self:F2( DCSCommand ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:setCommand( DCSCommand ) - return self - end - - return nil -end - ---- Perform a switch waypoint command --- @param #CONTROLLABLE self --- @param #number FromWayPoint --- @param #number ToWayPoint --- @return Dcs.DCSTasking.Task#Task --- @usage --- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. --- HeliGroup = GROUP:FindByName( "Helicopter" ) --- --- --- Route the helicopter back to the FARP after 60 seconds. --- -- We use the SCHEDULER class to do this. --- SCHEDULER:New( nil, --- function( HeliGroup ) --- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) --- HeliGroup:SetCommand( CommandRTB ) --- end, { HeliGroup }, 90 --- ) -function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) - self:F2( { FromWayPoint, ToWayPoint } ) - - local CommandSwitchWayPoint = { - id = 'SwitchWaypoint', - params = { - fromWaypointIndex = FromWayPoint, - goToWaypointIndex = ToWayPoint, - }, - } - - self:T3( { CommandSwitchWayPoint } ) - return CommandSwitchWayPoint -end - ---- Perform stop route command --- @param #CONTROLLABLE self --- @param #boolean StopRoute --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:CommandStopRoute( StopRoute, Index ) - self:F2( { StopRoute, Index } ) - - local CommandStopRoute = { - id = 'StopRoute', - params = { - value = StopRoute, - }, - } - - self:T3( { CommandStopRoute } ) - return CommandStopRoute -end - - --- TASKS FOR AIR CONTROLLABLES - - ---- (AIR) Attack a Controllable. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- AttackGroup = { - -- id = 'AttackGroup', - -- params = { - -- groupId = Group.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'AttackGroup', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR) Attack the Unit. --- @param #CONTROLLABLE self --- @param Wrapper.Unit#UNIT AttackUnit The UNIT. --- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskAttackUnit( AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack } ) - - local DCSTask - DCSTask = { - id = 'AttackUnit', - params = { - unitId = AttackUnit:GetID(), - groupAttack = GroupAttack or false, - visible = Visible or false, - expend = WeaponExpend or "Auto", - directionEnabled = Direction and true or false, - direction = Direction, - altitudeEnabled = Altitude and true or false, - altitude = Altitude or 30, - attackQtyLimit = AttackQty and true or false, - attackQty = AttackQty, - weaponType = 1073741822, - }, - } - - self:E( DCSTask ) - - return DCSTask -end - - ---- (AIR) Delivering weapon at the point on the ground. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) Desired quantity of passes. The parameter is not the same in AttackGroup and AttackUnit tasks. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- Bombing = { --- id = 'Bombing', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'Bombing', - params = { - point = Vec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point to hold the position. --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) - self:F2( { self.ControllableName, Point, Altitude, Speed } ) - - -- pattern = enum AI.Task.OribtPattern, - -- point = Vec2, - -- point2 = Vec2, - -- speed = Distance, - -- altitude = Distance - - local LandHeight = land.getHeight( Point ) - - self:T3( { LandHeight } ) - - local DCSTask = { id = 'Orbit', - params = { pattern = AI.Task.OrbitPattern.CIRCLE, - point = Point, - speed = Speed, - altitude = Altitude + LandHeight - } - } - - - -- local AITask = { id = 'ControlledTask', - -- params = { task = { id = 'Orbit', - -- params = { pattern = AI.Task.OrbitPattern.CIRCLE, - -- point = Point, - -- speed = Speed, - -- altitude = Altitude + LandHeight - -- } - -- }, - -- stopCondition = { duration = Duration - -- } - -- } - -- } - -- ) - - return DCSTask -end - ---- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- @param #CONTROLLABLE self --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) - self:F2( { self.ControllableName, Altitude, Speed } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllablePoint = self:GetVec2() - return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) - end - - return nil -end - - - ---- (AIR) Hold position at the current position of the first unit of the controllable. --- @param #CONTROLLABLE self --- @param #number Duration The maximum duration in seconds to hold the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskHoldPosition() - self:F2( { self.ControllableName } ) - - return self:TaskOrbitCircle( 30, 10 ) -end - - - - ---- (AIR) Attacking the map object (building, structure, e.t.c). --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- AttackMapObject = { --- id = 'AttackMapObject', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'AttackMapObject', - params = { - point = Vec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Delivering weapon on the runway. --- @param #CONTROLLABLE self --- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- BombingRunway = { --- id = 'BombingRunway', --- params = { --- runwayId = AirdromeId, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'BombingRunway', - params = { - point = Airbase:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Refueling from the nearest tanker. No parameters. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskRefueling() - self:F2( { self.ControllableName } ) - --- Refueling = { --- id = 'Refueling', --- params = {} --- } - - local DCSTask - DCSTask = { id = 'Refueling', - params = { - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR HELICOPTER) Landing at the ground. For helicopters only. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) - self:F2( { self.ControllableName, Point, Duration } ) - --- Land = { --- id= 'Land', --- params = { --- point = Vec2, --- durationFlag = boolean, --- duration = Time --- } --- } - - local DCSTask - if Duration and Duration > 0 then - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = true, - duration = Duration, - }, - } - else - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = false, - }, - } - end - - self:T3( DCSTask ) - return DCSTask -end - ---- (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- @param #CONTROLLABLE self --- @param Core.Zone#ZONE Zone The zone where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) - self:F2( { self.ControllableName, Zone, Duration, RandomPoint } ) - - local Point - if RandomPoint then - Point = Zone:GetRandomVec2() - else - Point = Zone:GetVec2() - end - - local DCSTask = self:TaskLandAtVec2( Point, Duration ) - - self:T3( DCSTask ) - return DCSTask -end - - - ---- (AIR) Following another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- If another controllable is on land the unit / controllable will orbit around. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE FollowControllable The controllable to be followed. --- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) - self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) - --- Follow = { --- id = 'Follow', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number --- } --- } - - local LastWaypointIndexFlag = false - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { - id = 'Follow', - params = { - groupId = FollowControllable:GetID(), - pos = Vec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Escort another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- The unit / controllable will also protect that controllable from threats of specified types. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. --- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) - self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) - --- Escort = { --- id = 'Escort', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number, --- engagementDistMax = Distance, --- targetTypes = array of AttributeName, --- } --- } - - local LastWaypointIndexFlag = false - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { id = 'Escort', - params = { - groupId = FollowControllable:GetID(), - pos = Vec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex, - engagementDistMax = EngagementDistance, - targetTypes = TargetTypes, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - --- GROUND TASKS - ---- (GROUND) Fire at a VEC2 point until ammunition is finished. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 The point to fire at. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone to deploy the fire at. --- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted). --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount ) - self:F2( { self.ControllableName, Vec2, Radius, AmmoCount } ) - - -- FireAtPoint = { - -- id = 'FireAtPoint', - -- params = { - -- point = Vec2, - -- radius = Distance, - -- expendQty = number, - -- expendQtyEnabled = boolean, - -- } - -- } - - local DCSTask - DCSTask = { id = 'FireAtPoint', - params = { - point = Vec2, - radius = Radius, - expendQty = 100, -- dummy value - expendQtyEnabled = false, - } - } - - if AmmoCount then - DCSTask.params.expendQty = AmmoCount - DCSTask.params.expendQtyEnabled = true - end - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Hold ground controllable from moving. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskHold() - self:F2( { self.ControllableName } ) - --- Hold = { --- id = 'Hold', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Hold', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) - --- FAC_AttackGroup = { --- id = 'FAC_AttackGroup', --- params = { --- groupId = Group.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_AttackGroup', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - --- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES - ---- (AIR) Engaging targets of defined types. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) - self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) - --- EngageTargets ={ --- id = 'EngageTargets', --- params = { --- maxDist = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargets', - params = { - maxDist = Distance, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Engaging a targets of defined types at circle-shaped zone. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the zone. --- @param Dcs.DCSTypes#Distance Radius Radius of the zone. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) - self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) - --- EngageTargetsInZone = { --- id = 'EngageTargetsInZone', --- params = { --- point = Vec2, --- zoneRadius = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargetsInZone', - params = { - point = Vec2, - zoneRadius = Radius, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- EngageControllable = { - -- id = 'EngageControllable ', - -- params = { - -- groupId = Group.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- priority = number, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'EngageControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - priority = Priority, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Search and attack the Unit. --- @param #CONTROLLABLE self --- @param Wrapper.Unit#UNIT EngageUnit The UNIT. --- @param #number Priority (optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired altitude to perform the unit engagement. --- @param #boolean Visible (optional) Unit must be visible. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageUnit( EngageUnit, Priority, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack ) - self:F2( { self.ControllableName, EngageUnit, Priority, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack } ) - - -- EngageUnit = { - -- id = 'EngageUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- priority = number, - -- } - -- } - - local DCSTask - DCSTask = { id = 'EngageUnit', - params = { - unitId = EngageUnit:GetID(), - priority = Priority or 1, - groupAttack = GroupAttack or false, - visible = Visible or false, - expend = WeaponExpend or "Auto", - directionEnabled = Direction and true or false, - direction = Direction, - altitudeEnabled = Altitude and true or false, - altitude = Altitude, - attackQtyLimit = AttackQty and true or false, - attackQty = AttackQty, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskAWACS( ) - self:F2( { self.ControllableName } ) - --- AWACS = { --- id = 'AWACS', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'AWACS', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskTanker( ) - self:F2( { self.ControllableName } ) - --- Tanker = { --- id = 'Tanker', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Tanker', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for ground units/controllables - ---- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEWR( ) - self:F2( { self.ControllableName } ) - --- EWR = { --- id = 'EWR', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'EWR', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for airborne and ground units/controllables - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) - --- FAC_EngageControllable = { --- id = 'FAC_EngageControllable', --- params = { --- groupId = Group.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean, --- priority = number, --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_EngageControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - priority = Priority, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Distance Radius The maximal distance from the FAC to a target. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) - self:F2( { self.ControllableName, Radius, Priority } ) - --- FAC = { --- id = 'FAC', --- params = { --- radius = Distance, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'FAC', - params = { - radius = Radius, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - - ---- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to wait. --- @param #number Duration The duration in seconds to wait. --- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. --- @return Dcs.DCSTasking.Task#Task The DCS task structure -function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) - self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) - - local DCSTask - DCSTask = { id = 'Embarking', - params = { x = Point.x, - y = Point.y, - duration = Duration, - controllablesForEmbarking = { EmbarkingControllable.ControllableID }, - durationFlag = true, - distributionFlag = false, - distribution = {}, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Embark to a Transport landed at a location. - ---- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to wait. --- @param #number Radius The radius of the embarking zone around the Point. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) - self:F2( { self.ControllableName, Point, Radius } ) - - local DCSTask --Dcs.DCSTasking.Task#Task - DCSTask = { id = 'EmbarkToTransport', - params = { x = Point.x, - y = Point.y, - zoneRadius = Radius, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR + GROUND) Return a mission task from a mission template. --- @param #CONTROLLABLE self --- @param #table TaskMission A table containing the mission task. --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskMission( TaskMission ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { TaskMission, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- Return a Misson task to follow a given route defined by Points. --- @param #CONTROLLABLE self --- @param #table Points A table of route points. --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskRoute( Points ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { route = { points = Points, }, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR + GROUND) Make the Controllable move to fly to a given point. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:RouteToVec2( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllablePoint = self:GetUnit( 1 ):GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.y - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - ---- (AIR + GROUND) Make the Controllable move to a given point. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:RouteToVec3( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllableVec3 = self:GetUnit( 1 ):GetVec3() - - local PointFrom = {} - PointFrom.x = ControllableVec3.x - PointFrom.y = ControllableVec3.z - PointFrom.alt = ControllableVec3.y - PointFrom.alt_type = "BARO" - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.z - PointTo.alt = Point.y - PointTo.alt_type = "BARO" - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - - - ---- Make the controllable to follow a given route. --- @param #CONTROLLABLE self --- @param #table GoPoints A table of Route Points. --- @return #CONTROLLABLE self -function CONTROLLABLE:Route( GoPoints ) - self:F2( GoPoints ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Points = routines.utils.deepCopy( GoPoints ) - local MissionTask = { id = 'Mission', params = { route = { points = Points, }, }, } - local Controller = self:_GetController() - --Controller.setTask( Controller, MissionTask ) - self.TaskScheduler:Schedule( Controller, Controller.setTask, { MissionTask }, 1 ) - return self - end - - return nil -end - - - ---- (AIR + GROUND) Route the controllable to a given zone. --- The controllable final destination point can be randomized. --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Core.Zone#ZONE Zone The zone where to route to. --- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. --- @param #number Speed The speed. --- @param Base#FORMATION Formation The formation string. -function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) - self:F2( Zone ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = Formation or "Cone" - PointFrom.speed = 20 / 1.6 - - - local PointTo = {} - local ZonePoint - - if Randomize then - ZonePoint = Zone:GetRandomVec2() - else - ZonePoint = Zone:GetVec2() - end - - PointTo.x = ZonePoint.x - PointTo.y = ZonePoint.y - PointTo.type = "Turning Point" - - if Formation then - PointTo.action = Formation - else - PointTo.action = "Cone" - end - - if Speed then - PointTo.speed = Speed - else - PointTo.speed = 20 / 1.6 - end - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self - end - - return nil -end - ---- (AIR) Return the Controllable to an @{Airbase#AIRBASE} --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Wrapper.Airbase#AIRBASE ReturnAirbase The @{Airbase#AIRBASE} to return to. --- @param #number Speed (optional) The speed. --- @return #string The route -function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) - self:F2( { ReturnAirbase, Speed } ) - --- Example --- [4] = --- { --- ["alt"] = 45, --- ["type"] = "Land", --- ["action"] = "Landing", --- ["alt_type"] = "BARO", --- ["formation_template"] = "", --- ["properties"] = --- { --- ["vnav"] = 1, --- ["scale"] = 0, --- ["angle"] = 0, --- ["vangle"] = 0, --- ["steer"] = 2, --- }, -- end of ["properties"] --- ["ETA"] = 527.81058817743, --- ["airdromeId"] = 12, --- ["y"] = 243127.2973737, --- ["x"] = -5406.2803440839, --- ["name"] = "DictKey_WptName_53", --- ["speed"] = 138.88888888889, --- ["ETA_locked"] = false, --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] --- ["speed_locked"] = true, --- }, -- end of [4] - - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - local ControllableVelocity = self:GetMaxVelocity() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = ControllableVelocity - - - local PointTo = {} - local AirbasePoint = ReturnAirbase:GetVec2() - - PointTo.x = AirbasePoint.x - PointTo.y = AirbasePoint.y - PointTo.type = "Land" - PointTo.action = "Landing" - PointTo.airdromeId = ReturnAirbase:GetID()-- Airdrome ID - self:T(PointTo.airdromeId) - --PointTo.alt = 0 - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - local Route = { points = Points, } - - return Route - end - - return nil -end - --- Commands - ---- Do Script command --- @param #CONTROLLABLE self --- @param #string DoScript --- @return #DCSCommand -function CONTROLLABLE:CommandDoScript( DoScript ) - - local DCSDoScript = { - id = "Script", - params = { - command = DoScript, - }, - } - - self:T3( DCSDoScript ) - return DCSDoScript -end - - ---- Return the mission template of the controllable. --- @param #CONTROLLABLE self --- @return #table The MissionTemplate --- TODO: Rework the method how to retrieve a template ... -function CONTROLLABLE:GetTaskMission() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template ) -end - ---- Return the mission route of the controllable. --- @param #CONTROLLABLE self --- @return #table The mission route defined by points. -function CONTROLLABLE:GetTaskRoute() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) -end - ---- Return the route of a controllable by using the @{Database#DATABASE} class. --- @param #CONTROLLABLE self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Controllable - local ControllableName = string.match( self:GetName(), ".*#" ) - if ControllableName then - ControllableName = ControllableName:sub( 1, -2 ) - else - ControllableName = self:GetName() - end - - self:T3( { ControllableName } ) - - local Template = _DATABASE.Templates.Controllables[ControllableName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Controllable : " .. ControllableName ) - end - - return nil -end - - ---- Return the detected targets of the controllable. --- The optional parametes specify the detection methods that can be applied. --- If no detection method is given, the detection will use all the available methods by default. --- @param Wrapper.Controllable#CONTROLLABLE self --- @param #boolean DetectVisual (optional) --- @param #boolean DetectOptical (optional) --- @param #boolean DetectRadar (optional) --- @param #boolean DetectIRST (optional) --- @param #boolean DetectRWR (optional) --- @param #boolean DetectDLINK (optional) --- @return #table DetectedTargets -function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local DetectionVisual = ( DetectVisual and DetectVisual == true ) and Controller.Detection.VISUAL or nil - local DetectionOptical = ( DetectOptical and DetectOptical == true ) and Controller.Detection.OPTICAL or nil - local DetectionRadar = ( DetectRadar and DetectRadar == true ) and Controller.Detection.RADAR or nil - local DetectionIRST = ( DetectIRST and DetectIRST == true ) and Controller.Detection.IRST or nil - local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil - local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil - - - return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) - end - - return nil -end - -function CONTROLLABLE:IsTargetDetected( DCSObject ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - = self:_GetController().isTargetDetected( self:_GetController(), DCSObject, - Controller.Detection.VISUAL, - Controller.Detection.OPTIC, - Controller.Detection.RADAR, - Controller.Detection.IRST, - Controller.Detection.RWR, - Controller.Detection.DLINK - ) - return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - end - - return nil -end - --- Options - ---- Can the CONTROLLABLE hold their weapons? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEHoldFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Holding weapons. --- @param Wrapper.Controllable#CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:OptionROEHoldFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack returning on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEReturnFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Return fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEReturnFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack designated targets? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEOpenFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Openfire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEOpenFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack targets of opportunity? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEWeaponFreePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Weapon free. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEWeaponFree() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE ignore enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTNoReactionPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- No evasion on enemy threats. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTNoReaction() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade using passive defenses? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTPassiveDefensePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Evasion passive defense. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTPassiveDefense() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTEvadeFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTEvadeFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on fire using vertical manoeuvres? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTVerticalPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire using vertical manoeuvres. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTVertical() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) - end - - return self - end - - return nil -end - ---- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. --- Use the method @{Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. --- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. --- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! --- @param #CONTROLLABLE self --- @param #table WayPoints If WayPoints is given, then use the route. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointInitialize( WayPoints ) - self:F( { WayPoints } ) - - if WayPoints then - self.WayPoints = WayPoints - else - self.WayPoints = self:GetTaskRoute() - end - - return self -end - ---- Get the current WayPoints set with the WayPoint functions( Note that the WayPoints can be nil, although there ARE waypoints). --- @param #CONTROLLABLE self --- @return #table WayPoints If WayPoints is given, then return the WayPoints structure. -function CONTROLLABLE:GetWayPoints() - self:F( ) - - if self.WayPoints then - return self.WayPoints - end - - return nil -end - ---- Registers a waypoint function that will be executed when the controllable moves over the WayPoint. --- @param #CONTROLLABLE self --- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1! --- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. --- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) - self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) - - table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex ) - self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPoint, WayPointIndex, WayPointFunction, arg ) - return self -end - - -function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionArguments ) - self:F2( { WayPoint, WayPointIndex, FunctionString, FunctionArguments } ) - - local DCSTask - - local DCSScript = {} - DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " - - if FunctionArguments and #FunctionArguments > 0 then - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")" - else - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" - end - - DCSTask = self:TaskWrappedAction( - self:CommandDoScript( - table.concat( DCSScript ) - ), WayPointIndex - ) - - self:T3( DCSTask ) - - return DCSTask - -end - ---- Executes the WayPoint plan. --- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. --- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! --- @param #CONTROLLABLE self --- @param #number WayPoint The WayPoint from where to execute the mission. --- @param #number WaitTime The amount seconds to wait before initiating the mission. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) - self:F( { WayPoint, WaitTime } ) - - if not WayPoint then - WayPoint = 1 - end - - -- When starting the mission from a certain point, the TaskPoints need to be deleted before the given WayPoint. - for TaskPointID = 1, WayPoint - 1 do - table.remove( self.WayPoints, 1 ) - end - - self:T3( self.WayPoints ) - - self:SetTask( self:TaskRoute( self.WayPoints ), WaitTime ) - - return self -end - --- Message APIs--- This module contains the GROUP class. --- --- 1) @{Group#GROUP} class, extends @{Controllable#CONTROLLABLE} --- ============================================================= --- The @{Group#GROUP} class is a wrapper class to handle the DCS Group objects: --- --- * Support all DCS Group APIs. --- * Enhance with Group specific APIs not in the DCS Group API set. --- * Handle local Group Controller. --- * Manage the "state" of the DCS Group. --- --- **IMPORTANT: ONE SHOULD NEVER SANATIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).** --- --- 1.1) GROUP reference methods --- ----------------------- --- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class). --- --- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Group or the DCS GroupName. --- --- Another thing to know is that GROUP objects do not "contain" the DCS Group object. --- The GROUP methods will reference the DCS Group object by name when it is needed during API execution. --- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and log an exception in the DCS.log file. --- --- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance: --- --- * @{#GROUP.Find}(): Find a GROUP instance from the _DATABASE object using a DCS Group object. --- * @{#GROUP.FindByName}(): Find a GROUP instance from the _DATABASE object using a DCS Group name. --- --- ## 1.2) GROUP task methods --- --- A GROUP is a @{Controllable}. See the @{Controllable} task methods section for a description of the task methods. --- --- ### 1.2.4) Obtain the mission from group templates --- --- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: --- --- * @{Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- ## 1.3) GROUP Command methods --- --- A GROUP is a @{Controllable}. See the @{Controllable} command methods section for a description of the command methods. --- --- ## 1.4) GROUP option methods --- --- A GROUP is a @{Controllable}. See the @{Controllable} option methods section for a description of the option methods. --- --- ## 1.5) GROUP Zone validation methods --- --- The group can be validated whether it is completely, partly or not within a @{Zone}. --- Use the following Zone validation methods on the group: --- --- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Zone}. --- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. --- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. --- --- The zone can be of any @{Zone} class derived from @{Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. --- --- ## 1.6) GROUP AI methods --- --- A GROUP has AI methods to control the AI activation. --- --- * @{#GROUP.SetAIOnOff}(): Turns the GROUP AI On or Off. --- * @{#GROUP.SetAIOn}(): Turns the GROUP AI On. --- * @{#GROUP.SetAIOff}(): Turns the GROUP AI Off. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-07: GROUP:**HandleEvent( Event, EventFunction )** added. --- 2017-03-07: GROUP:**UnHandleEvent( Event )** added. --- --- 2017-01-24: GROUP:**SetAIOnOff( AIOnOff )** added. --- --- 2017-01-24: GROUP:**SetAIOn()** added. --- --- 2017-01-24: GROUP:**SetAIOff()** added. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff(). --- --- ### Authors: --- --- * **FlightControl**: Design & Programming --- --- @module Group --- @author FlightControl - ---- The GROUP class --- @type GROUP --- @extends Wrapper.Controllable#CONTROLLABLE --- @field #string GroupName The name of the group. -GROUP = { - ClassName = "GROUP", -} - ---- Create a new GROUP from a DCSGroup --- @param #GROUP self --- @param Dcs.DCSWrapper.Group#Group GroupName The DCS Group name --- @return #GROUP self -function GROUP:Register( GroupName ) - self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) - self:F2( GroupName ) - self.GroupName = GroupName - - self:SetEventPriority( 4 ) - return self -end - --- Reference methods. - ---- Find the GROUP wrapper class instance using the DCS Group. --- @param #GROUP self --- @param Dcs.DCSWrapper.Group#Group DCSGroup The DCS Group. --- @return #GROUP The GROUP. -function GROUP:Find( DCSGroup ) - - local GroupName = DCSGroup:getName() -- Wrapper.Group#GROUP - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - ---- Find the created GROUP using the DCS Group Name. --- @param #GROUP self --- @param #string GroupName The DCS Group Name. --- @return #GROUP The GROUP. -function GROUP:FindByName( GroupName ) - - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - --- DCS Group methods support. - ---- Returns the DCS Group. --- @param #GROUP self --- @return Dcs.DCSWrapper.Group#Group The DCS Group. -function GROUP:GetDCSObject() - local DCSGroup = Group.getByName( self.GroupName ) - - if DCSGroup then - return DCSGroup - end - - return nil -end - ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePosition = DCSPositionable:getUnits()[1]:getPosition().p - self:T3( PositionablePosition ) - return PositionablePosition - end - - return nil -end - ---- Returns if the DCS Group is alive. --- When the group exists at run-time, this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean true if the DCS Group is alive. -function GROUP:IsAlive() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupIsAlive = DCSGroup:isExist() and DCSGroup:getUnit(1) ~= nil - self:T3( GroupIsAlive ) - return GroupIsAlive - end - - return nil -end - ---- Destroys the DCS Group and all of its DCS Units. --- Note that this destroy method also raises a destroy event at run-time. --- So all event listeners will catch the destroy event of this DCS Group. --- @param #GROUP self -function GROUP:Destroy() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - self:CreateEventCrash( timer.getTime(), UnitData ) - end - DCSGroup:destroy() - DCSGroup = nil - end - - return nil -end - ---- Returns category of the DCS Group. --- @param #GROUP self --- @return Dcs.DCSWrapper.Group#Group.Category The category ID -function GROUP:GetCategory() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - return GroupCategory - end - - return nil -end - ---- Returns the category name of the #GROUP. --- @param #GROUP self --- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship -function GROUP:GetCategoryName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local CategoryNames = { - [Group.Category.AIRPLANE] = "Airplane", - [Group.Category.HELICOPTER] = "Helicopter", - [Group.Category.GROUND] = "Ground Unit", - [Group.Category.SHIP] = "Ship", - } - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - - return CategoryNames[GroupCategory] - end - - return nil -end - - ---- Returns the coalition of the DCS Group. --- @param #GROUP self --- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The coalition side of the DCS Group. -function GROUP:GetCoalition() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCoalition = DCSGroup:getCoalition() - self:T3( GroupCoalition ) - return GroupCoalition - end - - return nil -end - ---- Returns the country of the DCS Group. --- @param #GROUP self --- @return Dcs.DCScountry#country.id The country identifier. --- @return #nil The DCS Group is not existing or alive. -function GROUP:GetCountry() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCountry = DCSGroup:getUnit(1):getCountry() - self:T3( GroupCountry ) - return GroupCountry - end - - return nil -end - ---- Returns the UNIT wrapper class with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the UNIT wrapper class to be returned. --- @return Wrapper.Unit#UNIT The UNIT wrapper class. -function GROUP:GetUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) - self:T2( UnitFound ) - return UnitFound - end - - return nil -end - ---- Returns the DCS Unit with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the DCS Unit to be returned. --- @return Dcs.DCSWrapper.Unit#Unit The DCS Unit. -function GROUP:GetDCSUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) - self:T3( DCSUnitFound ) - return DCSUnitFound - end - - return nil -end - ---- Returns current size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed the size of the DCS Group is changed. --- @param #GROUP self --- @return #number The DCS Group size. -function GROUP:GetSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupSize = DCSGroup:getSize() - self:T3( GroupSize ) - return GroupSize - end - - return nil -end - ---- ---- Returns the initial size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged. --- @param #GROUP self --- @return #number The DCS Group initial size. -function GROUP:GetInitialSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupInitialSize = DCSGroup:getInitialSize() - self:T3( GroupInitialSize ) - return GroupInitialSize - end - - return nil -end - - ---- Returns the DCS Units of the DCS Group. --- @param #GROUP self --- @return #table The DCS Units. -function GROUP:GetDCSUnits() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnits = DCSGroup:getUnits() - self:T3( DCSUnits ) - return DCSUnits - end - - return nil -end - - ---- Activates a GROUP. --- @param #GROUP self -function GROUP:Activate() - self:F2( { self.GroupName } ) - trigger.action.activateGroup( self:GetDCSObject() ) - return self:GetDCSObject() -end - - ---- Gets the type name of the group. --- @param #GROUP self --- @return #string The type name of the group. -function GROUP:GetTypeName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupTypeName = DCSGroup:getUnit(1):getTypeName() - self:T3( GroupTypeName ) - return( GroupTypeName ) - end - - return nil -end - ---- Gets the CallSign of the first DCS Unit of the DCS Group. --- @param #GROUP self --- @return #string The CallSign of the first DCS Unit of the DCS Group. -function GROUP:GetCallsign() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCallSign = DCSGroup:getUnit(1):getCallsign() - self:T3( GroupCallSign ) - return GroupCallSign - end - - return nil -end - ---- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. --- @param #GROUP self --- @return Dcs.DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. -function GROUP:GetVec2() - self:F2( self.GroupName ) - - local UnitPoint = self:GetUnit(1) - UnitPoint:GetVec2() - local GroupPointVec2 = UnitPoint:GetVec2() - self:T3( GroupPointVec2 ) - return GroupPointVec2 -end - ---- Returns the current Vec3 vector of the first DCS Unit in the GROUP. --- @return Dcs.DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. -function GROUP:GetVec3() - self:F2( self.GroupName ) - - local GroupVec3 = self:GetUnit(1):GetVec3() - self:T3( GroupVec3 ) - return GroupVec3 -end - - - -do -- Is Zone methods - ---- Returns true if all units of the group are within a @{Zone}. --- @param #GROUP self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsCompletelyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - if Zone:IsVec3InZone( Unit:GetVec3() ) then - else - return false - end - end - - return true -end - ---- Returns true if some units of the group are within a @{Zone}. --- @param #GROUP self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsPartlyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - if Zone:IsVec3InZone( Unit:GetVec3() ) then - return true - end - end - - return false -end - ---- Returns true if none of the group units of the group are within a @{Zone}. --- @param #GROUP self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsNotInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - if Zone:IsVec3InZone( Unit:GetVec3() ) then - return false - end - end - - return true -end - ---- Returns if the group is of an air category. --- If the group is a helicopter or a plane, then this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean Air category evaluation result. -function GROUP:IsAir() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the DCS Group contains Helicopters. --- @param #GROUP self --- @return #boolean true if DCS Group contains Helicopters. -function GROUP:IsHelicopter() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.HELICOPTER - end - - return nil -end - ---- Returns if the DCS Group contains AirPlanes. --- @param #GROUP self --- @return #boolean true if DCS Group contains AirPlanes. -function GROUP:IsAirPlane() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.AIRPLANE - end - - return nil -end - ---- Returns if the DCS Group contains Ground troops. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ground troops. -function GROUP:IsGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.GROUND - end - - return nil -end - ---- Returns if the DCS Group contains Ships. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ships. -function GROUP:IsShip() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.SHIP - end - - return nil -end - ---- Returns if all units of the group are on the ground or landed. --- If all units of this group are on the ground, this function will return true, otherwise false. --- @param #GROUP self --- @return #boolean All units on the ground result. -function GROUP:AllOnGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local AllOnGroundResult = true - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - if UnitData:inAir() then - AllOnGroundResult = false - end - end - - self:T3( AllOnGroundResult ) - return AllOnGroundResult - end - - return nil -end - -end - -do -- AI methods - - --- Turns the AI On or Off for the GROUP. - -- @param #GROUP self - -- @param #boolean AIOnOff The value true turns the AI On, the value false turns the AI Off. - -- @return #GROUP The GROUP. - function GROUP:SetAIOnOff( AIOnOff ) - - local DCSGroup = self:GetDCSObject() -- Dcs.DCSGroup#Group - - if DCSGroup then - local DCSController = DCSGroup:getController() -- Dcs.DCSController#Controller - if DCSController then - DCSController:setOnOff( AIOnOff ) - return self - end - end - - return nil - end - - --- Turns the AI On for the GROUP. - -- @param #GROUP self - -- @return #GROUP The GROUP. - function GROUP:SetAIOn() - - return self:SetAIOnOff( true ) - end - - --- Turns the AI Off for the GROUP. - -- @param #GROUP self - -- @return #GROUP The GROUP. - function GROUP:SetAIOff() - - return self:SetAIOnOff( false ) - end - -end - - - ---- Returns the current maximum velocity of the group. --- Each unit within the group gets evaluated, and the maximum velocity (= the unit which is going the fastest) is returned. --- @param #GROUP self --- @return #number Maximum velocity found. -function GROUP:GetMaxVelocity() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupVelocityMax = 0 - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - - local UnitVelocityVec3 = UnitData:getVelocity() - local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z ) - - if UnitVelocity > GroupVelocityMax then - GroupVelocityMax = UnitVelocity - end - end - - return GroupVelocityMax - end - - return nil -end - ---- Returns the current minimum height of the group. --- Each unit within the group gets evaluated, and the minimum height (= the unit which is the lowest elevated) is returned. --- @param #GROUP self --- @return #number Minimum height found. -function GROUP:GetMinHeight() - self:F2() - -end - ---- Returns the current maximum height of the group. --- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned. --- @param #GROUP self --- @return #number Maximum height found. -function GROUP:GetMaxHeight() - self:F2() - -end - --- SPAWNING - ---- Respawn the @{GROUP} using a (tweaked) template of the Group. --- The template must be retrieved with the @{Group#GROUP.GetTemplate}() function. --- The template contains all the definitions as declared within the mission file. --- To understand templates, do the following: --- --- * unpack your .miz file into a directory using 7-zip. --- * browse in the directory created to the file **mission**. --- * open the file and search for the country group definitions. --- --- Your group template will contain the fields as described within the mission file. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the group is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will destroy the current alive group. --- * And it will respawn the group using your new template definition. --- @param Wrapper.Group#GROUP self --- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() -function GROUP:Respawn( Template ) - - local Vec3 = self:GetVec3() - Template.x = Vec3.x - Template.y = Vec3.z - --Template.x = nil - --Template.y = nil - - self:E( #Template.units ) - for UnitID, UnitData in pairs( self:GetUnits() ) do - local GroupUnit = UnitData -- Wrapper.Unit#UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - Template.units[UnitID].alt = GroupUnitVec3.y - Template.units[UnitID].x = GroupUnitVec3.x - Template.units[UnitID].y = GroupUnitVec3.z - Template.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) - end - end - - self:Destroy() - _DATABASE:Spawn( Template ) -end - ---- Returns the group template from the @{DATABASE} (_DATABASE object). --- @param #GROUP self --- @return #table -function GROUP:GetTemplate() - local GroupName = self:GetName() - self:E( GroupName ) - return _DATABASE:GetGroupTemplate( GroupName ) -end - ---- Sets the controlled status in a Template. --- @param #GROUP self --- @param #boolean Controlled true is controlled, false is uncontrolled. --- @return #table -function GROUP:SetTemplateControlled( Template, Controlled ) - Template.uncontrolled = not Controlled - return Template -end - ---- Sets the CountryID of the group in a Template. --- @param #GROUP self --- @param Dcs.DCScountry#country.id CountryID The country ID. --- @return #table -function GROUP:SetTemplateCountry( Template, CountryID ) - Template.CountryID = CountryID - return Template -end - ---- Sets the CoalitionID of the group in a Template. --- @param #GROUP self --- @param Dcs.DCSCoalitionWrapper.Object#coalition.side CoalitionID The coalition ID. --- @return #table -function GROUP:SetTemplateCoalition( Template, CoalitionID ) - Template.CoalitionID = CoalitionID - return Template -end - - - - ---- Return the mission template of the group. --- @param #GROUP self --- @return #table The MissionTemplate -function GROUP:GetTaskMission() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) -end - ---- Return the mission route of the group. --- @param #GROUP self --- @return #table The mission route defined by points. -function GROUP:GetTaskRoute() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) -end - ---- Return the route of a group by using the @{Database#DATABASE} class. --- @param #GROUP self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function GROUP:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Group - local GroupName = string.match( self:GetName(), ".*#" ) - if GroupName then - GroupName = GroupName:sub( 1, -2 ) - else - GroupName = self:GetName() - end - - self:T3( { GroupName } ) - - local Template = _DATABASE.Templates.Groups[GroupName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Group : " .. GroupName ) - end - - return nil -end - ---- Calculate the maxium A2G threat level of the Group. --- @param #GROUP self -function GROUP:CalculateThreatLevelA2G() - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( self:GetUnits() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - return MaxThreatLevelA2G -end - ---- Returns true if the first unit of the GROUP is in the air. --- @param Wrapper.Group#GROUP self --- @return #boolean true if in the first unit of the group is in the air. --- @return #nil The GROUP is not existing or not alive. -function GROUP:InAir() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnit = DCSGroup:getUnit(1) - if DCSUnit then - local GroupInAir = DCSGroup:getUnit(1):inAir() - self:T3( GroupInAir ) - return GroupInAir - end - end - - return nil -end - -function GROUP:OnReSpawn( ReSpawnFunction ) - - self.ReSpawnFunction = ReSpawnFunction -end - -do -- Event Handling - - --- Subscribe to a DCS Event. - -- @param #GROUP self - -- @param Core.Event#EVENTS Event - -- @param #function EventFunction (optional) The function to be called when the event occurs for the GROUP. - -- @return #GROUP - function GROUP:HandleEvent( Event, EventFunction ) - - self:EventDispatcher():OnEventForGroup( self:GetName(), EventFunction, self, Event ) - - return self - end - - --- UnSubscribe to a DCS event. - -- @param #GROUP self - -- @param Core.Event#EVENTS Event - -- @return #GROUP - function GROUP:UnHandleEvent( Event ) - - self:EventDispatcher():RemoveForGroup( self:GetName(), self, Event ) - - return self - end - -end - -do -- Players - - --- Get player names - -- @param #GROUP self - -- @return #table The group has players, an array of player names is returned. - -- @return #nil The group has no players - function GROUP:GetPlayerNames() - - local PlayerNames = nil - - local Units = self:GetUnits() - for UnitID, UnitData in pairs( Units ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = Unit:GetPlayerName() - if PlayerName and PlayerName ~= "" then - PlayerNames = PlayerNames or {} - table.insert( PlayerNames, PlayerName ) - end - end - - self:F( PlayerNames ) - return PlayerNames - end - -end--- This module contains the UNIT class. --- --- 1) @{#UNIT} class, extends @{Controllable#CONTROLLABLE} --- =========================================================== --- The @{#UNIT} class is a wrapper class to handle the DCS Unit objects: --- --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Unit API set. --- * Handle local Unit Controller. --- * Manage the "state" of the DCS Unit. --- --- --- 1.1) UNIT reference methods --- ---------------------- --- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Unit objects are spawned (using the @{SPAWN} class). --- --- The UNIT class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that UNIT objects do not "contain" the DCS Unit object. --- The UNIT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the UNIT methods will return nil and log an exception in the DCS.log file. --- --- The UNIT class provides the following functions to retrieve quickly the relevant UNIT instance: --- --- * @{#UNIT.Find}(): Find a UNIT instance from the _DATABASE object using a DCS Unit object. --- * @{#UNIT.FindByName}(): Find a UNIT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these UNIT OBJECT REFERENCES! (make the UNIT object references nil). --- --- 1.2) DCS UNIT APIs --- ------------------ --- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. --- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, --- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{DCSWrapper.Unit#Unit.getName}() --- is implemented in the UNIT class as @{#UNIT.GetName}(). --- --- 1.3) Smoke, Flare Units --- ----------------------- --- The UNIT class provides methods to smoke or flare units easily. --- The @{#UNIT.SmokeBlue}(), @{#UNIT.SmokeGreen}(),@{#UNIT.SmokeOrange}(), @{#UNIT.SmokeRed}(), @{#UNIT.SmokeRed}() methods --- will smoke the unit in the corresponding color. Note that smoking a unit is done at the current position of the DCS Unit. --- When the DCS Unit moves for whatever reason, the smoking will still continue! --- The @{#UNIT.FlareGreen}(), @{#UNIT.FlareRed}(), @{#UNIT.FlareWhite}(), @{#UNIT.FlareYellow}() --- methods will fire off a flare in the air with the corresponding color. Note that a flare is a one-off shot and its effect is of very short duration. --- --- 1.4) Location Position, Point --- ----------------------------- --- The UNIT class provides methods to obtain the current point or position of the DCS Unit. --- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. --- --- 1.5) Test if alive --- ------------------ --- The @{#UNIT.IsAlive}(), @{#UNIT.IsActive}() methods determines if the DCS Unit is alive, meaning, it is existing and active. --- --- 1.6) Test for proximity --- ----------------------- --- The UNIT class contains methods to test the location or proximity against zones or other objects. --- --- ### 1.6.1) Zones --- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Zone#ZONE_BASE}. --- --- ### 1.6.2) Units --- Test if another DCS Unit is within a given radius of the current DCS Unit, use the @{#UNIT.OtherUnitInRadius}() method. --- --- @module Unit --- @author FlightControl - - - - - ---- The UNIT class --- @type UNIT --- @extends Wrapper.Controllable#CONTROLLABLE -UNIT = { - ClassName="UNIT", -} - - ---- Unit.SensorType --- @type Unit.SensorType --- @field OPTIC --- @field RADAR --- @field IRST --- @field RWR - - --- Registration. - ---- Create a new UNIT from DCSUnit. --- @param #UNIT self --- @param #string UnitName The name of the DCS unit. --- @return #UNIT -function UNIT:Register( UnitName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) - self.UnitName = UnitName - - self:SetEventPriority( 3 ) - return self -end - --- Reference methods. - ---- Finds a UNIT from the _DATABASE using a DCSUnit object. --- @param #UNIT self --- @param Dcs.DCSWrapper.Unit#Unit DCSUnit An existing DCS Unit object reference. --- @return #UNIT self -function UNIT:Find( DCSUnit ) - - local UnitName = DCSUnit:getName() - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. --- @param #UNIT self --- @param #string UnitName The Unit Name. --- @return #UNIT self -function UNIT:FindByName( UnitName ) - - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Return the name of the UNIT. --- @param #UNIT self --- @return #string The UNIT name. -function UNIT:Name() - - return self.UnitName -end - - ---- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit -function UNIT:GetDCSObject() - - local DCSUnit = Unit.getByName( self.UnitName ) - - if DCSUnit then - return DCSUnit - end - - return nil -end - ---- Respawn the @{Unit} using a (tweaked) template of the parent Group. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the unit is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will respawn the re-modelled group. --- --- @param #UNIT self --- @param Dcs.DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. --- @param #number Heading The heading of the unit respawn. -function UNIT:ReSpawn( SpawnVec3, Heading ) - - local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) - self:T( SpawnGroupTemplate ) - - local SpawnGroup = self:GetGroup() - - if SpawnGroup then - - local Vec3 = SpawnGroup:GetVec3() - SpawnGroupTemplate.x = SpawnVec3.x - SpawnGroupTemplate.y = SpawnVec3.z - - self:E( #SpawnGroupTemplate.units ) - for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do - local GroupUnit = UnitData -- #UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y - SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x - SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z - SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) - end - end - end - - for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do - self:T( UnitTemplateData.name ) - if UnitTemplateData.name == self:Name() then - self:T("Adjusting") - SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y - SpawnGroupTemplate.units[UnitTemplateID].x = SpawnVec3.x - SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z - SpawnGroupTemplate.units[UnitTemplateID].heading = Heading - self:E( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) - else - self:E( SpawnGroupTemplate.units[UnitTemplateID].name ) - local GroupUnit = UNIT:FindByName( SpawnGroupTemplate.units[UnitTemplateID].name ) -- #UNIT - if GroupUnit and GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - UnitTemplateData.alt = GroupUnitVec3.y - UnitTemplateData.x = GroupUnitVec3.x - UnitTemplateData.y = GroupUnitVec3.z - UnitTemplateData.heading = GroupUnitHeading - else - if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then - self:T("nilling") - SpawnGroupTemplate.units[UnitTemplateID].delete = true - end - end - end - end - - -- Remove obscolete units from the group structure - i = 1 - while i <= #SpawnGroupTemplate.units do - - local UnitTemplateData = SpawnGroupTemplate.units[i] - self:T( UnitTemplateData.name ) - - if UnitTemplateData.delete then - table.remove( SpawnGroupTemplate.units, i ) - else - i = i + 1 - end - end - - _DATABASE:Spawn( SpawnGroupTemplate ) -end - - - ---- Returns if the unit is activated. --- @param #UNIT self --- @return #boolean true if Unit is activated. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:IsActive() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local UnitIsActive = DCSUnit:isActive() - return UnitIsActive - end - - return nil -end - - - ---- Returns the Unit's callsign - the localized string. --- @param #UNIT self --- @return #string The Callsign of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetCallsign() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCallSign = DCSUnit:getCallsign() - return UnitCallSign - end - - self:E( self.ClassName .. " " .. self.UnitName .. " not found!" ) - return nil -end - - ---- Returns name of the player that control the unit or nil if the unit is controlled by A.I. --- @param #UNIT self --- @return #string Player Name --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPlayerName() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local PlayerName = DCSUnit:getPlayerName() - if PlayerName == nil then - PlayerName = "" - end - return PlayerName - end - - return nil -end - ---- Returns the unit's number in the group. --- The number is the same number the unit has in ME. --- It may not be changed during the mission. --- If any unit in the group is destroyed, the numbers of another units will not be changed. --- @param #UNIT self --- @return #number The Unit number. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetNumber() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitNumber = DCSUnit:getNumber() - return UnitNumber - end - - return nil -end - ---- Returns the unit's group if it exist and nil otherwise. --- @param Wrapper.Unit#UNIT self --- @return Wrapper.Group#GROUP The Group of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetGroup() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) - return UnitGroup - end - - return nil -end - - --- Need to add here functions to check if radar is on and which object etc. - ---- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. --- DCS Units spawned with the @{SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. --- The spawn sequence number and unit number are contained within the name after the '#' sign. --- @param #UNIT self --- @return #string The name of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPrefix() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) - self:T3( UnitPrefix ) - return UnitPrefix - end - - return nil -end - ---- Returns the Unit's ammunition. --- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit.Ammo --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetAmmo() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitAmmo = DCSUnit:getAmmo() - return UnitAmmo - end - - return nil -end - ---- Returns the unit sensors. --- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit.Sensors --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetSensors() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSensors = DCSUnit:getSensors() - return UnitSensors - end - - return nil -end - --- Need to add here a function per sensortype --- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) - ---- Returns if the unit has sensors of a certain type. --- @param #UNIT self --- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSensors( ... ) - self:F2( arg ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) - return HasSensors - end - - return nil -end - ---- Returns if the unit is SEADable. --- @param #UNIT self --- @return #boolean returns true if the unit is SEADable. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSEAD() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSEADAttributes = DCSUnit:getDesc().attributes - - local HasSEAD = false - if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or - UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then - HasSEAD = true - end - return HasSEAD - end - - return nil -end - ---- Returns two values: --- --- * First value indicates if at least one of the unit's radar(s) is on. --- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @param #UNIT self --- @return #boolean Indicates if at least one of the unit's radar(s) is on. --- @return Dcs.DCSWrapper.Object#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetRadar() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() - return UnitRadarOn, UnitRadarObject - end - - return nil, nil -end - ---- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. --- @param #UNIT self --- @return #number The relative amount of fuel (from 0.0 to 1.0). --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetFuel() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitFuel = DCSUnit:getFuel() - return UnitFuel - end - - return nil -end - ---- Returns the UNIT in a UNIT list of one element. --- @param #UNIT self --- @return #list The UNITs wrappers. -function UNIT:GetUnits() - self:F2( { self.UnitName } ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local DCSUnits = DCSUnit:getUnits() - local Units = {} - Units[1] = UNIT:Find( DCSUnit ) - self:T3( Units ) - return Units - end - - return nil -end - - ---- Returns the unit's health. Dead units has health <= 1.0. --- @param #UNIT self --- @return #number The Unit's health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife = DCSUnit:getLife() - return UnitLife - end - - return nil -end - ---- Returns the Unit's initial health. --- @param #UNIT self --- @return #number The Unit's initial health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife0() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife0 = DCSUnit:getLife0() - return UnitLife0 - end - - return nil -end - ---- Returns the category name of the #UNIT. --- @param #UNIT self --- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship -function UNIT:GetCategoryName() - self:F3( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - if DCSUnit then - local CategoryNames = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicopter", - [Unit.Category.GROUND_UNIT] = "Ground Unit", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - local UnitCategory = DCSUnit:getDesc().category - self:T3( UnitCategory ) - - return CategoryNames[UnitCategory] - end - - return nil -end - - ---- Returns the Unit's A2G threat level on a scale from 1 to 10 ... --- The following threat levels are foreseen: --- --- * Threat level 0: Unit is unarmed. --- * Threat level 1: Unit is infantry. --- * Threat level 2: Unit is an infantry vehicle. --- * Threat level 3: Unit is ground artillery. --- * Threat level 4: Unit is a tank. --- * Threat level 5: Unit is a modern tank or ifv with ATGM. --- * Threat level 6: Unit is a AAA. --- * Threat level 7: Unit is a SAM or manpad, IR guided. --- * Threat level 8: Unit is a Short Range SAM, radar guided. --- * Threat level 9: Unit is a Medium Range SAM, radar guided. --- * Threat level 10: Unit is a Long Range SAM, radar guided. --- @param #UNIT self -function UNIT:GetThreatLevel() - - local Attributes = self:GetDesc().attributes - self:T( Attributes ) - - local ThreatLevel = 0 - local ThreatText = "" - - if self:IsGround() then - - self:T( "Ground" ) - - local ThreatLevels = { - "Unarmed", - "Infantry", - "Old Tanks & APCs", - "Tanks & IFVs without ATGM", - "Tanks & IFV with ATGM", - "Modern Tanks", - "AAA", - "IR Guided SAMs", - "SR SAMs", - "MR SAMs", - "LR SAMs" - } - - - if Attributes["LR SAM"] then ThreatLevel = 10 - elseif Attributes["MR SAM"] then ThreatLevel = 9 - elseif Attributes["SR SAM"] and - not Attributes["IR Guided SAM"] then ThreatLevel = 8 - elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and - Attributes["IR Guided SAM"] then ThreatLevel = 7 - elseif Attributes["AAA"] then ThreatLevel = 6 - elseif Attributes["Modern Tanks"] then ThreatLevel = 5 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - Attributes["ATGM"] then ThreatLevel = 4 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - not Attributes["ATGM"] then ThreatLevel = 3 - elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then ThreatLevel = 2 - elseif Attributes["Infantry"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - if self:IsAir() then - - self:T( "Air" ) - - local ThreatLevels = { - "Unarmed", - "Tanker", - "AWACS", - "Transport Helicpter", - "UAV", - "Bomber", - "Strategic Bomber", - "Attack Helicopter", - "Interceptor", - "Multirole Fighter", - "Fighter" - } - - - if Attributes["Fighters"] then ThreatLevel = 10 - elseif Attributes["Multirole fighters"] then ThreatLevel = 9 - elseif Attributes["Battleplanes"] then ThreatLevel = 8 - elseif Attributes["Attack helicopters"] then ThreatLevel = 7 - elseif Attributes["Strategic bombers"] then ThreatLevel = 6 - elseif Attributes["Bombers"] then ThreatLevel = 5 - elseif Attributes["UAVs"] then ThreatLevel = 4 - elseif Attributes["Transport helicopters"] then ThreatLevel = 3 - elseif Attributes["AWACS"] then ThreatLevel = 2 - elseif Attributes["Tankers"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - if self:IsShip() then - - self:T( "Ship" ) - ---["Aircraft Carriers"] = {"Heavy armed ships",}, ---["Cruisers"] = {"Heavy armed ships",}, ---["Destroyers"] = {"Heavy armed ships",}, ---["Frigates"] = {"Heavy armed ships",}, ---["Corvettes"] = {"Heavy armed ships",}, ---["Heavy armed ships"] = {"Armed ships", "Armed Air Defence", "HeavyArmoredUnits",}, ---["Light armed ships"] = {"Armed ships","NonArmoredUnits"}, ---["Armed ships"] = {"Ships"}, ---["Unarmed ships"] = {"Ships","HeavyArmoredUnits",}, - - local ThreatLevels = { - "Unarmed ship", - "Light armed ships", - "Corvettes", - "", - "Frigates", - "", - "Cruiser", - "", - "Destroyer", - "", - "Aircraft Carrier" - } - - - if Attributes["Aircraft Carriers"] then ThreatLevel = 10 - elseif Attributes["Destroyers"] then ThreatLevel = 8 - elseif Attributes["Cruisers"] then ThreatLevel = 6 - elseif Attributes["Frigates"] then ThreatLevel = 4 - elseif Attributes["Corvettes"] then ThreatLevel = 2 - elseif Attributes["Light armed ships"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - self:T2( ThreatLevel ) - return ThreatLevel, ThreatText - -end - - --- Is functions - ---- Returns true if the unit is within a @{Zone}. --- @param #UNIT self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is within the @{Zone#ZONE_BASE} -function UNIT:IsInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = Zone:IsVec3InZone( self:GetVec3() ) - - self:T( { IsInZone } ) - return IsInZone - end - - return false -end - ---- Returns true if the unit is not within a @{Zone}. --- @param #UNIT self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is not within the @{Zone#ZONE_BASE} -function UNIT:IsNotInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = not Zone:IsVec3InZone( self:GetVec3() ) - - self:T( { IsInZone } ) - return IsInZone - else - return false - end -end - - ---- Returns true if there is an **other** DCS Unit within a radius of the current 2D point of the DCS Unit. --- @param #UNIT self --- @param #UNIT AwaitUnit The other UNIT wrapper object. --- @param Radius The radius in meters with the DCS Unit in the centre. --- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitVec3 = self:GetVec3() - local AwaitUnitVec3 = AwaitUnit:GetVec3() - - if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then - self:T3( "true" ) - return true - else - self:T3( "false" ) - return false - end - end - - return nil -end - - - ---- Signal a flare at the position of the UNIT. --- @param #UNIT self --- @param Utilities.Utils#FLARECOLOR FlareColor -function UNIT:Flare( FlareColor ) - self:F2() - trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) -end - ---- Signal a white flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareWhite() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) -end - ---- Signal a yellow flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareYellow() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) -end - ---- Signal a green flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareGreen() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) -end - ---- Signal a red flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareRed() - self:F2() - local Vec3 = self:GetVec3() - if Vec3 then - trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) - end -end - ---- Smoke the UNIT. --- @param #UNIT self -function UNIT:Smoke( SmokeColor, Range ) - self:F2() - if Range then - trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) - else - trigger.action.smoke( self:GetVec3(), SmokeColor ) - end - -end - ---- Smoke the UNIT Green. --- @param #UNIT self -function UNIT:SmokeGreen() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green ) -end - ---- Smoke the UNIT Red. --- @param #UNIT self -function UNIT:SmokeRed() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red ) -end - ---- Smoke the UNIT White. --- @param #UNIT self -function UNIT:SmokeWhite() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White ) -end - ---- Smoke the UNIT Orange. --- @param #UNIT self -function UNIT:SmokeOrange() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange ) -end - ---- Smoke the UNIT Blue. --- @param #UNIT self -function UNIT:SmokeBlue() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) -end - --- Is methods - ---- Returns if the unit is of an air category. --- If the unit is a helicopter or a plane, then this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Air category evaluation result. -function UNIT:IsAir() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) - - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the unit is of an ground category. --- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ground category evaluation result. -function UNIT:IsGround() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) - - local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) - - self:T3( IsGroundResult ) - return IsGroundResult - end - - return nil -end - ---- Returns if the unit is a friendly unit. --- @param #UNIT self --- @return #boolean IsFriendly evaluation result. -function UNIT:IsFriendly( FriendlyCoalition ) - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCoalition = DCSUnit:getCoalition() - self:T3( { UnitCoalition, FriendlyCoalition } ) - - local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) - - self:E( IsFriendlyResult ) - return IsFriendlyResult - end - - return nil -end - ---- Returns if the unit is of a ship category. --- If the unit is a ship, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ship category evaluation result. -function UNIT:IsShip() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - - local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) - - self:T3( IsShipResult ) - return IsShipResult - end - - return nil -end - ---- Returns true if the UNIT is in the air. --- @param Wrapper.Positionable#UNIT self --- @return #boolean true if in the air. --- @return #nil The UNIT is not existing or alive. -function UNIT:InAir() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitInAir = DCSUnit:inAir() - self:T3( UnitInAir ) - return UnitInAir - end - - return nil -end - -do -- Event Handling - - --- Subscribe to a DCS Event. - -- @param #UNIT self - -- @param Core.Event#EVENTS Event - -- @param #function EventFunction (optional) The function to be called when the event occurs for the unit. - -- @return #UNIT - function UNIT:HandleEvent( Event, EventFunction ) - - self:EventDispatcher():OnEventForUnit( self:GetName(), EventFunction, self, Event ) - - return self - end - - --- UnSubscribe to a DCS event. - -- @param #UNIT self - -- @param Core.Event#EVENTS Event - -- @return #UNIT - function UNIT:UnHandleEvent( Event ) - - self:EventDispatcher():RemoveForUnit( self:GetName(), self, Event ) - - return self - end - -end--- This module contains the CLIENT class. --- --- 1) @{Client#CLIENT} class, extends @{Unit#UNIT} --- =============================================== --- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. --- Note that clients are NOT the same as Units, they are NOT necessarily alive. --- The @{Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: --- --- * Wraps the DCS Unit objects with skill level set to Player or Client. --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Group API set. --- * When player joins Unit, execute alive init logic. --- * Handles messages to players. --- * Manage the "state" of the DCS Unit. --- --- Clients are being used by the @{MISSION} class to follow players and register their successes. --- --- 1.1) CLIENT reference methods --- ----------------------------- --- For each DCS Unit having skill level Player or Client, a CLIENT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The CLIENT class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that CLIENT objects do not "contain" the DCS Unit object. --- The CLIENT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the CLIENT methods will return nil and log an exception in the DCS.log file. --- --- The CLIENT class provides the following functions to retrieve quickly the relevant CLIENT instance: --- --- * @{#CLIENT.Find}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit object. --- * @{#CLIENT.FindByName}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these CLIENT OBJECT REFERENCES! (make the CLIENT object references nil). --- --- @module Client - ---- The CLIENT class --- @type CLIENT --- @extends Wrapper.Unit#UNIT -CLIENT = { - ONBOARDSIDE = { - NONE = 0, - LEFT = 1, - RIGHT = 2, - BACK = 3, - FRONT = 4 - }, - ClassName = "CLIENT", - ClientName = nil, - ClientAlive = false, - ClientTransport = false, - ClientBriefingShown = false, - _Menus = {}, - _Tasks = {}, - Messages = { - } -} - - ---- Finds a CLIENT from the _DATABASE using the relevant DCS Unit. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:Find( DCSUnit ) - local ClientName = DCSUnit:getName() - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( ClientName ) - return ClientFound - end - - error( "CLIENT not found for: " .. ClientName ) -end - - ---- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name. --- As an optional parameter, a briefing text can be given also. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @param #boolean Error A flag that indicates whether an error should be raised if the CLIENT cannot be found. By default an error will be raised. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:FindByName( ClientName, ClientBriefing, Error ) - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( { ClientName, ClientBriefing } ) - ClientFound:AddBriefing( ClientBriefing ) - ClientFound.MessageSwitch = true - - return ClientFound - end - - if not Error then - error( "CLIENT not found for: " .. ClientName ) - end -end - -function CLIENT:Register( ClientName ) - local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) - - self:F( ClientName ) - self.ClientName = ClientName - self.MessageSwitch = true - self.ClientAlive2 = false - - --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) - self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 ) - - self:E( self ) - return self -end - - ---- Transport defines that the Client is a Transport. Transports show cargo. --- @param #CLIENT self --- @return #CLIENT -function CLIENT:Transport() - self:F() - - self.ClientTransport = true - return self -end - ---- AddBriefing adds a briefing to a CLIENT when a player joins a mission. --- @param #CLIENT self --- @param #string ClientBriefing is the text defining the Mission briefing. --- @return #CLIENT self -function CLIENT:AddBriefing( ClientBriefing ) - self:F( ClientBriefing ) - self.ClientBriefing = ClientBriefing - self.ClientBriefingShown = false - - return self -end - ---- Show the briefing of a CLIENT. --- @param #CLIENT self --- @return #CLIENT self -function CLIENT:ShowBriefing() - self:F( { self.ClientName, self.ClientBriefingShown } ) - - if not self.ClientBriefingShown then - self.ClientBriefingShown = true - local Briefing = "" - if self.ClientBriefing then - Briefing = Briefing .. self.ClientBriefing - end - Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing." - self:Message( Briefing, 60, "Briefing" ) - end - - return self -end - ---- Show the mission briefing of a MISSION to the CLIENT. --- @param #CLIENT self --- @param #string MissionBriefing --- @return #CLIENT self -function CLIENT:ShowMissionBriefing( MissionBriefing ) - self:F( { self.ClientName } ) - - if MissionBriefing then - self:Message( MissionBriefing, 60, "Mission Briefing" ) - end - - return self -end - - - ---- Resets a CLIENT. --- @param #CLIENT self --- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. -function CLIENT:Reset( ClientName ) - self:F() - self._Menus = {} -end - --- Is Functions - ---- Checks if the CLIENT is a multi-seated UNIT. --- @param #CLIENT self --- @return #boolean true if multi-seated. -function CLIENT:IsMultiSeated() - self:F( self.ClientName ) - - local ClientMultiSeatedTypes = { - ["Mi-8MT"] = "Mi-8MT", - ["UH-1H"] = "UH-1H", - ["P-51B"] = "P-51B" - } - - if self:IsAlive() then - local ClientTypeName = self:GetClientGroupUnit():GetTypeName() - if ClientMultiSeatedTypes[ClientTypeName] then - return true - end - end - - return false -end - ---- Checks for a client alive event and calls a function on a continuous basis. --- @param #CLIENT self --- @param #function CallBackFunction Create a function that will be called when a player joins the slot. --- @return #CLIENT -function CLIENT:Alive( CallBackFunction, ... ) - self:F() - - self.ClientCallBack = CallBackFunction - self.ClientParameters = arg - - return self -end - ---- @param #CLIENT self -function CLIENT:_AliveCheckScheduler( SchedulerName ) - self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) - - if self:IsAlive() then - if self.ClientAlive2 == false then - self:ShowBriefing() - if self.ClientCallBack then - self:T("Calling Callback function") - self.ClientCallBack( self, unpack( self.ClientParameters ) ) - end - self.ClientAlive2 = true - end - else - if self.ClientAlive2 == true then - self.ClientAlive2 = false - end - end - - return true -end - ---- Return the DCSGroup of a Client. --- This function is modified to deal with a couple of bugs in DCS 1.5.3 --- @param #CLIENT self --- @return Dcs.DCSWrapper.Group#Group -function CLIENT:GetDCSGroup() - self:F3() - --- local ClientData = Group.getByName( self.ClientName ) --- if ClientData and ClientData:isExist() then --- self:T( self.ClientName .. " : group found!" ) --- return ClientData --- else --- return nil --- end - - local ClientUnit = Unit.getByName( self.ClientName ) - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - self:T3( { "CoalitionData:", CoalitionData } ) - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - - --self:E(self.ClientName) - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() and UnitData:getGroup():isExist() then - if ClientGroup:getID() == UnitData:getGroup():getID() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - self.ClientGroupID = ClientGroup:getID() - self.ClientGroupName = ClientGroup:getName() - return ClientGroup - end - else - -- Now we need to resolve the bugs in DCS 1.5 ... - -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) - self:T3( "Bug 1.5 logic" ) - local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate - self.ClientGroupID = ClientGroupTemplate.groupId - self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName - self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) - return ClientGroup - end - -- else - -- error( "Client " .. self.ClientName .. " not found!" ) - end - else - --self:E( { "Client not found!", self.ClientName } ) - end - end - end - end - - -- For non player clients - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - end - end - - self.ClientGroupID = nil - self.ClientGroupUnit = nil - - return nil -end - - --- TODO: Check Dcs.DCSTypes#Group.ID ---- Get the group ID of the client. --- @param #CLIENT self --- @return Dcs.DCSTypes#Group.ID -function CLIENT:GetClientGroupID() - - local ClientGroup = self:GetDCSGroup() - - --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() - return self.ClientGroupID -end - - ---- Get the name of the group of the client. --- @param #CLIENT self --- @return #string -function CLIENT:GetClientGroupName() - - local ClientGroup = self:GetDCSGroup() - - self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() - return self.ClientGroupName -end - ---- Returns the UNIT of the CLIENT. --- @param #CLIENT self --- @return Wrapper.Unit#UNIT -function CLIENT:GetClientGroupUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - self:T( self.ClientDCSUnit ) - if ClientDCSUnit and ClientDCSUnit:isExist() then - local ClientUnit = _DATABASE:FindUnit( self.ClientName ) - self:T2( ClientUnit ) - return ClientUnit - end -end - ---- Returns the DCSUnit of the CLIENT. --- @param #CLIENT self --- @return Dcs.DCSTypes#Unit -function CLIENT:GetClientGroupDCSUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - if ClientDCSUnit and ClientDCSUnit:isExist() then - self:T2( ClientDCSUnit ) - return ClientDCSUnit - end -end - - ---- Evaluates if the CLIENT is a transport. --- @param #CLIENT self --- @return #boolean true is a transport. -function CLIENT:IsTransport() - self:F() - return self.ClientTransport -end - ---- Shows the @{AI_Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{AI_Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. --- @param #CLIENT self -function CLIENT:ShowCargo() - self:F() - - local CargoMsg = "" - - for CargoName, Cargo in pairs( CARGOS ) do - if self == Cargo:IsLoadedInClient() then - CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" - end - end - - if CargoMsg == "" then - CargoMsg = "empty" - end - - self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 ) - -end - --- TODO (1) I urgently need to revise this. ---- A local function called by the DCS World Menu system to switch off messages. -function CLIENT.SwitchMessages( PrmTable ) - PrmTable[1].MessageSwitch = PrmTable[2] -end - ---- The main message driver for the CLIENT. --- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. --- @param #CLIENT self --- @param #string Message is the text describing the message. --- @param #number MessageDuration is the duration in seconds that the Message should be displayed. --- @param #string MessageCategory is the category of the message (the title). --- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. --- @param #string MessageID is the identifier of the message when displayed with intervals. -function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) - self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) - - if self.MessageSwitch == true then - if MessageCategory == nil then - MessageCategory = "Messages" - end - if MessageID ~= nil then - if self.Messages[MessageID] == nil then - self.Messages[MessageID] = {} - self.Messages[MessageID].MessageId = MessageID - self.Messages[MessageID].MessageTime = timer.getTime() - self.Messages[MessageID].MessageDuration = MessageDuration - if MessageInterval == nil then - self.Messages[MessageID].MessageInterval = 600 - else - self.Messages[MessageID].MessageInterval = MessageInterval - end - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - else - if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then - MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - else - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - end - end - else - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - end - end -end ---- This module contains the STATIC class. --- --- 1) @{Static#STATIC} class, extends @{Positionable#POSITIONABLE} --- =============================================================== --- Statics are **Static Units** defined within the Mission Editor. --- Note that Statics are almost the same as Units, but they don't have a controller. --- The @{Static#STATIC} class is a wrapper class to handle the DCS Static objects: --- --- * Wraps the DCS Static objects. --- * Support all DCS Static APIs. --- * Enhance with Static specific APIs not in the DCS API set. --- --- 1.1) STATIC reference methods --- ----------------------------- --- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the Static Name. --- --- Another thing to know is that STATIC objects do not "contain" the DCS Static object. --- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. --- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. --- --- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: --- --- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). --- --- @module Static --- @author FlightControl - - - - - - ---- The STATIC class --- @type STATIC --- @extends Wrapper.Positionable#POSITIONABLE -STATIC = { - ClassName = "STATIC", -} - - ---- Finds a STATIC from the _DATABASE using the relevant Static Name. --- As an optional parameter, a briefing text can be given also. --- @param #STATIC self --- @param #string StaticName Name of the DCS **Static** as defined within the Mission Editor. --- @param #boolean RaiseError Raise an error if not found. --- @return #STATIC -function STATIC:FindByName( StaticName, RaiseError ) - local StaticFound = _DATABASE:FindStatic( StaticName ) - - self.StaticName = StaticName - - if StaticFound then - StaticFound:F3( { StaticName } ) - - return StaticFound - end - - if RaiseError == nil or RaiseError == true then - error( "STATIC not found for: " .. StaticName ) - end - - return nil -end - -function STATIC:Register( StaticName ) - local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) - self.StaticName = StaticName - return self -end - - -function STATIC:GetDCSObject() - local DCSStatic = StaticObject.getByName( self.StaticName ) - - if DCSStatic then - return DCSStatic - end - - return nil -end - -function STATIC:GetThreatLevel() - - return 1, "Static" -end--- This module contains the AIRBASE classes. --- --- === --- --- 1) @{Airbase#AIRBASE} class, extends @{Positionable#POSITIONABLE} --- ================================================================= --- The @{AIRBASE} class is a wrapper class to handle the DCS Airbase objects: --- --- * Support all DCS Airbase APIs. --- * Enhance with Airbase specific APIs not in the DCS Airbase API set. --- --- --- 1.1) AIRBASE reference methods --- ------------------------------ --- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Airbase or the DCS AirbaseName. --- --- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. --- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. --- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. --- --- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: --- --- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. --- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). --- --- 1.2) DCS AIRBASE APIs --- --------------------- --- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. --- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, --- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSWrapper.Airbase#Airbase.getName}() --- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). --- --- More functions will be added --- ---------------------------- --- During the MOOSE development, more functions will be added. --- --- @module Airbase --- @author FlightControl - - - - - ---- The AIRBASE class --- @type AIRBASE --- @extends Wrapper.Positionable#POSITIONABLE -AIRBASE = { - ClassName="AIRBASE", - CategoryName = { - [Airbase.Category.AIRDROME] = "Airdrome", - [Airbase.Category.HELIPAD] = "Helipad", - [Airbase.Category.SHIP] = "Ship", - }, - } - --- Registration. - ---- Create a new AIRBASE from DCSAirbase. --- @param #AIRBASE self --- @param #string AirbaseName The name of the airbase. --- @return Wrapper.Airbase#AIRBASE -function AIRBASE:Register( AirbaseName ) - - local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) - self.AirbaseName = AirbaseName - return self -end - --- Reference methods. - ---- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. --- @param #AIRBASE self --- @param Dcs.DCSWrapper.Airbase#Airbase DCSAirbase An existing DCS Airbase object reference. --- @return Wrapper.Airbase#AIRBASE self -function AIRBASE:Find( DCSAirbase ) - - local AirbaseName = DCSAirbase:getName() - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - ---- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. --- @param #AIRBASE self --- @param #string AirbaseName The Airbase Name. --- @return Wrapper.Airbase#AIRBASE self -function AIRBASE:FindByName( AirbaseName ) - - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - -function AIRBASE:GetDCSObject() - local DCSAirbase = Airbase.getByName( self.AirbaseName ) - - if DCSAirbase then - return DCSAirbase - end - - return nil -end - - - ---- This module contains the SCENERY class. --- --- 1) @{Scenery#SCENERY} class, extends @{Positionable#POSITIONABLE} --- =============================================================== --- Scenery objects are defined on the map. --- The @{Scenery#SCENERY} class is a wrapper class to handle the DCS Scenery objects: --- --- * Wraps the DCS Scenery objects. --- * Support all DCS Scenery APIs. --- * Enhance with Scenery specific APIs not in the DCS API set. --- --- @module Scenery --- @author FlightControl - - - ---- The SCENERY class --- @type SCENERY --- @extends Wrapper.Positionable#POSITIONABLE -SCENERY = { - ClassName = "SCENERY", -} - - -function SCENERY:Register( SceneryName, SceneryObject ) - local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) ) - self.SceneryName = SceneryName - self.SceneryObject = SceneryObject - return self -end - -function SCENERY:GetDCSObject() - return self.SceneryObject -end - -function SCENERY:GetThreatLevel() - - return 0, "Scenery" -end ---- Single-Player:**Yes** / Multi-Player:**Yes** / Core:**Yes** -- **Administer the scoring of player achievements, --- and create a CSV file logging the scoring events for use at team or squadron websites.** --- --- ![Banner Image](..\Presentations\SCORING\Dia1.JPG) --- --- === --- --- # 1) @{Scoring#SCORING} class, extends @{Base#BASE} --- --- The @{#SCORING} class administers the scoring of player achievements, --- and creates a CSV file logging the scoring events and results for use at team or squadron websites. --- --- SCORING automatically calculates the threat level of the objects hit and destroyed by players, --- which can be @{Unit}, @{Static) and @{Scenery} objects. --- --- Positive score points are granted when enemy or neutral targets are destroyed. --- Negative score points or penalties are given when a friendly target is hit or destroyed. --- This brings a lot of dynamism in the scoring, where players need to take care to inflict damage on the right target. --- By default, penalties weight heavier in the scoring, to ensure that players don't commit fratricide. --- The total score of the player is calculated by **adding the scores minus the penalties**. --- --- ![Banner Image](..\Presentations\SCORING\Dia4.JPG) --- --- The score value is calculated based on the **threat level of the player** and the **threat level of the target**. --- A calculated score takes the threat level of the target divided by a balanced threat level of the player unit. --- As such, if the threat level of the target is high, and the player threat level is low, a higher score will be given than --- if the threat level of the player would be high too. --- --- ![Banner Image](..\Presentations\SCORING\Dia5.JPG) --- --- When multiple players hit the same target, and finally succeed in destroying the target, then each player who contributed to the target --- destruction, will receive a score. This is important for targets that require significant damage before it can be destroyed, like --- ships or heavy planes. --- --- ![Banner Image](..\Presentations\SCORING\Dia13.JPG) --- --- Optionally, the score values can be **scaled** by a **scale**. Specific scales can be set for positive cores or negative penalties. --- The default range of the scores granted is a value between 0 and 10. The default range of penalties given is a value between 0 and 30. --- --- ![Banner Image](..\Presentations\SCORING\Dia7.JPG) --- --- **Additional scores** can be granted to **specific objects**, when the player(s) destroy these objects. --- --- ![Banner Image](..\Presentations\SCORING\Dia9.JPG) --- --- Various @{Zone}s can be defined for which scores are also granted when objects in that @{Zone} are destroyed. --- This is **specifically useful** to designate **scenery targets on the map** that will generate points when destroyed. --- --- With a small change in MissionScripting.lua, the scoring results can also be logged in a **CSV file**. --- These CSV files can be used to: --- --- * Upload scoring to a database or a BI tool to publish the scoring results to the player community. --- * Upload scoring in an (online) Excel like tool, using pivot tables and pivot charts to show mission results. --- * Share scoring amoung players after the mission to discuss mission results. --- --- Scores can be **reported**. **Menu options** are automatically added to **each player group** when a player joins a client slot or a CA unit. --- Use the radio menu F10 to consult the scores while running the mission. --- Scores can be reported for your user, or an overall score can be reported of all players currently active in the mission. --- --- ## 1.1) Set the destroy score or penalty scale --- --- Score scales can be set for scores granted when enemies or friendlies are destroyed. --- Use the method @{#SCORING.SetScaleDestroyScore}() to set the scale of enemy destroys (positive destroys). --- Use the method @{#SCORING.SetScaleDestroyPenalty}() to set the scale of friendly destroys (negative destroys). --- --- local Scoring = SCORING:New( "Scoring File" ) --- Scoring:SetScaleDestroyScore( 10 ) --- Scoring:SetScaleDestroyPenalty( 40 ) --- --- The above sets the scale for valid scores to 10. So scores will be given in a scale from 0 to 10. --- The penalties will be given in a scale from 0 to 40. --- --- ## 1.2) Define special targets that will give extra scores. --- --- Special targets can be set that will give extra scores to the players when these are destroyed. --- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Unit}s. --- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s. --- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Group}s. --- --- local Scoring = SCORING:New( "Scoring File" ) --- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) --- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 ) --- --- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed. --- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over. --- For example, this can be done as follows: --- --- Scoring:RemoveUnitScore( UNIT:FindByName( "Unit #001" ) ) --- --- --- --- ## 1.3) Define destruction zones that will give extra scores. --- --- Define zones of destruction. Any object destroyed within the zone of the given category will give extra points. --- Use the method @{#SCORING.AddZoneScore}() to add a @{Zone} for additional scoring. --- Use the method @{#SCORING.RemoveZoneScore}() to remove a @{Zone} for additional scoring. --- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Zone#ZONE_UNIT}, --- then the zone is a moving zone, and anything destroyed within that @{Zone} will generate points. --- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone}, --- just large enough around that building. --- --- ## 1.4) Add extra Goal scores upon an event or a condition. --- --- A mission has goals and achievements. The scoring system provides an API to set additional scores when a goal or achievement event happens. --- Use the method @{#SCORING.AddGoalScore}() to add a score for a Player at any time in your mission. --- --- ## 1.5) Configure fratricide level. --- --- When a player commits too much damage to friendlies, his penalty score will reach a certain level. --- Use the method @{#SCORING.SetFratricide}() to define the level when a player gets kicked. --- By default, the fratricide level is the default penalty mutiplier * 2 for the penalty score. --- --- ## 1.6) Penalty score when a player changes the coalition. --- --- When a player changes the coalition, he can receive a penalty score. --- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. --- By default, the penalty for changing coalition is the default penalty scale. --- --- ## 1.8) Define output CSV files. --- --- The CSV file is given the name of the string given in the @{#SCORING.New}{} constructor, followed by the .csv extension. --- The file is incrementally saved in the **\\Saved Games\\DCS\\Logs** folder, and has a time stamp indicating each mission run. --- See the following example: --- --- local ScoringFirstMission = SCORING:New( "FirstMission" ) --- local ScoringSecondMission = SCORING:New( "SecondMission" ) --- --- The above documents that 2 Scoring objects are created, ScoringFirstMission and ScoringSecondMission. --- --- ## 1.9) Configure messages. --- --- When players hit or destroy targets, messages are sent. --- Various methods exist to configure: --- --- * Which messages are sent upon the event. --- * Which audience receives the message. --- --- ### 1.9.1) Configure the messages sent upon the event. --- --- Use the following methods to configure when to send messages. By default, all messages are sent. --- --- * @{#SCORING.SetMessagesHit}(): Configure to send messages after a target has been hit. --- * @{#SCORING.SetMessagesDestroy}(): Configure to send messages after a target has been destroyed. --- * @{#SCORING.SetMessagesAddon}(): Configure to send messages for additional score, after a target has been destroyed. --- * @{#SCORING.SetMessagesZone}(): Configure to send messages for additional score, after a target has been destroyed within a given zone. --- --- ### 1.9.2) Configure the audience of the messages. --- --- Use the following methods to configure the audience of the messages. By default, the messages are sent to all players in the mission. --- --- * @{#SCORING.SetMessagesToAll}(): Configure to send messages to all players. --- * @{#SCORING.SetMessagesToCoalition}(): Configure to send messages to only those players within the same coalition as the player. --- --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-02-26: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **Wingthor (TAW)**: Testing & Advice. --- * **Dutch-Baron (TAW)**: Testing & Advice. --- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing and Advice. --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module Scoring - - ---- The Scoring class --- @type SCORING --- @field Players A collection of the current players that have joined the game. --- @extends Core.Base#BASE -SCORING = { - ClassName = "SCORING", - ClassID = 0, - Players = {}, -} - -local _SCORINGCoalition = - { - [1] = "Red", - [2] = "Blue", - } - -local _SCORINGCategory = - { - [Unit.Category.AIRPLANE] = "Plane", - [Unit.Category.HELICOPTER] = "Helicopter", - [Unit.Category.GROUND_UNIT] = "Vehicle", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - ---- Creates a new SCORING object to administer the scoring achieved by players. --- @param #SCORING self --- @param #string GameName The name of the game. This name is also logged in the CSV score file. --- @return #SCORING self --- @usage --- -- Define a new scoring object for the mission Gori Valley. --- ScoringObject = SCORING:New( "Gori Valley" ) -function SCORING:New( GameName ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- #SCORING - - if GameName then - self.GameName = GameName - else - error( "A game name must be given to register the scoring results" ) - end - - - -- Additional Object scores - self.ScoringObjects = {} - - -- Additional Zone scores. - self.ScoringZones = {} - - -- Configure Messages - self:SetMessagesToAll() - self:SetMessagesHit( true ) - self:SetMessagesDestroy( true ) - self:SetMessagesScore( true ) - self:SetMessagesZone( true ) - - -- Scales - self:SetScaleDestroyScore( 10 ) - self:SetScaleDestroyPenalty( 30 ) - - -- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked). - self:SetFratricide( self.ScaleDestroyPenalty * 3 ) - - -- Default penalty when a player changes coalition. - self:SetCoalitionChangePenalty( self.ScaleDestroyPenalty ) - - -- Event handlers - self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Hit, self._EventOnHit ) - self:HandleEvent( EVENTS.PlayerEnterUnit ) - self:HandleEvent( EVENTS.PlayerLeaveUnit ) - - -- Create the CSV file. - self:OpenCSV( GameName ) - - return self - -end - ---- Set the scale for scoring valid destroys (enemy destroys). --- A default calculated score is a value between 1 and 10. --- The scale magnifies the scores given to the players. --- @param #SCORING self --- @param #number Scale The scale of the score given. -function SCORING:SetScaleDestroyScore( Scale ) - - self.ScaleDestroyScore = Scale - - return self -end - ---- Set the scale for scoring penalty destroys (friendly destroys). --- A default calculated penalty is a value between 1 and 10. --- The scale magnifies the scores given to the players. --- @param #SCORING self --- @param #number Scale The scale of the score given. --- @return #SCORING -function SCORING:SetScaleDestroyPenalty( Scale ) - - self.ScaleDestroyPenalty = Scale - - return self -end - ---- Add a @{Unit} for additional scoring when the @{Unit} is destroyed. --- Note that if there was already a @{Unit} declared within the scoring with the same name, --- then the old @{Unit} will be replaced with the new @{Unit}. --- @param #SCORING self --- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given. --- @param #number Score The Score value. --- @return #SCORING -function SCORING:AddUnitScore( ScoreUnit, Score ) - - local UnitName = ScoreUnit:GetName() - - self.ScoringObjects[UnitName] = Score - - return self -end - ---- Removes a @{Unit} for additional scoring when the @{Unit} is destroyed. --- @param #SCORING self --- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given. --- @return #SCORING -function SCORING:RemoveUnitScore( ScoreUnit ) - - local UnitName = ScoreUnit:GetName() - - self.ScoringObjects[UnitName] = nil - - return self -end - ---- Add a @{Static} for additional scoring when the @{Static} is destroyed. --- Note that if there was already a @{Static} declared within the scoring with the same name, --- then the old @{Static} will be replaced with the new @{Static}. --- @param #SCORING self --- @param Wrapper.Static#UNIT ScoreStatic The @{Static} for which the Score needs to be given. --- @param #number Score The Score value. --- @return #SCORING -function SCORING:AddStaticScore( ScoreStatic, Score ) - - local StaticName = ScoreStatic:GetName() - - self.ScoringObjects[StaticName] = Score - - return self -end - ---- Removes a @{Static} for additional scoring when the @{Static} is destroyed. --- @param #SCORING self --- @param Wrapper.Static#UNIT ScoreStatic The @{Static} for which the Score needs to be given. --- @return #SCORING -function SCORING:RemoveStaticScore( ScoreStatic ) - - local StaticName = ScoreStatic:GetName() - - self.ScoringObjects[StaticName] = nil - - return self -end - - ---- Specify a special additional score for a @{Group}. --- @param #SCORING self --- @param Wrapper.Group#GROUP ScoreGroup The @{Group} for which each @{Unit} a Score is given. --- @param #number Score The Score value. --- @return #SCORING -function SCORING:AddScoreGroup( ScoreGroup, Score ) - - local ScoreUnits = ScoreGroup:GetUnits() - - for ScoreUnitID, ScoreUnit in pairs( ScoreUnits ) do - local UnitName = ScoreUnit:GetName() - self.ScoringObjects[UnitName] = Score - end - - return self -end - ---- Add a @{Zone} to define additional scoring when any object is destroyed in that zone. --- Note that if a @{Zone} with the same name is already within the scoring added, the @{Zone} (type) and Score will be replaced! --- This allows for a dynamic destruction zone evolution within your mission. --- @param #SCORING self --- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. --- Note that a zone can be a polygon or a moving zone. --- @param #number Score The Score value. --- @return #SCORING -function SCORING:AddZoneScore( ScoreZone, Score ) - - local ZoneName = ScoreZone:GetName() - - self.ScoringZones[ZoneName] = {} - self.ScoringZones[ZoneName].ScoreZone = ScoreZone - self.ScoringZones[ZoneName].Score = Score - - return self -end - ---- Remove a @{Zone} for additional scoring. --- The scoring will search if any @{Zone} is added with the given name, and will remove that zone from the scoring. --- This allows for a dynamic destruction zone evolution within your mission. --- @param #SCORING self --- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. --- Note that a zone can be a polygon or a moving zone. --- @return #SCORING -function SCORING:RemoveZoneScore( ScoreZone ) - - local ZoneName = ScoreZone:GetName() - - self.ScoringZones[ZoneName] = nil - - return self -end - - ---- Configure to send messages after a target has been hit. --- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. --- @return #SCORING -function SCORING:SetMessagesHit( OnOff ) - - self.MessagesHit = OnOff - return self -end - ---- If to send messages after a target has been hit. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesHit() - - return self.MessagesHit -end - ---- Configure to send messages after a target has been destroyed. --- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. --- @return #SCORING -function SCORING:SetMessagesDestroy( OnOff ) - - self.MessagesDestroy = OnOff - return self -end - ---- If to send messages after a target has been destroyed. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesDestroy() - - return self.MessagesDestroy -end - ---- Configure to send messages after a target has been destroyed and receives additional scores. --- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. --- @return #SCORING -function SCORING:SetMessagesScore( OnOff ) - - self.MessagesScore = OnOff - return self -end - ---- If to send messages after a target has been destroyed and receives additional scores. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesScore() - - return self.MessagesScore -end - ---- Configure to send messages after a target has been hit in a zone, and additional score is received. --- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. --- @return #SCORING -function SCORING:SetMessagesZone( OnOff ) - - self.MessagesZone = OnOff - return self -end - ---- If to send messages after a target has been hit in a zone, and additional score is received. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesZone() - - return self.MessagesZone -end - ---- Configure to send messages to all players. --- @param #SCORING self --- @return #SCORING -function SCORING:SetMessagesToAll() - - self.MessagesAudience = 1 - return self -end - ---- If to send messages to all players. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesToAll() - - return self.MessagesAudience == 1 -end - ---- Configure to send messages to only those players within the same coalition as the player. --- @param #SCORING self --- @return #SCORING -function SCORING:SetMessagesToCoalition() - - self.MessagesAudience = 2 - return self -end - ---- If to send messages to only those players within the same coalition as the player. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesToCoalition() - - return self.MessagesAudience == 2 -end - - ---- When a player commits too much damage to friendlies, his penalty score will reach a certain level. --- Use this method to define the level when a player gets kicked. --- By default, the fratricide level is the default penalty mutiplier * 2 for the penalty score. --- @param #SCORING self --- @param #number Fratricide The amount of maximum penalty that may be inflicted by a friendly player before he gets kicked. --- @return #SCORING -function SCORING:SetFratricide( Fratricide ) - - self.Fratricide = Fratricide - return self -end - - ---- When a player changes the coalition, he can receive a penalty score. --- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. --- By default, the penalty for changing coalition is the default penalty scale. --- @param #SCORING self --- @param #number CoalitionChangePenalty The amount of penalty that is given. --- @return #SCORING -function SCORING:SetCoalitionChangePenalty( CoalitionChangePenalty ) - - self.CoalitionChangePenalty = CoalitionChangePenalty - return self -end - - ---- Add a new player entering a Unit. --- @param #SCORING self --- @param Wrapper.Unit#UNIT UnitData -function SCORING:_AddPlayerFromUnit( UnitData ) - self:F( UnitData ) - - if UnitData:IsAlive() then - local UnitName = UnitData:GetName() - local PlayerName = UnitData:GetPlayerName() - local UnitDesc = UnitData:GetDesc() - local UnitCategory = UnitDesc.category - local UnitCoalition = UnitData:GetCoalition() - local UnitTypeName = UnitData:GetTypeName() - - self:T( { PlayerName, UnitName, UnitCategory, UnitCoalition, UnitTypeName } ) - - if self.Players[PlayerName] == nil then -- I believe this is the place where a Player gets a life in a mission when he enters a unit ... - self.Players[PlayerName] = {} - self.Players[PlayerName].Hit = {} - self.Players[PlayerName].Destroy = {} - self.Players[PlayerName].Goals = {} - self.Players[PlayerName].Mission = {} - - -- for CategoryID, CategoryName in pairs( SCORINGCategory ) do - -- self.Players[PlayerName].Hit[CategoryID] = {} - -- self.Players[PlayerName].Destroy[CategoryID] = {} - -- end - self.Players[PlayerName].HitPlayers = {} - self.Players[PlayerName].Score = 0 - self.Players[PlayerName].Penalty = 0 - self.Players[PlayerName].PenaltyCoalition = 0 - self.Players[PlayerName].PenaltyWarning = 0 - end - - if not self.Players[PlayerName].UnitCoalition then - self.Players[PlayerName].UnitCoalition = UnitCoalition - else - if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then - self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50 - self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1 - MESSAGE:New( "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] .. - "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.", - 2 - ):ToAll() - self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType, - UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() ) - end - end - self.Players[PlayerName].UnitName = UnitName - self.Players[PlayerName].UnitCoalition = UnitCoalition - self.Players[PlayerName].UnitCategory = UnitCategory - self.Players[PlayerName].UnitType = UnitTypeName - self.Players[PlayerName].UNIT = UnitData - - if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then - if self.Players[PlayerName].PenaltyWarning < 1 then - MESSAGE:New( "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty, - 30 - ):ToAll() - self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1 - end - end - - if self.Players[PlayerName].Penalty > self.Fratricide then - UnitData:Destroy() - MESSAGE:New( "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!", - 10 - ):ToAll() - end - - end -end - - ---- Add a goal score for a player. --- The method takes the PlayerUnit for which the Goal score needs to be set. --- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal. --- A free text can be given that is shown to the players. --- The Score can be both positive and negative. --- @param #SCORING self --- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc. --- @param #string GoalTag The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel). --- @param #string Text A free text that is shown to the players. --- @param #number Score The score can be both positive or negative ( Penalty ). -function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score ) - - local PlayerName = PlayerUnit:GetPlayerName() - - self:E( { PlayerUnit.UnitName, PlayerName, GoalTag, Text, Score } ) - - -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then - local PlayerData = self.Players[PlayerName] - - PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 } - PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score - PlayerData.Score = PlayerData.Score + Score - - MESSAGE:New( Text, 30 ):ToAll() - - self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, PlayerUnit:GetName() ) - end -end - - ---- Registers Scores the players completing a Mission Task. --- @param #SCORING self --- @param Tasking.Mission#MISSION Mission --- @param Wrapper.Unit#UNIT PlayerUnit --- @param #string Text --- @param #number Score -function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) - - local PlayerName = PlayerUnit:GetPlayerName() - local MissionName = Mission:GetName() - - self:E( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) - - -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then - local PlayerData = self.Players[PlayerName] - - if not PlayerData.Mission[MissionName] then - PlayerData.Mission[MissionName] = {} - PlayerData.Mission[MissionName].ScoreTask = 0 - PlayerData.Mission[MissionName].ScoreMission = 0 - end - - self:T( PlayerName ) - self:T( PlayerData.Mission[MissionName] ) - - PlayerData.Score = self.Players[PlayerName].Score + Score - PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score - - MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. - Score .. " task score!", - 30 ):ToAll() - - self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() ) - end -end - - ---- Registers Mission Scores for possible multiple players that contributed in the Mission. --- @param #SCORING self --- @param Tasking.Mission#MISSION Mission --- @param Wrapper.Unit#UNIT PlayerUnit --- @param #string Text --- @param #number Score -function SCORING:_AddMissionScore( Mission, Text, Score ) - - local MissionName = Mission:GetName() - - self:E( { Mission, Text, Score } ) - self:E( self.Players ) - - for PlayerName, PlayerData in pairs( self.Players ) do - - self:E( PlayerData ) - if PlayerData.Mission[MissionName] then - - PlayerData.Score = PlayerData.Score + Score - PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score - - MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. - Score .. " mission score!", - 60 ):ToAll() - - self:ScoreCSV( PlayerName, "", "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score ) - end - end -end - - ---- Handles the OnPlayerEnterUnit event for the scoring. --- @param #SCORING self --- @param Core.Event#EVENTDATA Event -function SCORING:OnEventPlayerEnterUnit( Event ) - if Event.IniUnit then - self:_AddPlayerFromUnit( Event.IniUnit ) - local Menu = MENU_GROUP:New( Event.IniGroup, 'Scoring' ) - local ReportGroupSummary = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Summary report players in group', Menu, SCORING.ReportScoreGroupSummary, self, Event.IniGroup ) - local ReportGroupDetailed = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Detailed report players in group', Menu, SCORING.ReportScoreGroupDetailed, self, Event.IniGroup ) - local ReportToAllSummary = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Summary report all players', Menu, SCORING.ReportScoreAllSummary, self, Event.IniGroup ) - self:SetState( Event.IniUnit, "ScoringMenu", Menu ) - end -end - ---- Handles the OnPlayerLeaveUnit event for the scoring. --- @param #SCORING self --- @param Core.Event#EVENTDATA Event -function SCORING:OnEventPlayerLeaveUnit( Event ) - if Event.IniUnit then - local Menu = self:GetState( Event.IniUnit, "ScoringMenu" ) -- Core.Menu#MENU_GROUP - if Menu then - Menu:Remove() - end - end -end - - ---- Handles the OnHit event for the scoring. --- @param #SCORING self --- @param Core.Event#EVENTDATA Event -function SCORING:_EventOnHit( Event ) - self:F( { Event } ) - - local InitUnit = nil - local InitUNIT = nil - local InitUnitName = "" - local InitGroup = nil - local InitGroupName = "" - local InitPlayerName = nil - - local InitCoalition = nil - local InitCategory = nil - local InitType = nil - local InitUnitCoalition = nil - local InitUnitCategory = nil - local InitUnitType = nil - - local TargetUnit = nil - local TargetUNIT = nil - local TargetUnitName = "" - local TargetGroup = nil - local TargetGroupName = "" - local TargetPlayerName = nil - - local TargetCoalition = nil - local TargetCategory = nil - local TargetType = nil - local TargetUnitCoalition = nil - local TargetUnitCategory = nil - local TargetUnitType = nil - - if Event.IniDCSUnit then - - InitUnit = Event.IniDCSUnit - InitUNIT = Event.IniUnit - InitUnitName = Event.IniDCSUnitName - InitGroup = Event.IniDCSGroup - InitGroupName = Event.IniDCSGroupName - InitPlayerName = Event.IniPlayerName - - InitCoalition = Event.IniCoalition - --TODO: Workaround Client DCS Bug - --InitCategory = InitUnit:getCategory() - --InitCategory = InitUnit:getDesc().category - InitCategory = Event.IniCategory - InitType = Event.IniTypeName - - InitUnitCoalition = _SCORINGCoalition[InitCoalition] - InitUnitCategory = _SCORINGCategory[InitCategory] - InitUnitType = InitType - - self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } ) - end - - - if Event.TgtDCSUnit then - - TargetUnit = Event.TgtDCSUnit - TargetUNIT = Event.TgtUnit - TargetUnitName = Event.TgtDCSUnitName - TargetGroup = Event.TgtDCSGroup - TargetGroupName = Event.TgtDCSGroupName - TargetPlayerName = Event.TgtPlayerName - - TargetCoalition = Event.TgtCoalition - --TODO: Workaround Client DCS Bug - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category - TargetCategory = Event.TgtCategory - TargetType = Event.TgtTypeName - - TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] - TargetUnitCategory = _SCORINGCategory[TargetCategory] - TargetUnitType = TargetType - - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType, TargetUnitCoalition, TargetUnitCategory, TargetUnitType } ) - end - - if InitPlayerName ~= nil then -- It is a player that is hitting something - self:_AddPlayerFromUnit( InitUNIT ) - if self.Players[InitPlayerName] then -- This should normally not happen, but i'll test it anyway. - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - self:_AddPlayerFromUnit( TargetUNIT ) - end - - self:T( "Hitting Something" ) - - -- What is he hitting? - if TargetCategory then - - -- A target got hit, score it. - -- Player contains the score data from self.Players[InitPlayerName] - local Player = self.Players[InitPlayerName] - - -- Ensure there is a hit table per TargetCategory and TargetUnitName. - Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {} - Player.Hit[TargetCategory][TargetUnitName] = Player.Hit[TargetCategory][TargetUnitName] or {} - - -- PlayerHit contains the score counters and data per unit that was hit. - local PlayerHit = Player.Hit[TargetCategory][TargetUnitName] - - PlayerHit.Score = PlayerHit.Score or 0 - PlayerHit.Penalty = PlayerHit.Penalty or 0 - PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0 - PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 - PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 - PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT - - -- Only grant hit scores if there was more than one second between the last hit. - if timer.getTime() - PlayerHit.TimeStamp > 1 then - PlayerHit.TimeStamp = timer.getTime() - - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - - -- Ensure there is a Player to Player hit reference table. - Player.HitPlayers[TargetPlayerName] = true - end - - local Score = 0 - - if InitCoalition then -- A coalition object was hit. - if InitCoalition == TargetCoalition then - Player.Penalty = Player.Penalty + 10 - PlayerHit.Penalty = PlayerHit.Penalty + 10 - PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 - - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. - "Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - else - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit a friendly target " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. - "Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - end - self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -25, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - else - Player.Score = Player.Score + 1 - PlayerHit.Score = PlayerHit.Score + 1 - PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. - "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - else - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit an enemy target " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. - "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - end - self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - else -- A scenery object was hit. - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit a scenery object.", - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - self:ScoreCSV( InitPlayerName, "", "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) - end - end - end - end - elseif InitPlayerName == nil then -- It is an AI hitting a player??? - - end -end - ---- Track DEAD or CRASH events for the scoring. --- @param #SCORING self --- @param Core.Event#EVENTDATA Event -function SCORING:_EventOnDeadOrCrash( Event ) - self:F( { Event } ) - - local TargetUnit = nil - local TargetGroup = nil - local TargetUnitName = "" - local TargetGroupName = "" - local TargetPlayerName = "" - local TargetCoalition = nil - local TargetCategory = nil - local TargetType = nil - local TargetUnitCoalition = nil - local TargetUnitCategory = nil - local TargetUnitType = nil - - if Event.IniDCSUnit then - - TargetUnit = Event.IniUnit - TargetUnitName = Event.IniDCSUnitName - TargetGroup = Event.IniDCSGroup - TargetGroupName = Event.IniDCSGroupName - TargetPlayerName = Event.IniPlayerName - - TargetCoalition = Event.IniCoalition - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category -- Workaround - TargetCategory = Event.IniCategory - TargetType = Event.IniTypeName - - TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] - TargetUnitCategory = _SCORINGCategory[TargetCategory] - TargetUnitType = TargetType - - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) - end - - -- Player contains the score and reference data for the player. - for PlayerName, Player in pairs( self.Players ) do - if Player then -- This should normally not happen, but i'll test it anyway. - self:T( "Something got destroyed" ) - - -- Some variables - local InitUnitName = Player.UnitName - local InitUnitType = Player.UnitType - local InitCoalition = Player.UnitCoalition - local InitCategory = Player.UnitCategory - local InitUnitCoalition = _SCORINGCoalition[InitCoalition] - local InitUnitCategory = _SCORINGCategory[InitCategory] - - self:T( { InitUnitName, InitUnitType, InitUnitCoalition, InitCoalition, InitUnitCategory, InitCategory } ) - - -- What is the player destroying? - if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] then -- Was there a hit for this unit for this player before registered??? - - Player.Destroy[TargetCategory] = Player.Destroy[TargetCategory] or {} - Player.Destroy[TargetCategory][TargetType] = Player.Destroy[TargetCategory][TargetType] or {} - - -- PlayerDestroy contains the destroy score data per category and target type of the player. - local TargetDestroy = Player.Destroy[TargetCategory][TargetType] - TargetDestroy.Score = TargetDestroy.Score or 0 - TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy or 0 - TargetDestroy.Penalty = TargetDestroy.Penalty or 0 - TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy or 0 - - if TargetCoalition then - if InitCoalition == TargetCoalition then - local ThreatLevelTarget, ThreatTypeTarget = TargetUnit:GetThreatLevel() - local ThreatLevelPlayer = Player.UNIT:GetThreatLevel() / 10 + 1 - local ThreatPenalty = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyPenalty / 10 ) - self:E( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } ) - - Player.Penalty = Player.Penalty + ThreatPenalty - TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty - TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1 - - if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player - MESSAGE - :New( "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. - TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.PenaltyDestroy .. " times. " .. - "Penalty: -" .. TargetDestroy.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) - else - MESSAGE - :New( "Player '" .. PlayerName .. "' destroyed a friendly target " .. - TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.PenaltyDestroy .. " times. " .. - "Penalty: -" .. TargetDestroy.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) - end - - self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_PENALTY", 1, ThreatPenalty, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - else - - local ThreatLevelTarget, ThreatTypeTarget = TargetUnit:GetThreatLevel() - local ThreatLevelPlayer = Player.UNIT:GetThreatLevel() / 10 + 1 - local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyScore / 10 ) - - self:E( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } ) - - Player.Score = Player.Score + ThreatScore - TargetDestroy.Score = TargetDestroy.Score + ThreatScore - TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1 - if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player - MESSAGE - :New( "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. - TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.ScoreDestroy .. " times. " .. - "Score: " .. TargetDestroy.Score .. ". Score Total:" .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) - else - MESSAGE - :New( "Player '" .. PlayerName .. "' destroyed an enemy " .. - TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.ScoreDestroy .. " times. " .. - "Score: " .. TargetDestroy.Score .. ". Total:" .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) - end - self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, ThreatScore, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - - local UnitName = TargetUnit:GetName() - local Score = self.ScoringObjects[UnitName] - if Score then - Player.Score = Player.Score + Score - TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE - :New( "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() ) - self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - - -- Check if there are Zones where the destruction happened. - for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do - self:E( { ScoringZone = ScoreZoneData } ) - local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE - local Score = ScoreZoneData.Score - if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then - Player.Score = Player.Score + Score - TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE - :New( "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. - "Total: " .. Player.Score - Player.Penalty, - 15 ) - :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) - self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - end - - end - else - -- Check if there are Zones where the destruction happened. - for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do - self:E( { ScoringZone = ScoreZoneData } ) - local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE - local Score = ScoreZoneData.Score - if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then - Player.Score = Player.Score + Score - TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE - :New( "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. - "Total: " .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) - self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) - end - end - end - end - end - end -end - - ---- Produce detailed report of player hit scores. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerHits( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageHits = "" - for CategoryID, CategoryName in pairs( _SCORINGCategory ) do - self:T( CategoryName ) - if PlayerData.Hit[CategoryID] then - self:T( "Hit scores exist for player " .. PlayerName ) - local Score = 0 - local ScoreHit = 0 - local Penalty = 0 - local PenaltyHit = 0 - for UnitName, UnitData in pairs( PlayerData.Hit[CategoryID] ) do - Score = Score + UnitData.Score - ScoreHit = ScoreHit + UnitData.ScoreHit - Penalty = Penalty + UnitData.Penalty - PenaltyHit = UnitData.PenaltyHit - end - local ScoreMessageHit = string.format( "%s:%d ", CategoryName, Score - Penalty ) - self:T( ScoreMessageHit ) - ScoreMessageHits = ScoreMessageHits .. ScoreMessageHit - PlayerScore = PlayerScore + Score - PlayerPenalty = PlayerPenalty + Penalty - else - --ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) - end - end - if ScoreMessageHits ~= "" then - ScoreMessage = "Hits: " .. ScoreMessageHits - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - - ---- Produce detailed report of player destroy scores. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerDestroys( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageDestroys = "" - for CategoryID, CategoryName in pairs( _SCORINGCategory ) do - if PlayerData.Destroy[CategoryID] then - self:T( "Destroy scores exist for player " .. PlayerName ) - local Score = 0 - local ScoreDestroy = 0 - local Penalty = 0 - local PenaltyDestroy = 0 - - for UnitName, UnitData in pairs( PlayerData.Destroy[CategoryID] ) do - self:E( { UnitData = UnitData } ) - if UnitData ~= {} then - Score = Score + UnitData.Score - ScoreDestroy = ScoreDestroy + UnitData.ScoreDestroy - Penalty = Penalty + UnitData.Penalty - PenaltyDestroy = PenaltyDestroy + UnitData.PenaltyDestroy - end - end - - local ScoreMessageDestroy = string.format( " %s:%d ", CategoryName, Score - Penalty ) - self:T( ScoreMessageDestroy ) - ScoreMessageDestroys = ScoreMessageDestroys .. ScoreMessageDestroy - - PlayerScore = PlayerScore + Score - PlayerPenalty = PlayerPenalty + Penalty - else - --ScoreMessageDestroys = ScoreMessageDestroys .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) - end - end - if ScoreMessageDestroys ~= "" then - ScoreMessage = "Destroys: " .. ScoreMessageDestroys - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - ---- Produce detailed report of player penalty scores because of changing the coalition. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerCoalitionChanges( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageCoalitionChangePenalties = "" - if PlayerData.PenaltyCoalition ~= 0 then - ScoreMessageCoalitionChangePenalties = ScoreMessageCoalitionChangePenalties .. string.format( " -%d (%d changed)", PlayerData.Penalty, PlayerData.PenaltyCoalition ) - PlayerPenalty = PlayerPenalty + PlayerData.Penalty - end - if ScoreMessageCoalitionChangePenalties ~= "" then - ScoreMessage = ScoreMessage .. "Coalition Penalties: " .. ScoreMessageCoalitionChangePenalties - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - ---- Produce detailed report of player goal scores. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerGoals( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageGoal = "" - local ScoreGoal = 0 - local ScoreTask = 0 - for GoalName, GoalData in pairs( PlayerData.Goals ) do - ScoreGoal = ScoreGoal + GoalData.Score - ScoreMessageGoal = ScoreMessageGoal .. "'" .. GoalName .. "':" .. GoalData.Score .. "; " - end - PlayerScore = PlayerScore + ScoreGoal - - if ScoreMessageGoal ~= "" then - ScoreMessage = "Goals: " .. ScoreMessageGoal - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - ---- Produce detailed report of player penalty scores because of changing the coalition. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerMissions( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageMission = "" - local ScoreMission = 0 - local ScoreTask = 0 - for MissionName, MissionData in pairs( PlayerData.Mission ) do - ScoreMission = ScoreMission + MissionData.ScoreMission - ScoreTask = ScoreTask + MissionData.ScoreTask - ScoreMessageMission = ScoreMessageMission .. "'" .. MissionName .. "'; " - end - PlayerScore = PlayerScore + ScoreMission + ScoreTask - - if ScoreMessageMission ~= "" then - ScoreMessage = "Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ")" - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - - ---- Report Group Score Summary --- @param #SCORING self --- @param Wrapper.Group#GROUP PlayerGroup The player group. -function SCORING:ReportScoreGroupSummary( PlayerGroup ) - - local PlayerMessage = "" - - self:T( "Report Score Group Summary" ) - - local PlayerUnits = PlayerGroup:GetUnits() - for UnitID, PlayerUnit in pairs( PlayerUnits ) do - local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT - local PlayerName = PlayerUnit:GetPlayerName() - - if PlayerName then - - local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits - self:E( { ReportHits, ScoreHits, PenaltyHits } ) - - local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) - ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys - self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } ) - - local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName ) - ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges - self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } ) - - local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName ) - ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals - self:E( { ReportGoals, ScoreGoals, PenaltyGoals } ) - - local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) - ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions - self:E( { ReportMissions, ScoreMissions, PenaltyMissions } ) - - local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions - local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty - ) - MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup ) - end - end - -end - ---- Report Group Score Detailed --- @param #SCORING self --- @param Wrapper.Group#GROUP PlayerGroup The player group. -function SCORING:ReportScoreGroupDetailed( PlayerGroup ) - - local PlayerMessage = "" - - self:T( "Report Score Group Detailed" ) - - local PlayerUnits = PlayerGroup:GetUnits() - for UnitID, PlayerUnit in pairs( PlayerUnits ) do - local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT - local PlayerName = PlayerUnit:GetPlayerName() - - if PlayerName then - - local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits - self:E( { ReportHits, ScoreHits, PenaltyHits } ) - - local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) - ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys - self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } ) - - local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName ) - ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges - self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } ) - - local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName ) - ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals - self:E( { ReportGoals, ScoreGoals, PenaltyGoals } ) - - local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) - ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions - self:E( { ReportMissions, ScoreMissions, PenaltyMissions } ) - - local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions - local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty, - ReportHits, - ReportDestroys, - ReportCoalitionChanges, - ReportGoals, - ReportMissions - ) - MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup ) - end - end - -end - ---- Report all players score --- @param #SCORING self --- @param Wrapper.Group#GROUP PlayerGroup The player group. -function SCORING:ReportScoreAllSummary( PlayerGroup ) - - local PlayerMessage = "" - - self:T( "Report Score All Players" ) - - for PlayerName, PlayerData in pairs( self.Players ) do - - if PlayerName then - - local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits - self:E( { ReportHits, ScoreHits, PenaltyHits } ) - - local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) - ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys - self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } ) - - local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName ) - ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges - self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } ) - - local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName ) - ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals - self:E( { ReportGoals, ScoreGoals, PenaltyGoals } ) - - local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) - ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions - self:E( { ReportMissions, ScoreMissions, PenaltyMissions } ) - - local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions - local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty - ) - MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup ) - end - end - -end - - -function SCORING:SecondsToClock(sSeconds) - local nSeconds = sSeconds - if nSeconds == 0 then - --return nil; - return "00:00:00"; - else - nHours = string.format("%02.f", math.floor(nSeconds/3600)); - nMins = string.format("%02.f", math.floor(nSeconds/60 - (nHours*60))); - nSecs = string.format("%02.f", math.floor(nSeconds - nHours*3600 - nMins *60)); - return nHours..":"..nMins..":"..nSecs - end -end - ---- Opens a score CSV file to log the scores. --- @param #SCORING self --- @param #string ScoringCSV --- @return #SCORING self --- @usage --- -- Open a new CSV file to log the scores of the game Gori Valley. Let the name of the CSV file begin with "Player Scores". --- ScoringObject = SCORING:New( "Gori Valley" ) --- ScoringObject:OpenCSV( "Player Scores" ) -function SCORING:OpenCSV( ScoringCSV ) - self:F( ScoringCSV ) - - if lfs and io and os then - if ScoringCSV then - self.ScoringCSV = ScoringCSV - local fdir = lfs.writedir() .. [[Logs\]] .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv" - - self.CSVFile, self.err = io.open( fdir, "w+" ) - if not self.CSVFile then - error( "Error: Cannot open CSV file in " .. lfs.writedir() ) - end - - self.CSVFile:write( '"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoaltion","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n' ) - - self.RunTime = os.date("%y-%m-%d_%H-%M-%S") - else - error( "A string containing the CSV file name must be given." ) - end - else - self:E( "The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used..." ) - end - return self -end - - ---- Registers a score for a player. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @param #string TargetPlayerName The name of the target player. --- @param #string ScoreType The type of the score. --- @param #string ScoreTimes The amount of scores achieved. --- @param #string ScoreAmount The score given. --- @param #string PlayerUnitName The unit name of the player. --- @param #string PlayerUnitCoalition The coalition of the player unit. --- @param #string PlayerUnitCategory The category of the player unit. --- @param #string PlayerUnitType The type of the player unit. --- @param #string TargetUnitName The name of the target unit. --- @param #string TargetUnitCoalition The coalition of the target unit. --- @param #string TargetUnitCategory The category of the target unit. --- @param #string TargetUnitType The type of the target unit. --- @return #SCORING self -function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, ScoreAmount, PlayerUnitName, PlayerUnitCoalition, PlayerUnitCategory, PlayerUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - --write statistic information to file - local ScoreTime = self:SecondsToClock( timer.getTime() ) - PlayerName = PlayerName:gsub( '"', '_' ) - - TargetPlayerName = TargetPlayerName or "" - TargetPlayerName = TargetPlayerName:gsub( '"', '_' ) - - if PlayerUnitName and PlayerUnitName ~= '' then - local PlayerUnit = Unit.getByName( PlayerUnitName ) - - if PlayerUnit then - if not PlayerUnitCategory then - --PlayerUnitCategory = SCORINGCategory[PlayerUnit:getCategory()] - PlayerUnitCategory = _SCORINGCategory[PlayerUnit:getDesc().category] - end - - if not PlayerUnitCoalition then - PlayerUnitCoalition = _SCORINGCoalition[PlayerUnit:getCoalition()] - end - - if not PlayerUnitType then - PlayerUnitType = PlayerUnit:getTypeName() - end - else - PlayerUnitName = '' - PlayerUnitCategory = '' - PlayerUnitCoalition = '' - PlayerUnitType = '' - end - else - PlayerUnitName = '' - PlayerUnitCategory = '' - PlayerUnitCoalition = '' - PlayerUnitType = '' - end - - TargetUnitCoalition = TargetUnitCoalition or "" - TargetUnitCategory = TargetUnitCategory or "" - TargetUnitType = TargetUnitType or "" - TargetUnitName = TargetUnitName or "" - - if lfs and io and os then - self.CSVFile:write( - '"' .. self.GameName .. '"' .. ',' .. - '"' .. self.RunTime .. '"' .. ',' .. - '' .. ScoreTime .. '' .. ',' .. - '"' .. PlayerName .. '"' .. ',' .. - '"' .. TargetPlayerName .. '"' .. ',' .. - '"' .. ScoreType .. '"' .. ',' .. - '"' .. PlayerUnitCoalition .. '"' .. ',' .. - '"' .. PlayerUnitCategory .. '"' .. ',' .. - '"' .. PlayerUnitType .. '"' .. ',' .. - '"' .. PlayerUnitName .. '"' .. ',' .. - '"' .. TargetUnitCoalition .. '"' .. ',' .. - '"' .. TargetUnitCategory .. '"' .. ',' .. - '"' .. TargetUnitType .. '"' .. ',' .. - '"' .. TargetUnitName .. '"' .. ',' .. - '' .. ScoreTimes .. '' .. ',' .. - '' .. ScoreAmount - ) - - self.CSVFile:write( "\n" ) - end -end - - -function SCORING:CloseCSV() - if lfs and io and os then - self.CSVFile:close() - end -end - ---- The CLEANUP class keeps an area clean of crashing or colliding airplanes. It also prevents airplanes from firing within this area. --- @module CleanUp --- @author Flightcontrol - - - - - - - ---- The CLEANUP class. --- @type CLEANUP --- @extends Core.Base#BASE -CLEANUP = { - ClassName = "CLEANUP", - ZoneNames = {}, - TimeInterval = 300, - CleanUpList = {}, -} - ---- Creates the main object which is handling the cleaning of the debris within the given Zone Names. --- @param #CLEANUP self --- @param #table ZoneNames Is a table of zone names where the debris should be cleaned. Also a single string can be passed with one zone name. --- @param #number TimeInterval The interval in seconds when the clean activity takes place. The default is 300 seconds, thus every 5 minutes. --- @return #CLEANUP --- @usage --- -- Clean these Zones. --- CleanUpAirports = CLEANUP:New( { 'CLEAN Tbilisi', 'CLEAN Kutaisi' }, 150 ) --- or --- CleanUpTbilisi = CLEANUP:New( 'CLEAN Tbilisi', 150 ) --- CleanUpKutaisi = CLEANUP:New( 'CLEAN Kutaisi', 600 ) -function CLEANUP:New( ZoneNames, TimeInterval ) - - local self = BASE:Inherit( self, BASE:New() ) -- #CLEANUP - self:F( { ZoneNames, TimeInterval } ) - - if type( ZoneNames ) == 'table' then - self.ZoneNames = ZoneNames - else - self.ZoneNames = { ZoneNames } - end - if TimeInterval then - self.TimeInterval = TimeInterval - end - - self:HandleEvent( EVENTS.Birth ) - - self.CleanUpScheduler = SCHEDULER:New( self, self._CleanUpScheduler, {}, 1, TimeInterval ) - - return self -end - - ---- Destroys a group from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param Dcs.DCSWrapper.Group#Group GroupObject The object to be destroyed. --- @param #string CleanUpGroupName The groupname... -function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) - self:F( { GroupObject, CleanUpGroupName } ) - - if GroupObject then -- and GroupObject:isExist() then - trigger.action.deactivateGroup(GroupObject) - self:T( { "GroupObject Destroyed", GroupObject } ) - end -end - ---- Destroys a @{DCSWrapper.Unit#Unit} from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param Dcs.DCSWrapper.Unit#Unit CleanUpUnit The object to be destroyed. --- @param #string CleanUpUnitName The Unit name ... -function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) - self:F( { CleanUpUnit, CleanUpUnitName } ) - - if CleanUpUnit then - local CleanUpGroup = Unit.getGroup(CleanUpUnit) - -- TODO Client bug in 1.5.3 - if CleanUpGroup and CleanUpGroup:isExist() then - local CleanUpGroupUnits = CleanUpGroup:getUnits() - if #CleanUpGroupUnits == 1 then - local CleanUpGroupName = CleanUpGroup:getName() - --self:CreateEventCrash( timer.getTime(), CleanUpUnit ) - CleanUpGroup:destroy() - self:T( { "Destroyed Group:", CleanUpGroupName } ) - else - CleanUpUnit:destroy() - self:T( { "Destroyed Unit:", CleanUpUnitName } ) - end - self.CleanUpList[CleanUpUnitName] = nil -- Cleaning from the list - CleanUpUnit = nil - end - end -end - --- TODO check Dcs.DCSTypes#Weapon ---- Destroys a missile from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param Dcs.DCSTypes#Weapon MissileObject -function CLEANUP:_DestroyMissile( MissileObject ) - self:F( { MissileObject } ) - - if MissileObject and MissileObject:isExist() then - MissileObject:destroy() - self:T( "MissileObject Destroyed") - end -end - ---- @param #CLEANUP self --- @param Core.Event#EVENTDATA EventData -function CLEANUP:_OnEventBirth( EventData ) - self:F( { EventData } ) - - self.CleanUpList[EventData.IniDCSUnitName] = {} - self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit = EventData.IniDCSUnit - self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup = EventData.IniDCSGroup - self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName = EventData.IniDCSGroupName - self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName = EventData.IniDCSUnitName - - EventData.IniUnit:HandleEvent( EVENTS.EngineShutdown , self._EventAddForCleanUp ) - EventData.IniUnit:HandleEvent( EVENTS.EngineStartup, self._EventAddForCleanUp ) - EventData.IniUnit:HandleEvent( EVENTS.Hit, self._EventAddForCleanUp ) - EventData.IniUnit:HandleEvent( EVENTS.PilotDead, self._EventCrash ) - EventData.IniUnit:HandleEvent( EVENTS.Dead, self._EventCrash ) - EventData.IniUnit:HandleEvent( EVENTS.Crash, self._EventCrash ) - EventData.IniUnit:HandleEvent( EVENTS.Shot, self._EventShot ) - -end - ---- Detects if a crash event occurs. --- Crashed units go into a CleanUpList for removal. --- @param #CLEANUP self --- @param Dcs.DCSTypes#Event event -function CLEANUP:_EventCrash( Event ) - self:F( { Event } ) - - --TODO: This stuff is not working due to a DCS bug. Burning units cannot be destroyed. - -- self:T("before getGroup") - -- local _grp = Unit.getGroup(event.initiator)-- Identify the group that fired - -- self:T("after getGroup") - -- _grp:destroy() - -- self:T("after deactivateGroup") - -- event.initiator:destroy() - - self.CleanUpList[Event.IniDCSUnitName] = {} - self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit = Event.IniDCSUnit - self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup = Event.IniDCSGroup - self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName = Event.IniDCSGroupName - self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName = Event.IniDCSUnitName - -end - ---- Detects if a unit shoots a missile. --- If this occurs within one of the zones, then the weapon used must be destroyed. --- @param #CLEANUP self --- @param Dcs.DCSTypes#Event event -function CLEANUP:_EventShot( Event ) - self:F( { Event } ) - - -- Test if the missile was fired within one of the CLEANUP.ZoneNames. - local CurrentLandingZoneID = 0 - CurrentLandingZoneID = routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) - if ( CurrentLandingZoneID ) then - -- Okay, the missile was fired within the CLEANUP.ZoneNames, destroy the fired weapon. - --_SEADmissile:destroy() - SCHEDULER:New( self, CLEANUP._DestroyMissile, { Event.Weapon }, 0.1 ) - end -end - - ---- Detects if the Unit has an S_EVENT_HIT within the given ZoneNames. If this is the case, destroy the unit. --- @param #CLEANUP self --- @param Dcs.DCSTypes#Event event -function CLEANUP:_EventHitCleanUp( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - if routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) ~= nil then - self:T( { "Life: ", Event.IniDCSUnitName, ' = ', Event.IniDCSUnit:getLife(), "/", Event.IniDCSUnit:getLife0() } ) - if Event.IniDCSUnit:getLife() < Event.IniDCSUnit:getLife0() then - self:T( "CleanUp: Destroy: " .. Event.IniDCSUnitName ) - SCHEDULER:New( self, CLEANUP._DestroyUnit, { Event.IniDCSUnit }, 0.1 ) - end - end - end - - if Event.TgtDCSUnit then - if routines.IsUnitInZones( Event.TgtDCSUnit, self.ZoneNames ) ~= nil then - self:T( { "Life: ", Event.TgtDCSUnitName, ' = ', Event.TgtDCSUnit:getLife(), "/", Event.TgtDCSUnit:getLife0() } ) - if Event.TgtDCSUnit:getLife() < Event.TgtDCSUnit:getLife0() then - self:T( "CleanUp: Destroy: " .. Event.TgtDCSUnitName ) - SCHEDULER:New( self, CLEANUP._DestroyUnit, { Event.TgtDCSUnit }, 0.1 ) - end - end - end -end - ---- Add the @{DCSWrapper.Unit#Unit} to the CleanUpList for CleanUp. -function CLEANUP:_AddForCleanUp( CleanUpUnit, CleanUpUnitName ) - self:F( { CleanUpUnit, CleanUpUnitName } ) - - self.CleanUpList[CleanUpUnitName] = {} - self.CleanUpList[CleanUpUnitName].CleanUpUnit = CleanUpUnit - self.CleanUpList[CleanUpUnitName].CleanUpUnitName = CleanUpUnitName - self.CleanUpList[CleanUpUnitName].CleanUpGroup = Unit.getGroup(CleanUpUnit) - self.CleanUpList[CleanUpUnitName].CleanUpGroupName = Unit.getGroup(CleanUpUnit):getName() - self.CleanUpList[CleanUpUnitName].CleanUpTime = timer.getTime() - self.CleanUpList[CleanUpUnitName].CleanUpMoved = false - - self:T( { "CleanUp: Add to CleanUpList: ", Unit.getGroup(CleanUpUnit):getName(), CleanUpUnitName } ) - -end - ---- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given ZoneNames. If this is the case, add the Group to the CLEANUP List. --- @param #CLEANUP self --- @param Dcs.DCSTypes#Event event -function CLEANUP:_EventAddForCleanUp( Event ) - - if Event.IniDCSUnit then - if self.CleanUpList[Event.IniDCSUnitName] == nil then - if routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) ~= nil then - self:_AddForCleanUp( Event.IniDCSUnit, Event.IniDCSUnitName ) - end - end - end - - if Event.TgtDCSUnit then - if self.CleanUpList[Event.TgtDCSUnitName] == nil then - if routines.IsUnitInZones( Event.TgtDCSUnit, self.ZoneNames ) ~= nil then - self:_AddForCleanUp( Event.TgtDCSUnit, Event.TgtDCSUnitName ) - end - end - end - -end - -local CleanUpSurfaceTypeText = { - "LAND", - "SHALLOW_WATER", - "WATER", - "ROAD", - "RUNWAY" - } - ---- At the defined time interval, CleanUp the Groups within the CleanUpList. --- @param #CLEANUP self -function CLEANUP:_CleanUpScheduler() - self:F( { "CleanUp Scheduler" } ) - - local CleanUpCount = 0 - for CleanUpUnitName, UnitData in pairs( self.CleanUpList ) do - CleanUpCount = CleanUpCount + 1 - - self:T( { CleanUpUnitName, UnitData } ) - local CleanUpUnit = Unit.getByName(UnitData.CleanUpUnitName) - local CleanUpGroupName = UnitData.CleanUpGroupName - local CleanUpUnitName = UnitData.CleanUpUnitName - if CleanUpUnit then - self:T( { "CleanUp Scheduler", "Checking:", CleanUpUnitName } ) - if _DATABASE:GetStatusGroup( CleanUpGroupName ) ~= "ReSpawn" then - local CleanUpUnitVec3 = CleanUpUnit:getPoint() - --self:T( CleanUpUnitVec3 ) - local CleanUpUnitVec2 = {} - CleanUpUnitVec2.x = CleanUpUnitVec3.x - CleanUpUnitVec2.y = CleanUpUnitVec3.z - --self:T( CleanUpUnitVec2 ) - local CleanUpSurfaceType = land.getSurfaceType(CleanUpUnitVec2) - --self:T( CleanUpSurfaceType ) - - if CleanUpUnit and CleanUpUnit:getLife() <= CleanUpUnit:getLife0() * 0.95 then - if CleanUpSurfaceType == land.SurfaceType.RUNWAY then - if CleanUpUnit:inAir() then - local CleanUpLandHeight = land.getHeight(CleanUpUnitVec2) - local CleanUpUnitHeight = CleanUpUnitVec3.y - CleanUpLandHeight - self:T( { "CleanUp Scheduler", "Height = " .. CleanUpUnitHeight } ) - if CleanUpUnitHeight < 30 then - self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because below safe height and damaged." } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - else - self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because on runway and damaged." } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - end - end - -- Clean Units which are waiting for a very long time in the CleanUpZone. - if CleanUpUnit then - local CleanUpUnitVelocity = CleanUpUnit:getVelocity() - local CleanUpUnitVelocityTotal = math.abs(CleanUpUnitVelocity.x) + math.abs(CleanUpUnitVelocity.y) + math.abs(CleanUpUnitVelocity.z) - if CleanUpUnitVelocityTotal < 1 then - if UnitData.CleanUpMoved then - if UnitData.CleanUpTime + 180 <= timer.getTime() then - self:T( { "CleanUp Scheduler", "Destroy due to not moving anymore " .. CleanUpUnitName } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - end - else - UnitData.CleanUpTime = timer.getTime() - UnitData.CleanUpMoved = true - end - end - - else - -- Do nothing ... - self.CleanUpList[CleanUpUnitName] = nil -- Not anymore in the DCSRTE - end - else - self:T( "CleanUp: Group " .. CleanUpUnitName .. " cannot be found in DCS RTE, removing ..." ) - self.CleanUpList[CleanUpUnitName] = nil -- Not anymore in the DCSRTE - end - end - self:T(CleanUpCount) - - return true -end - ---- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**All** -- --- **Spawn groups of units dynamically in your missions.** --- --- ![Banner Image](..\Presentations\SPAWN\SPAWN.JPG) --- --- === --- --- # 1) @{#SPAWN} class, extends @{Base#BASE} --- --- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned. --- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object. --- A reference to this Spawn Template needs to be provided when constructing the SPAWN object, by indicating the name of the group within the mission editor in the constructor methods. --- --- Within the SPAWN object, there is an internal index that keeps track of which group from the internal group list was spawned. --- When new groups get spawned by using the SPAWN methods (see below), it will be validated whether the Limits (@{#SPAWN.Limit}) of the SPAWN object are not reached. --- When all is valid, a new group will be created by the spawning methods, and the internal index will be increased with 1. --- --- Regarding the name of new spawned groups, a _SpawnPrefix_ will be assigned for each new group created. --- If you want to have the Spawn Template name to be used as the _SpawnPrefix_ name, use the @{#SPAWN.New} constructor. --- However, when the @{#SPAWN.NewWithAlias} constructor was used, the Alias name will define the _SpawnPrefix_ name. --- Groups will follow the following naming structure when spawned at run-time: --- --- 1. Spawned groups will have the name _SpawnPrefix_#ggg, where ggg is a counter from 0 to 999. --- 2. Spawned units will have the name _SpawnPrefix_#ggg-uu, where uu is a counter from 0 to 99 for each new spawned unit belonging to the group. --- --- Some additional notes that need to be remembered: --- --- * Templates are actually groups defined within the mission editor, with the flag "Late Activation" set. As such, these groups are never used within the mission, but are used by the @{#SPAWN} module. --- * It is important to defined BEFORE you spawn new groups, a proper initialization of the SPAWN instance is done with the options you want to use. --- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn Template(s), or the SPAWN module logic won't work anymore. --- --- ## 1.1) SPAWN construction methods --- --- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods: --- --- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). --- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition), and gives each spawned @{Group} an different name. --- --- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned. --- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons. --- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient. --- --- ## 1.2) SPAWN initialization methods --- --- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: --- --- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!! --- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height. --- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled. --- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- * @{#SPAWN.InitRepeat}(): Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. --- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. --- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius. --- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. --- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object. --- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object. --- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object. --- --- ## 1.3) SPAWN spawning methods --- --- Groups can be spawned at different times and methods: --- --- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index. --- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index. --- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart}() and @{#SPAWN.SpawnScheduleStop}() to start and stop the schedule respectively. --- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). --- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). --- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}. --- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}. --- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. --- --- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object. --- You can use the @{GROUP} object to do further actions with the DCSGroup. --- --- ## 1.4) Retrieve alive GROUPs spawned by the SPAWN object --- --- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution. --- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS. --- SPAWN provides methods to iterate through that internal GROUP object reference table: --- --- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. --- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. --- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. --- --- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. --- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive... --- --- ## 1.5) SPAWN object cleaning --- --- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive. --- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, --- and it may occur that no new groups are or can be spawned as limits are reached. --- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group. --- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. --- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... --- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. --- This models AI that has succesfully returned to their airbase, to restart their combat activities. --- Check the @{#SPAWN.InitCleanUp}() for further info. --- --- ## 1.6) Catch the @{Group} spawn event in a callback function! --- --- When using the SpawnScheduled method, new @{Group}s are created following the schedule timing parameters. --- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. --- To SPAWN class supports this functionality through the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method, which takes a function as a parameter that you can define locally. --- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter. --- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object. --- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-14: SPAWN:**InitKeepUnitNames()** added. --- 2017-03-14: SPAWN:**InitRandomizePosition( RandomizePosition, OuterRadious, InnerRadius )** added. --- --- 2017-02-04: SPAWN:InitUnControlled( **UnControlled** ) replaces SPAWN:InitUnControlled(). --- --- 2017-01-24: SPAWN:**InitAIOnOff( AIOnOff )** added. --- --- 2017-01-24: SPAWN:**InitAIOn()** added. --- --- 2017-01-24: SPAWN:**InitAIOff()** added. --- --- 2016-08-15: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ). --- --- 2016-08-15: SPAWN:**InitRandomizeZones( SpawnZones )** added. --- --- 2016-08-14: SPAWN:**OnSpawnGroup**( SpawnCallBackFunction, ... ) replaces SPAWN:_SpawnFunction_( SpawnCallBackFunction, ... ). --- --- 2016-08-14: SPAWN.SpawnInZone( Zone, __RandomizeGroup__, SpawnIndex ) replaces SpawnInZone( Zone, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ). --- --- 2016-08-14: SPAWN.SpawnFromVec3( Vec3, SpawnIndex ) replaces SpawnFromVec3( Vec3, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): --- --- 2016-08-14: SPAWN.SpawnFromVec2( Vec2, SpawnIndex ) replaces SpawnFromVec2( Vec2, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): --- --- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromUnit( SpawnUnit, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): --- --- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromStatic( SpawnStatic, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): --- --- 2016-08-14: SPAWN.**InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )** added: --- --- 2016-08-14: SPAWN.**Init**Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) replaces SPAWN._Limit_( SpawnMaxUnitsAlive, SpawnMaxGroups ): --- --- 2016-08-14: SPAWN.**Init**Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) replaces SPAWN._Array_( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ). --- --- 2016-08-14: SPAWN.**Init**RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) replaces SPAWN._RandomizeRoute_( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ). --- --- 2016-08-14: SPAWN.**Init**RandomizeTemplate( SpawnTemplatePrefixTable ) replaces SPAWN._RandomizeTemplate_( SpawnTemplatePrefixTable ). --- --- 2016-08-14: SPAWN.**Init**UnControlled() replaces SPAWN._UnControlled_(). --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization. --- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff(). --- --- ### Authors: --- --- * **FlightControl**: Design & Programming --- --- @module Spawn - - - ---- SPAWN Class --- @type SPAWN --- @extends Core.Base#BASE --- @field ClassName --- @field #string SpawnTemplatePrefix --- @field #string SpawnAliasPrefix --- @field #number AliveUnits --- @field #number MaxAliveUnits --- @field #number SpawnIndex --- @field #number MaxAliveGroups --- @field #SPAWN.SpawnZoneTable SpawnZoneTable -SPAWN = { - ClassName = "SPAWN", - SpawnTemplatePrefix = nil, - SpawnAliasPrefix = nil, -} - - ---- @type SPAWN.SpawnZoneTable --- @list SpawnZone - - ---- Creates the main object to spawn a @{Group} defined in the DCS ME. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ) --- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME. -function SPAWN:New( SpawnTemplatePrefix ) - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN - self:F( { SpawnTemplatePrefix } ) - - local TemplateGroup = Group.getByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. - self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end - - return self -end - ---- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. --- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) --- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME. -function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) - - local TemplateGroup = Group.getByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnAliasPrefix = SpawnAliasPrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. - self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end - - return self -end - - ---- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned. --- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. --- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used... --- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. --- @param #SPAWN self --- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. --- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group. --- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area. --- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time. --- @return #SPAWN self --- @usage --- -- NATO helicopters engaging in the battle field. --- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. --- -- There will be maximum 24 groups spawned during the whole mission lifetime. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) -function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) - self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) - - self.SpawnInitLimit = true - self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned. - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_InitializeSpawnGroups( SpawnGroupID ) - end - - return self -end - ---- Keeps the unit names as defined within the mission editor, --- but note that anything after a # mark is ignored, --- and any spaces before and after the resulting name are removed. --- IMPORTANT! This method MUST be the first used after :New !!! --- @param #SPAWN self --- @return #SPAWN self -function SPAWN:InitKeepUnitNames() - self:F( ) - - self.SpawnInitKeepUnitNames = true - - return self -end - - ---- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups. --- @param #SPAWN self --- @param #number SpawnStartPoint is the waypoint where the randomization begins. --- Note that the StartPoint = 0 equaling the point where the group is spawned. --- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards. --- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route. --- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ... --- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) -function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) - self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) - - self.SpawnRandomizeRoute = true - self.SpawnRandomizeRouteStartPoint = SpawnStartPoint - self.SpawnRandomizeRouteEndPoint = SpawnEndPoint - self.SpawnRandomizeRouteRadius = SpawnRadius - self.SpawnRandomizeRouteHeight = SpawnHeight - - for GroupID = 1, self.SpawnMaxGroups do - self:_RandomizeRoute( GroupID ) - end - - return self -end - ---- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. --- @param #SPAWN self --- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius. --- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. --- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. --- @return #SPAWN -function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius ) - self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } ) - - self.SpawnRandomizePosition = RandomizePosition or false - self.SpawnRandomizePositionOuterRadius = OuterRadius or 0 - self.SpawnRandomizePositionInnerRadius = InnerRadius or 0 - - for GroupID = 1, self.SpawnMaxGroups do - self:_RandomizeRoute( GroupID ) - end - - return self -end - - ---- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. --- @param #SPAWN self --- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. --- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. --- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) -function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) - self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) - - self.SpawnRandomizeUnits = RandomizeUnits or false - self.SpawnOuterRadius = OuterRadius or 0 - self.SpawnInnerRadius = InnerRadius or 0 - - for GroupID = 1, self.SpawnMaxGroups do - self:_RandomizeRoute( GroupID ) - end - - return self -end - ---- This method is rather complicated to understand. But I'll try to explain. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, --- but they will all follow the same Template route and have the same prefix name. --- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. --- @param #SPAWN self --- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned. --- @return #SPAWN --- @usage --- -- NATO Tank Platoons invading Gori. --- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes. --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', --- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', --- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) -function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) - self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) - - self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable - self.SpawnRandomizeTemplate = true - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeTemplate( SpawnGroupID ) - end - - return self -end - ---TODO: Add example. ---- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. --- @param #SPAWN self --- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. --- @return #SPAWN --- @usage --- -- NATO Tank Platoons invading Gori. --- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type. -function SPAWN:InitRandomizeZones( SpawnZoneTable ) - self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) - - self.SpawnZoneTable = SpawnZoneTable - self.SpawnRandomizeZones = true - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeZones( SpawnGroupID ) - end - - return self -end - - - - - ---- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. --- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... --- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. --- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ... --- @param #SPAWN self --- @return #SPAWN self --- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() -function SPAWN:InitRepeat() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) - - self.Repeat = true - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self -end - ---- Respawn group after landing. --- @param #SPAWN self --- @return #SPAWN self -function SPAWN:InitRepeatOnLanding() - self:F( { self.SpawnTemplatePrefix } ) - - self:InitRepeat() - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self -end - - ---- Respawn after landing when its engines have shut down. --- @param #SPAWN self --- @return #SPAWN self -function SPAWN:InitRepeatOnEngineShutDown() - self:F( { self.SpawnTemplatePrefix } ) - - self:InitRepeat() - self.RepeatOnEngineShutDown = true - self.RepeatOnLanding = false - - return self -end - - ---- CleanUp groups when they are still alive, but inactive. --- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds. --- @param #SPAWN self --- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds. --- @return #SPAWN self --- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. -function SPAWN:InitCleanUp( SpawnCleanUpInterval ) - self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) - - self.SpawnCleanUpInterval = SpawnCleanUpInterval - self.SpawnCleanUpTimeStamps = {} - - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup } ) - - --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) - self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) - return self -end - - - ---- Makes the groups visible before start (like a batallion). --- The method will take the position of the group as the first position in the array. --- @param #SPAWN self --- @param #number SpawnAngle The angle in degrees how the groups and each unit of the group will be positioned. --- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis. --- @param #number SpawnDeltaX The space between each Group on the X-axis. --- @param #number SpawnDeltaY The space between each Group on the Y-axis. --- @return #SPAWN self --- @usage --- -- Define an array of Groups. --- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 ) -function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) - self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) - - self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. - - local SpawnX = 0 - local SpawnY = 0 - local SpawnXIndex = 0 - local SpawnYIndex = 0 - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) - - self.SpawnGroups[SpawnGroupID].Visible = true - self.SpawnGroups[SpawnGroupID].Spawned = false - - SpawnXIndex = SpawnXIndex + 1 - if SpawnWidth and SpawnWidth ~= 0 then - if SpawnXIndex >= SpawnWidth then - SpawnXIndex = 0 - SpawnYIndex = SpawnYIndex + 1 - end - end - - local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x - local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y - - self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - - self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true - self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true - - self.SpawnGroups[SpawnGroupID].Visible = true - - _EVENTDISPATCHER:OnBirthForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self ) - - if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnLand, self ) - end - if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnEngineShutDown, self ) - end - - self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate ) - - SpawnX = SpawnXIndex * SpawnDeltaX - SpawnY = SpawnYIndex * SpawnDeltaY - end - - return self -end - -do -- AI methods - --- Turns the AI On or Off for the @{Group} when spawning. - -- @param #SPAWN self - -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off. - -- @return #SPAWN The SPAWN object - function SPAWN:InitAIOnOff( AIOnOff ) - - self.AIOnOff = AIOnOff - return self - end - - --- Turns the AI On for the @{Group} when spawning. - -- @param #SPAWN self - -- @return #SPAWN The SPAWN object - function SPAWN:InitAIOn() - - return self:InitAIOnOff( true ) - end - - --- Turns the AI Off for the @{Group} when spawning. - -- @param #SPAWN self - -- @return #SPAWN The SPAWN object - function SPAWN:InitAIOff() - - return self:InitAIOnOff( false ) - end - -end -- AI methods - ---- Will spawn a group based on the internal index. --- Note: Uses @{DATABASE} module defined in MOOSE. --- @param #SPAWN self --- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:Spawn() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) - - return self:SpawnWithIndex( self.SpawnIndex + 1 ) -end - ---- Will re-spawn a group based on a given index. --- Note: Uses @{DATABASE} module defined in MOOSE. --- @param #SPAWN self --- @param #string SpawnIndex The index of the group to be spawned. --- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:ReSpawn( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end - --- TODO: This logic makes DCS crash and i don't know why (yet). - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil - if SpawnGroup then - local SpawnDCSGroup = SpawnGroup:GetDCSObject() - if SpawnDCSGroup then - SpawnGroup:Destroy() - end - end - - local SpawnGroup = self:SpawnWithIndex( SpawnIndex ) - if SpawnGroup and WayPoints then - -- If there were WayPoints set, then Re-Execute those WayPoints! - SpawnGroup:WayPointInitialize( WayPoints ) - SpawnGroup:WayPointExecute( 1, 5 ) - end - - if SpawnGroup.ReSpawnFunction then - SpawnGroup:ReSpawnFunction() - end - - return SpawnGroup -end - ---- Will spawn a group with a specified index number. --- Uses @{DATABASE} global object defined in MOOSE. --- @param #SPAWN self --- @param #string SpawnIndex The index of the group to be spawned. --- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:SpawnWithIndex( SpawnIndex ) - self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) - - if self:_GetSpawnIndex( SpawnIndex ) then - - if self.SpawnGroups[self.SpawnIndex].Visible then - self.SpawnGroups[self.SpawnIndex].Group:Activate() - else - - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - self:T( SpawnTemplate.name ) - - if SpawnTemplate then - - local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) - self:T( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) - - -- If RandomizePosition, then Randomize the formation in the zone band, keeping the template. - if self.SpawnRandomizePosition then - local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnRandomizePositionOuterRadius, self.SpawnRandomizePositionInnerRadius ) - local CurrentX = SpawnTemplate.units[1].x - local CurrentY = SpawnTemplate.units[1].y - SpawnTemplate.x = RandomVec2.x - SpawnTemplate.y = RandomVec2.y - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].x = SpawnTemplate.units[UnitID].x + ( RandomVec2.x - CurrentX ) - SpawnTemplate.units[UnitID].y = SpawnTemplate.units[UnitID].y + ( RandomVec2.y - CurrentY ) - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - end - - -- If RandomizeUnits, then Randomize the formation at the start point. - if self.SpawnRandomizeUnits then - for UnitID = 1, #SpawnTemplate.units do - local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) - SpawnTemplate.units[UnitID].x = RandomVec2.x - SpawnTemplate.units[UnitID].y = RandomVec2.y - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - end - - if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then - if SpawnTemplate.route.points[1].type == "TakeOffParking" then - SpawnTemplate.uncontrolled = self.SpawnUnControlled - end - end - end - - _EVENTDISPATCHER:OnBirthForTemplate( SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) - - if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( SpawnTemplate, self._OnLand, self ) - end - if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( SpawnTemplate, self._OnEngineShutDown, self ) - end - self:T3( SpawnTemplate.name ) - - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) - - local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP - - --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there! - if SpawnGroup then - - SpawnGroup:SetAIOnOff( self.AIOnOff ) - end - - -- If there is a SpawnFunction hook defined, call it. - if self.SpawnFunctionHook then - self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) ) - end - -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. - --if self.Repeat then - -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) - --end - end - - self.SpawnGroups[self.SpawnIndex].Spawned = true - return self.SpawnGroups[self.SpawnIndex].Group - else - --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) - end - - return nil -end - ---- Spawns new groups at varying time intervals. --- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions. --- @param #SPAWN self --- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. --- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. --- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval. --- @return #SPAWN self --- @usage --- -- NATO helicopters engaging in the battle field. --- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%. --- -- The time variation in this case will be between 450 seconds and 750 seconds. --- -- This is calculated as follows: --- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450 --- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750 --- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 ) -function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) - self:F( { SpawnTime, SpawnTimeVariation } ) - - if SpawnTime ~= nil and SpawnTimeVariation ~= nil then - self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, 1, SpawnTime, SpawnTimeVariation ) - end - - return self -end - ---- Will re-start the spawning scheduler. --- Note: This method is only required to be called when the schedule was stopped. -function SPAWN:SpawnScheduleStart() - self:F( { self.SpawnTemplatePrefix } ) - - self.SpawnScheduler:Start() -end - ---- Will stop the scheduled spawning scheduler. -function SPAWN:SpawnScheduleStop() - self:F( { self.SpawnTemplatePrefix } ) - - self.SpawnScheduler:Stop() -end - - ---- Allows to place a CallFunction hook when a new group spawns. --- The provided method will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned. --- @param #SPAWN self --- @param #function SpawnCallBackFunction The function to be called when a group spawns. --- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. --- @return #SPAWN --- @usage --- -- Declare SpawnObject and call a function when a new Group is spawned. --- local SpawnObject = SPAWN --- :New( "SpawnObject" ) --- :InitLimit( 2, 10 ) --- :OnSpawnGroup( --- function( SpawnGroup ) --- SpawnGroup:E( "I am spawned" ) --- end --- ) --- :SpawnScheduled( 300, 0.3 ) -function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) - self:F( "OnSpawnGroup" ) - - self.SpawnFunctionHook = SpawnCallBackFunction - self.SpawnFunctionArguments = {} - if arg then - self.SpawnFunctionArguments = arg - end - - return self -end - - ---- Will spawn a group from a Vec3 in 3D space. --- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. --- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) - - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2(PointVec3) - - if SpawnIndex then - else - SpawnIndex = self.SpawnIndex + 1 - end - - if self:_GetSpawnIndex( SpawnIndex ) then - - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - - if SpawnTemplate then - - self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) - - -- Translate the position of the Group Template to the Vec3. - for UnitID = 1, #SpawnTemplate.units do - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - local UnitTemplate = SpawnTemplate.units[UnitID] - local SX = UnitTemplate.x - local SY = UnitTemplate.y - local BX = SpawnTemplate.route.points[1].x - local BY = SpawnTemplate.route.points[1].y - local TX = Vec3.x + ( SX - BX ) - local TY = Vec3.z + ( SY - BY ) - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY - SpawnTemplate.units[UnitID].alt = Vec3.y - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - - SpawnTemplate.route.points[1].x = Vec3.x - SpawnTemplate.route.points[1].y = Vec3.z - SpawnTemplate.route.points[1].alt = Vec3.y - - SpawnTemplate.x = Vec3.x - SpawnTemplate.y = Vec3.z - - return self:SpawnWithIndex( self.SpawnIndex ) - end - end - - return nil -end - ---- Will spawn a group from a Vec2 in 3D space. --- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. --- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec2( Vec2, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } ) - - local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex ) -end - - ---- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. --- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } ) - - if HostUnit and HostUnit:IsAlive() then -- and HostUnit:getUnit(1):inAir() == false then - return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex ) - end - - return nil -end - ---- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings). --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } ) - - if HostStatic and HostStatic:IsAlive() then - return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex ) - end - - return nil -end - ---- Will spawn a Group within a given @{Zone}. --- The @{Zone} can be of any type derived from @{Zone#ZONE_BASE}. --- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route. --- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates. --- @param #SPAWN self --- @param Core.Zone#ZONE Zone The zone where the group is to be spawned. --- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil when nothing was spawned. -function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } ) - - if Zone then - if RandomizeGroup then - return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex ) - else - return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex ) - end - end - - return nil -end - ---- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode... --- This will be similar to the uncontrolled flag setting in the ME. --- You can use UnControlled mode to simulate planes startup and ready for take-off but aren't moving (yet). --- ReSpawn the plane in Controlled mode, and the plane will move... --- @param #SPAWN self --- @param #boolean UnControlled true if UnControlled, false if Controlled. --- @return #SPAWN self -function SPAWN:InitUnControlled( UnControlled ) - self:F2( { self.SpawnTemplatePrefix, UnControlled } ) - - self.SpawnUnControlled = UnControlled - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self.SpawnGroups[SpawnGroupID].UnControlled = UnControlled - end - - return self -end - - - ---- Will return the SpawnGroupName either with with a specific count number or without any count. --- @param #SPAWN self --- @param #number SpawnIndex Is the number of the Group that is to be spawned. --- @return #string SpawnGroupName -function SPAWN:SpawnGroupName( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - local SpawnPrefix = self.SpawnTemplatePrefix - if self.SpawnAliasPrefix then - SpawnPrefix = self.SpawnAliasPrefix - end - - if SpawnIndex then - local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex ) - self:T( SpawnName ) - return SpawnName - else - self:T( SpawnPrefix ) - return SpawnPrefix - end - -end - ---- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found. --- @param #SPAWN self --- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found. --- @return #nil, #nil When no group is found, #nil is returned. --- @usage --- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end -function SPAWN:GetFirstAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - for SpawnIndex = 1, self.SpawnCount do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - return SpawnGroup, SpawnIndex - end - end - - return nil, nil -end - - ---- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found. --- @param #SPAWN self --- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index. --- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found. --- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned. --- @usage --- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end -function SPAWN:GetNextAliveGroup( SpawnIndexStart ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) - - SpawnIndexStart = SpawnIndexStart + 1 - for SpawnIndex = SpawnIndexStart, self.SpawnCount do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - return SpawnGroup, SpawnIndex - end - end - - return nil, nil -end - ---- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found. --- @param #SPAWN self --- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found. --- @return #nil, #nil When no alive @{Group} object is found, #nil is returned. --- @usage --- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() --- if GroupPlane then -- GroupPlane can be nil!!! --- -- Do actions with the GroupPlane object. --- end -function SPAWN:GetLastAliveGroup() - self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } ) - - self.SpawnIndex = self:_GetLastIndex() - for SpawnIndex = self.SpawnIndex, 1, -1 do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - self.SpawnIndex = SpawnIndex - return SpawnGroup - end - end - - self.SpawnIndex = nil - return nil -end - - - ---- Get the group from an index. --- Returns the group from the SpawnGroups list. --- If no index is given, it will return the first group in the list. --- @param #SPAWN self --- @param #number SpawnIndex The index of the group to return. --- @return Wrapper.Group#GROUP self -function SPAWN:GetGroupFromIndex( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end - - if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then - local SpawnGroup = self.SpawnGroups[SpawnIndex].Group - return SpawnGroup - else - return nil - end -end - ---- Get the group index from a DCSUnit. --- The method will search for a #-mark, and will return the index behind the #-mark of the DCSUnit. --- It will return nil of no prefix was found. --- @param #SPAWN self --- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return #string The prefix --- @return #nil Nothing found -function SPAWN:_GetGroupIndexFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local SpawnUnitName = ( DCSUnit and DCSUnit:getName() ) or nil - if SpawnUnitName then - local IndexString = string.match( SpawnUnitName, "#.*-" ):sub( 2, -2 ) - if IndexString then - local Index = tonumber( IndexString ) - return Index - end - end - - return nil -end - ---- Return the prefix of a SpawnUnit. --- The method will search for a #-mark, and will return the text before the #-mark. --- It will return nil of no prefix was found. --- @param #SPAWN self --- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched. --- @return #string The prefix --- @return #nil Nothing found -function SPAWN:_GetPrefixFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local DCSGroup = DCSUnit:getGroup() - local DCSUnitName = ( DCSGroup and DCSGroup:getName() ) or nil - if DCSUnitName then - local SpawnPrefix = string.match( DCSUnitName, ".*#" ) - if SpawnPrefix then - SpawnPrefix = SpawnPrefix:sub( 1, -2 ) - end - return SpawnPrefix - end - - return nil -end - ---- Return the group within the SpawnGroups collection with input a DCSUnit. --- @param #SPAWN self --- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return Wrapper.Group#GROUP The Group --- @return #nil Nothing found -function SPAWN:_GetGroupFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local SpawnPrefix = self:_GetPrefixFromDCSUnit( DCSUnit ) - - if self.SpawnTemplatePrefix == SpawnPrefix or ( self.SpawnAliasPrefix and self.SpawnAliasPrefix == SpawnPrefix ) then - local SpawnGroupIndex = self:_GetGroupIndexFromDCSUnit( DCSUnit ) - local SpawnGroup = self.SpawnGroups[SpawnGroupIndex].Group - self:T( SpawnGroup ) - return SpawnGroup - end - - return nil -end - - ---- Get the index from a given group. --- The function will search the name of the group for a #, and will return the number behind the #-mark. -function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) - - local IndexString = string.match( SpawnGroup:GetName(), "#.*$" ):sub( 2 ) - local Index = tonumber( IndexString ) - - self:T3( IndexString, Index ) - return Index - -end - ---- Return the last maximum index that can be used. -function SPAWN:_GetLastIndex() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - return self.SpawnMaxGroups -end - ---- Initalize the SpawnGroups collection. --- @param #SPAWN self -function SPAWN:_InitializeSpawnGroups( SpawnIndex ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) - - if not self.SpawnGroups[SpawnIndex] then - self.SpawnGroups[SpawnIndex] = {} - self.SpawnGroups[SpawnIndex].Visible = false - self.SpawnGroups[SpawnIndex].Spawned = false - self.SpawnGroups[SpawnIndex].UnControlled = false - self.SpawnGroups[SpawnIndex].SpawnTime = 0 - - self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix - self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) - end - - self:_RandomizeTemplate( SpawnIndex ) - self:_RandomizeRoute( SpawnIndex ) - --self:_TranslateRotate( SpawnIndex ) - - return self.SpawnGroups[SpawnIndex] -end - - - ---- Gets the CategoryID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCategoryID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCategory() - else - return nil - end -end - ---- Gets the CoalitionID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCoalitionID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCoalition() - else - return nil - end -end - ---- Gets the CountryID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCountryID( SpawnPrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } ) - - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - local TemplateUnits = TemplateGroup:getUnits() - return TemplateUnits[1]:getCountry() - else - return nil - end -end - ---- Gets the Group Template from the ME environment definition. --- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix --- @return @SPAWN self -function SPAWN:_GetTemplate( SpawnTemplatePrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) - - local SpawnTemplate = nil - - SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) - - if SpawnTemplate == nil then - error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) - end - - --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) - - self:T3( { SpawnTemplate } ) - return SpawnTemplate -end - ---- Prepares the new Group Template. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix --- @param #number SpawnIndex --- @return #SPAWN self -function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) - SpawnTemplate.name = self:SpawnGroupName( SpawnIndex ) - - SpawnTemplate.groupId = nil - --SpawnTemplate.lateActivation = false - SpawnTemplate.lateActivation = false - - if SpawnTemplate.CategoryID == Group.Category.GROUND then - self:T3( "For ground units, visible needs to be false..." ) - SpawnTemplate.visible = false - end - - if self.SpawnInitKeepUnitNames == false then - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) - SpawnTemplate.units[UnitID].unitId = nil - end - else - for UnitID = 1, #SpawnTemplate.units do - local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) - self:T( { UnitPrefix, Rest } ) - - SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) - SpawnTemplate.units[UnitID].unitId = nil - end - end - - self:T3( { "Template:", SpawnTemplate } ) - return SpawnTemplate - -end - ---- Private method randomizing the routes. --- @param #SPAWN self --- @param #number SpawnIndex The index of the group to be spawned. --- @return #SPAWN -function SPAWN:_RandomizeRoute( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) - - if self.SpawnRandomizeRoute then - local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate - local RouteCount = #SpawnTemplate.route.points - - for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do - - SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) - SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) - - -- Manage randomization of altitude for airborne units ... - if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then - if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then - SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight ) - end - else - SpawnTemplate.route.points[t].alt = nil - end - - self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) - end - end - - self:_RandomizeZones( SpawnIndex ) - - return self -end - ---- Private method that randomizes the template of the group. --- @param #SPAWN self --- @param #number SpawnIndex --- @return #SPAWN self -function SPAWN:_RandomizeTemplate( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) - - if self.SpawnRandomizeTemplate then - self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ] - self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x - self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y - self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time - local OldX = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x - local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y - for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt - end - end - - self:_RandomizeRoute( SpawnIndex ) - - return self -end - ---- Private method that randomizes the @{Zone}s where the Group will be spawned. --- @param #SPAWN self --- @param #number SpawnIndex --- @return #SPAWN self -function SPAWN:_RandomizeZones( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) - - if self.SpawnRandomizeZones then - local SpawnZone = nil -- Core.Zone#ZONE_BASE - while not SpawnZone do - self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) - local ZoneID = math.random( #self.SpawnZoneTable ) - self:T( ZoneID ) - SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe() - end - - self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) - - local SpawnVec2 = SpawnZone:GetRandomVec2() - - self:T( { SpawnVec2 = SpawnVec2 } ) - - local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate - - self:T( { Route = SpawnTemplate.route } ) - - for UnitID = 1, #SpawnTemplate.units do - local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) - local SX = UnitTemplate.x - local SY = UnitTemplate.y - local BX = SpawnTemplate.route.points[1].x - local BY = SpawnTemplate.route.points[1].y - local TX = SpawnVec2.x + ( SX - BX ) - local TY = SpawnVec2.y + ( SY - BY ) - UnitTemplate.x = TX - UnitTemplate.y = TY - -- TODO: Manage altitude based on landheight... - --SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) - end - SpawnTemplate.x = SpawnVec2.x - SpawnTemplate.y = SpawnVec2.y - SpawnTemplate.route.points[1].x = SpawnVec2.x - SpawnTemplate.route.points[1].y = SpawnVec2.y - end - - return self - -end - -function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) - - -- Translate - local TranslatedX = SpawnX - local TranslatedY = SpawnY - - -- Rotate - -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations - -- x' = x \cos \theta - y \sin \theta\ - -- y' = x \sin \theta + y \cos \theta\ - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - - -- Assign - self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX - self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY - - - local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units ) - for u = 1, SpawnUnitCount do - - -- Translate - local TranslatedX = SpawnX - local TranslatedY = SpawnY - 10 * ( u - 1 ) - - -- Rotate - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - - -- Assign - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle ) - end - - return self -end - ---- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces. -function SPAWN:_GetSpawnIndex( SpawnIndex ) - self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) - - if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then - if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then - if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then - self.SpawnCount = self.SpawnCount + 1 - SpawnIndex = self.SpawnCount - end - self.SpawnIndex = SpawnIndex - if not self.SpawnGroups[self.SpawnIndex] then - self:_InitializeSpawnGroups( self.SpawnIndex ) - end - else - return nil - end - else - return nil - end - - return self.SpawnIndex -end - - --- TODO Need to delete this... _DATABASE does this now ... - ---- @param #SPAWN self --- @param Core.Event#EVENTDATA Event -function SPAWN:_OnBirth( Event ) - - if timer.getTime0() < timer.getAbsTime() then - if Event.IniDCSUnit then - local EventPrefix = self:_GetPrefixFromDCSUnit( Event.IniDCSUnit ) - self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits + 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end - end - end - -end - ---- Obscolete --- @todo Need to delete this... _DATABASE does this now ... - ---- @param #SPAWN self --- @param Core.Event#EVENTDATA Event -function SPAWN:_OnDeadOrCrash( Event ) - self:F( self.SpawnTemplatePrefix, Event ) - - if Event.IniDCSUnit then - local EventPrefix = self:_GetPrefixFromDCSUnit( Event.IniDCSUnit ) - self:T( { "Dead event: " .. EventPrefix, self.SpawnTemplatePrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits - 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end - end -end - ---- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne... --- This is needed to ensure that Re-SPAWNing only is done for landed AIR Groups. --- @todo Need to test for AIR Groups only... -function SPAWN:_OnTakeOff( event ) - self:F( self.SpawnTemplatePrefix, event ) - - if event.initiator and event.initiator:getName() then - local SpawnGroup = self:_GetGroupFromDCSUnit( event.initiator ) - if SpawnGroup then - self:T( { "TakeOff event: " .. event.initiator:getName(), event } ) - self:T( "self.Landed = false" ) - self.Landed = false - end - end -end - ---- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed. --- This is needed to ensure that Re-SPAWNing is only done for landed AIR Groups. --- @todo Need to test for AIR Groups only... -function SPAWN:_OnLand( event ) - self:F( self.SpawnTemplatePrefix, event ) - - local SpawnUnit = event.initiator - if SpawnUnit and SpawnUnit:isExist() and Object.getCategory(SpawnUnit) == Object.Category.UNIT then - local SpawnGroup = self:_GetGroupFromDCSUnit( SpawnUnit ) - if SpawnGroup then - self:T( { "Landed event:" .. SpawnUnit:getName(), event } ) - self.Landed = true - self:T( "self.Landed = true" ) - if self.Landed and self.RepeatOnLanding then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - self:ReSpawn( SpawnGroupIndex ) - end - end - end -end - ---- Will detect AIR Units shutting down their engines ... --- When the event takes place, and the method @{RepeatOnEngineShutDown} was called, the spawned Group will Re-SPAWN. --- But only when the Unit was registered to have landed. --- @param #SPAWN self --- @see _OnTakeOff --- @see _OnLand --- @todo Need to test for AIR Groups only... -function SPAWN:_OnEngineShutDown( event ) - self:F( self.SpawnTemplatePrefix, event ) - - local SpawnUnit = event.initiator - if SpawnUnit and SpawnUnit:isExist() and Object.getCategory(SpawnUnit) == Object.Category.UNIT then - local SpawnGroup = self:_GetGroupFromDCSUnit( SpawnUnit ) - if SpawnGroup then - self:T( { "EngineShutDown event: " .. SpawnUnit:getName(), event } ) - if self.Landed and self.RepeatOnEngineShutDown then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - self:ReSpawn( SpawnGroupIndex ) - end - end - end -end - ---- This function is called automatically by the Spawning scheduler. --- It is the internal worker method SPAWNing new Groups on the defined time intervals. -function SPAWN:_Scheduler() - self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } ) - - -- Validate if there are still groups left in the batch... - self:Spawn() - - return true -end - ---- Schedules the CleanUp of Groups --- @param #SPAWN self --- @return #boolean True = Continue Scheduler -function SPAWN:_SpawnCleanUpScheduler() - self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - - while SpawnGroup do - - local SpawnUnits = SpawnGroup:GetUnits() - - for UnitID, UnitData in pairs( SpawnUnits ) do - - local SpawnUnit = UnitData -- Wrapper.Unit#UNIT - local SpawnUnitName = SpawnUnit:GetName() - - - self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} - local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] - self:T( { SpawnUnitName, Stamp } ) - - if Stamp.Vec2 then - if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then - local NewVec2 = SpawnUnit:GetVec2() - if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving, and is on the ground, assign it with a timestamp... - if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) - self:ReSpawn( SpawnCursor ) - Stamp.Vec2 = nil - Stamp.Time = nil - end - else - Stamp.Time = timer.getTime() - Stamp.Vec2 = SpawnUnit:GetVec2() - end - else - Stamp.Vec2 = nil - Stamp.Time = nil - end - else - if SpawnUnit:InAir() == false then - Stamp.Vec2 = SpawnUnit:GetVec2() - if SpawnUnit:GetVelocityKMH() < 1 then - Stamp.Time = timer.getTime() - end - else - Stamp.Time = nil - Stamp.Vec2 = nil - end - end - end - - SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - - end - - return true -- Repeat - -end ---- Limit the simultaneous movement of Groups within a running Mission. --- This module is defined to improve the performance in missions, and to bring additional realism for GROUND vehicles. --- Performance: If in a DCSRTE there are a lot of moving GROUND units, then in a multi player mission, this WILL create lag if --- the main DCS execution core of your CPU is fully utilized. So, this class will limit the amount of simultaneous moving GROUND units --- on defined intervals (currently every minute). --- @module Movement - ---- the MOVEMENT class --- @type MOVEMENT --- @extends Core.Base#BASE -MOVEMENT = { - ClassName = "MOVEMENT", -} - ---- Creates the main object which is handling the GROUND forces movement. --- @param table{string,...}|string MovePrefixes is a table of the Prefixes (names) of the GROUND Groups that need to be controlled by the MOVEMENT Object. --- @param number MoveMaximum is a number that defines the maximum amount of GROUND Units to be moving during one minute. --- @return MOVEMENT --- @usage --- -- Limit the amount of simultaneous moving units on the ground to prevent lag. --- Movement_US_Platoons = MOVEMENT:New( { 'US Tank Platoon Left', 'US Tank Platoon Middle', 'US Tank Platoon Right', 'US CH-47D Troops' }, 15 ) - -function MOVEMENT:New( MovePrefixes, MoveMaximum ) - local self = BASE:Inherit( self, BASE:New() ) -- #MOVEMENT - self:F( { MovePrefixes, MoveMaximum } ) - - if type( MovePrefixes ) == 'table' then - self.MovePrefixes = MovePrefixes - else - self.MovePrefixes = { MovePrefixes } - end - self.MoveCount = 0 -- The internal counter of the amount of Moveing the has happened since MoveStart. - self.MoveMaximum = MoveMaximum -- Contains the Maximum amount of units that are allowed to move... - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.MoveUnits = {} -- Reflects if the Moving for this MovePrefixes is going to be scheduled or not. - - self:HandleEvent( EVENTS.Birth ) - --- self:AddEvent( world.event.S_EVENT_BIRTH, self.OnBirth ) --- --- self:EnableEvents() - - self:ScheduleStart() - - return self -end - ---- Call this function to start the MOVEMENT scheduling. -function MOVEMENT:ScheduleStart() - self:F() - --self.MoveFunction = routines.scheduleFunction( self._Scheduler, { self }, timer.getTime() + 1, 120 ) - self.MoveFunction = SCHEDULER:New( self, self._Scheduler, {}, 1, 120 ) -end - ---- Call this function to stop the MOVEMENT scheduling. --- @todo need to implement it ... Forgot. -function MOVEMENT:ScheduleStop() - self:F() - -end - ---- Captures the birth events when new Units were spawned. --- @todo This method should become obsolete. The new @{DATABASE} class will handle the collection administration. --- @param #MOVEMENT self --- @param Core.Event#EVENTDATA self -function MOVEMENT:OnEventBirth( EventData ) - self:F( { EventData } ) - - if 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 - if EventData.IniDCSUnit then - self:T( "Birth object : " .. EventData.IniDCSUnitName ) - if EventData.IniDCSGroup and EventData.IniDCSGroup:isExist() then - for MovePrefixID, MovePrefix in pairs( self.MovePrefixes ) do - if string.find( EventData.IniDCSUnitName, MovePrefix, 1, true ) then - self.AliveUnits = self.AliveUnits + 1 - self.MoveUnits[EventData.IniDCSUnitName] = EventData.IniDCSGroupName - self:T( self.AliveUnits ) - end - end - end - end - - EventData.IniUnit:HandleEvent( EVENTS.DEAD, self.OnDeadOrCrash ) - end - -end - ---- Captures the Dead or Crash events when Units crash or are destroyed. --- @todo This method should become obsolete. The new @{DATABASE} class will handle the collection administration. -function MOVEMENT:OnDeadOrCrash( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - self:T( "Dead object : " .. Event.IniDCSUnitName ) - for MovePrefixID, MovePrefix in pairs( self.MovePrefixes ) do - if string.find( Event.IniDCSUnitName, MovePrefix, 1, true ) then - self.AliveUnits = self.AliveUnits - 1 - self.MoveUnits[Event.IniDCSUnitName] = nil - self:T( self.AliveUnits ) - end - end - end -end - ---- This function is called automatically by the MOVEMENT scheduler. A new function is scheduled when MoveScheduled is true. -function MOVEMENT:_Scheduler() - self:F( { self.MovePrefixes, self.MoveMaximum, self.AliveUnits, self.MovementGroups } ) - - if self.AliveUnits > 0 then - local MoveProbability = ( self.MoveMaximum * 100 ) / self.AliveUnits - self:T( 'Move Probability = ' .. MoveProbability ) - - for MovementUnitName, MovementGroupName in pairs( self.MoveUnits ) do - local MovementGroup = Group.getByName( MovementGroupName ) - if MovementGroup and MovementGroup:isExist() then - local MoveOrStop = math.random( 1, 100 ) - self:T( 'MoveOrStop = ' .. MoveOrStop ) - if MoveOrStop <= MoveProbability then - self:T( 'Group continues moving = ' .. MovementGroupName ) - trigger.action.groupContinueMoving( MovementGroup ) - else - self:T( 'Group stops moving = ' .. MovementGroupName ) - trigger.action.groupStopMoving( MovementGroup ) - end - else - self.MoveUnits[MovementUnitName] = nil - end - end - end - return true -end ---- Provides defensive behaviour to a set of SAM sites within a running Mission. --- @module Sead --- @author to be searched on the forum --- @author (co) Flightcontrol (Modified and enriched with functionality) - ---- The SEAD class --- @type SEAD --- @extends Core.Base#BASE -SEAD = { - ClassName = "SEAD", - TargetSkill = { - Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } , - Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } , - High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } , - Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } } - }, - SEADGroupPrefixes = {} -} - ---- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. --- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... --- Chances are big that the missile will miss. --- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken. --- @return SEAD --- @usage --- -- CCCP SEAD Defenses --- -- Defends the Russian SA installations from SEAD attacks. --- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) -function SEAD:New( SEADGroupPrefixes ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( SEADGroupPrefixes ) - if type( SEADGroupPrefixes ) == 'table' then - for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do - self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix - end - else - self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes - end - - self:HandleEvent( EVENTS.Shot ) - - return self -end - ---- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @see SEAD --- @param #SEAD --- @param Core.Event#EVENTDATA EventData -function SEAD:OnEventShot( EventData ) - self:F( { EventData } ) - - local SEADUnit = EventData.IniDCSUnit - local SEADUnitName = EventData.IniDCSUnitName - local SEADWeapon = EventData.Weapon -- Identify the weapon fired - local SEADWeaponName = EventData.WeaponName -- return weapon type - -- Start of the 2nd loop - self:T( "Missile Launched = " .. SEADWeaponName ) - if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD - local _evade = math.random (1,100) -- random number for chance of evading action - local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetMimname = Unit.getName(_targetMim) - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimgroupName = _targetMimgroup:getName() - local _targetMimcont= _targetMimgroup:getController() - local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) - local SEADGroupFound = false - for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do - if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then - SEADGroupFound = true - self:T( 'Group Found' ) - break - end - end - if SEADGroupFound == true then - if _targetskill == "Random" then -- when skill is random, choose a skill - local Skills = { "Average", "Good", "High", "Excellent" } - _targetskill = Skills[ math.random(1,4) ] - end - self:T( _targetskill ) - if self.TargetSkill[_targetskill] then - if (_evade > self.TargetSkill[_targetskill].Evade) then - self:T( string.format("Evading, target skill " ..string.format(_targetskill)) ) - local _targetMim = Weapon.getTarget(SEADWeapon) - local _targetMimname = Unit.getName(_targetMim) - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimcont= _targetMimgroup:getController() - routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - local SuppressedGroups1 = {} -- unit suppressed radar off for a random time - local function SuppressionEnd1(id) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - SuppressedGroups1[id.groupName] = nil - end - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2]) - if SuppressedGroups1[id.groupName] == nil then - SuppressedGroups1[id.groupName] = { - SuppressionEndTime1 = timer.getTime() + delay1, - SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function - } - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20) - end - - local SuppressedGroups = {} - local function SuppressionEnd(id) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - SuppressedGroups[id.groupName] = nil - end - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - if SuppressedGroups[id.groupName] == nil then - SuppressedGroups[id.groupName] = { - SuppressionEndTime = timer.getTime() + delay, - SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function - } - timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20) - end - end - end - end - end -end ---- Taking the lead of AI escorting your flight. --- --- @{#ESCORT} class --- ================ --- The @{#ESCORT} class allows you to interact with escorting AI on your flight and take the lead. --- Each escorting group can be commanded with a whole set of radio commands (radio menu in your flight, and then F10). --- --- The radio commands will vary according the category of the group. The richest set of commands are with Helicopters and AirPlanes. --- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. --- --- RADIO MENUs that can be created: --- ================================ --- Find a summary below of the current available commands: --- --- Navigation ...: --- --------------- --- Escort group navigation functions: --- --- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you. --- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. --- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. --- --- Hold position ...: --- ------------------ --- Escort group navigation functions: --- --- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. --- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. --- --- Report targets ...: --- ------------------- --- Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below). --- --- * **"Report now":** Will report the current detected targets. --- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list. --- * **"Report targets off":** Will stop detecting targets. --- --- Scan targets ...: --- ----------------- --- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task. --- --- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. --- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. --- --- Attack targets ...: --- ------------------- --- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. --- --- Request assistance from ...: --- ---------------------------- --- This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**. --- This menu item allows to request attack support from other escorts supporting the current client group. --- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. --- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. --- --- ROE ...: --- -------- --- Sets the Rules of Engagement (ROE) of the escort group when in flight. --- --- * **"Hold Fire":** The escort group will hold fire. --- * **"Return Fire":** The escort group will return fire. --- * **"Open Fire":** The escort group will open fire on designated targets. --- * **"Weapon Free":** The escort group will engage with any target. --- --- Evasion ...: --- ------------ --- Will define the evasion techniques that the escort group will perform during flight or combat. --- --- * **"Fight until death":** The escort group will have no reaction to threats. --- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed. --- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing. --- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres. --- --- Resume Mission ...: --- ------------------- --- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. --- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. --- --- ESCORT construction methods. --- ============================ --- Create a new SPAWN object with the @{#ESCORT.New} method: --- --- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text. --- --- ESCORT initialization methods. --- ============================== --- The following menus are created within the RADIO MENU (F10) of an active unit hosted by a player: --- --- * @{#ESCORT.MenuFollowAt}: Creates a menu to make the escort follow the client. --- * @{#ESCORT.MenuHoldAtEscortPosition}: Creates a menu to hold the escort at its current position. --- * @{#ESCORT.MenuHoldAtLeaderPosition}: Creates a menu to hold the escort at the client position. --- * @{#ESCORT.MenuScanForTargets}: Creates a menu so that the escort scans targets. --- * @{#ESCORT.MenuFlare}: Creates a menu to disperse flares. --- * @{#ESCORT.MenuSmoke}: Creates a menu to disparse smoke. --- * @{#ESCORT.MenuReportTargets}: Creates a menu so that the escort reports targets. --- * @{#ESCORT.MenuReportPosition}: Creates a menu so that the escort reports its current position from bullseye. --- * @{#ESCORT.MenuAssistedAttack: Creates a menu so that the escort supportes assisted attack from other escorts with the client. --- * @{#ESCORT.MenuROE: Creates a menu structure to set the rules of engagement of the escort. --- * @{#ESCORT.MenuEvasion: Creates a menu structure to set the evasion techniques when the escort is under threat. --- * @{#ESCORT.MenuResumeMission}: Creates a menu structure so that the escort can resume from a waypoint. --- --- --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortClient = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = ESCORT:New( EscortClient, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- --- --- --- @module Escort --- @author FlightControl - ---- ESCORT class --- @type ESCORT --- @extends Core.Base#BASE --- @field Wrapper.Client#CLIENT EscortClient --- @field Wrapper.Group#GROUP EscortGroup --- @field #string EscortName --- @field #ESCORT.MODE EscortMode The mode the escort is in. --- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. --- @field #number FollowDistance The current follow distance. --- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field Dcs.DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. --- @field Dcs.DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. --- @field Core.Menu#MENU_CLIENT EscortMenuResumeMission --- @field Functional.Detection#DETECTION_BASE Detection -ESCORT = { - ClassName = "ESCORT", - EscortName = nil, -- The Escort Name - EscortClient = nil, - EscortGroup = nil, - EscortMode = 1, - MODE = { - FOLLOW = 1, - MISSION = 2, - }, - Targets = {}, -- The identified targets - FollowScheduler = nil, - ReportTargets = true, - OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, - OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, - SmokeDirectionVector = false, - TaskPoints = {} -} - ---- ESCORT.Mode class --- @type ESCORT.MODE --- @field #number FOLLOW --- @field #number MISSION - ---- MENUPARAM type --- @type MENUPARAM --- @field #ESCORT ParamSelf --- @field #Distance ParamDistance --- @field #function ParamFunction --- @field #string ParamMessage - ---- ESCORT class constructor for an AI group --- @param #ESCORT self --- @param Wrapper.Client#CLIENT EscortClient The client escorted by the EscortGroup. --- @param Wrapper.Group#GROUP EscortGroup The group AI escorting the EscortClient. --- @param #string EscortName Name of the escort. --- @param #string EscortBriefing A text showing the ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #ESCORT self --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortClient = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = ESCORT:New( EscortClient, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) -function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) - - local self = BASE:Inherit( self, BASE:New() ) -- #ESCORT - self:F( { EscortClient, EscortGroup, EscortName } ) - - self.EscortClient = EscortClient -- Wrapper.Client#CLIENT - self.EscortGroup = EscortGroup -- Wrapper.Group#GROUP - self.EscortName = EscortName - self.EscortBriefing = EscortBriefing - - self.EscortSetGroup = SET_GROUP:New() - self.EscortSetGroup:AddObject( self.EscortGroup ) - self.EscortSetGroup:Flush() - self.Detection = DETECTION_UNITS:New( self.EscortSetGroup, 15000 ) - - self.EscortGroup.Detection = self.Detection - - -- Set EscortGroup known at EscortClient. - if not self.EscortClient._EscortGroups then - self.EscortClient._EscortGroups = {} - end - - if not self.EscortClient._EscortGroups[EscortGroup:GetName()] then - self.EscortClient._EscortGroups[EscortGroup:GetName()] = {} - self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortGroup = self.EscortGroup - self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortName = self.EscortName - self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection = self.EscortGroup.Detection - end - - self.EscortMenu = MENU_CLIENT:New( self.EscortClient, self.EscortName ) - - self.EscortGroup:WayPointInitialize(1) - - self.EscortGroup:OptionROTVertical() - self.EscortGroup:OptionROEOpenFire() - - if not EscortBriefing then - EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") reporting! " .. - "We're escorting your flight. " .. - "Use the Radio Menu and F10 and use the options under + " .. EscortName .. "\n", - 60, EscortClient - ) - else - EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") " .. EscortBriefing, - 60, EscortClient - ) - end - - self.FollowDistance = 100 - self.CT1 = 0 - self.GT1 = 0 - - self.FollowScheduler, self.FollowSchedule = SCHEDULER:New( self, self._FollowScheduler, {}, 1, .5, .01 ) - self.FollowScheduler:Stop( self.FollowSchedule ) - - self.EscortMode = ESCORT.MODE.MISSION - - - return self -end - ---- Set a Detection method for the EscortClient to be reported upon. --- Detection methods are based on the derived classes from DETECTION_BASE. --- @param #ESCORT self --- @param Function.Detection#DETECTION_BASE Detection -function ESCORT:SetDetection( Detection ) - - self.Detection = Detection - self.EscortGroup.Detection = self.Detection - self.EscortClient._EscortGroups[self.EscortGroup:GetName()].Detection = self.EscortGroup.Detection - - Detection:__Start( 1 ) - -end - ---- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This allows to visualize where the escort is flying to. --- @param #ESCORT self --- @param #boolean SmokeDirection If true, then the direction vector will be smoked. -function ESCORT:TestSmokeDirectionVector( SmokeDirection ) - self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false -end - - ---- Defines the default menus --- @param #ESCORT self --- @return #ESCORT -function ESCORT:Menus() - self:F() - - self:MenuFollowAt( 100 ) - self:MenuFollowAt( 200 ) - self:MenuFollowAt( 300 ) - self:MenuFollowAt( 400 ) - - self:MenuScanForTargets( 100, 60 ) - - self:MenuHoldAtEscortPosition( 30 ) - self:MenuHoldAtLeaderPosition( 30 ) - - self:MenuFlare() - self:MenuSmoke() - - self:MenuReportTargets( 60 ) - self:MenuAssistedAttack() - self:MenuROE() - self:MenuEvasion() - self:MenuResumeMission() - - - return self -end - - - ---- Defines a menu slot to let the escort Join and Follow you at a certain distance. --- This menu will appear under **Navigation**. --- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. --- @return #ESCORT -function ESCORT:MenuFollowAt( Distance ) - self:F(Distance) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - if not self.EscortMenuJoinUpAndFollow then - self.EscortMenuJoinUpAndFollow = {} - end - - self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1] = MENU_CLIENT_COMMAND:New( self.EscortClient, "Join-Up and Follow at " .. Distance, self.EscortMenuReportNavigation, ESCORT._JoinUpAndFollow, self, Distance ) - - self.EscortMode = ESCORT.MODE.FOLLOW - end - - return self -end - ---- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Hold position**. --- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT --- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. -function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - - if not self.EscortMenuHold then - self.EscortMenuHold = MENU_CLIENT:New( self.EscortClient, "Hold position", self.EscortMenu ) - end - - if not Height then - Height = 30 - end - - if not Seconds then - Seconds = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "Hold at %d meter", Height ) - else - MenuText = string.format( "Hold at %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuHoldPosition then - self.EscortMenuHoldPosition = {} - end - - self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuHold, - ESCORT._HoldPosition, - self, - self.EscortGroup, - Height, - Seconds - ) - end - - return self -end - - ---- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Navigation**. --- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT --- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. -function ESCORT:MenuHoldAtLeaderPosition( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - - if not self.EscortMenuHold then - self.EscortMenuHold = MENU_CLIENT:New( self.EscortClient, "Hold position", self.EscortMenu ) - end - - if not Height then - Height = 30 - end - - if not Seconds then - Seconds = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "Rejoin and hold at %d meter", Height ) - else - MenuText = string.format( "Rejoin and hold at %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuHoldAtLeaderPosition then - self.EscortMenuHoldAtLeaderPosition = {} - end - - self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuHold, - ESCORT._HoldPosition, - { ParamSelf = self, - ParamOrbitGroup = self.EscortClient, - ParamHeight = Height, - ParamSeconds = Seconds - } - ) - end - - return self -end - ---- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. --- This menu will appear under **Scan targets**. --- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuScan then - self.EscortMenuScan = MENU_CLIENT:New( self.EscortClient, "Scan for targets", self.EscortMenu ) - end - - if not Height then - Height = 100 - end - - if not Seconds then - Seconds = 30 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "At %d meter", Height ) - else - MenuText = string.format( "At %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuScanForTargets then - self.EscortMenuScanForTargets = {} - end - - self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuScan, - ESCORT._ScanTargets, - self, - 30 - ) - end - - return self -end - - - ---- Defines a menu slot to let the escort disperse a flare in a certain color. --- This menu will appear under **Navigation**. --- The flare will be fired from the first unit in the group. --- @param #ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuFlare( MenuTextFormat ) - self:F() - - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Flare" - else - MenuText = MenuTextFormat - end - - if not self.EscortMenuFlare then - self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, self ) - self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Green, "Released a green flare!" ) - self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Red, "Released a red flare!" ) - self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.White, "Released a white flare!" ) - self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Yellow, "Released a yellow flare!" ) - end - - return self -end - ---- Defines a menu slot to let the escort disperse a smoke in a certain color. --- This menu will appear under **Navigation**. --- Note that smoke menu options will only be displayed for ships and ground units. Not for air units. --- The smoke will be fired from the first unit in the group. --- @param #ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuSmoke( MenuTextFormat ) - self:F() - - if not self.EscortGroup:IsAir() then - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Smoke" - else - MenuText = MenuTextFormat - end - - if not self.EscortMenuSmoke then - self.EscortMenuSmoke = MENU_CLIENT:New( self.EscortClient, "Smoke", self.EscortMenuReportNavigation, ESCORT._Smoke, self ) - self.EscortMenuSmokeGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Green, "Releasing green smoke!" ) - self.EscortMenuSmokeRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Red, "Releasing red smoke!" ) - self.EscortMenuSmokeWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.White, "Releasing white smoke!" ) - self.EscortMenuSmokeOrange = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release orange smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Orange, "Releasing orange smoke!" ) - self.EscortMenuSmokeBlue = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release blue smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Blue, "Releasing blue smoke!" ) - end - end - - return self -end - ---- Defines a menu slot to let the escort report their current detected targets with a specified time interval in seconds. --- This menu will appear under **Report targets**. --- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. --- @param #ESCORT self --- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. --- @return #ESCORT -function ESCORT:MenuReportTargets( Seconds ) - self:F( { Seconds } ) - - if not self.EscortMenuReportNearbyTargets then - self.EscortMenuReportNearbyTargets = MENU_CLIENT:New( self.EscortClient, "Report targets", self.EscortMenu ) - end - - if not Seconds then - Seconds = 30 - end - - -- Report Targets - self.EscortMenuReportNearbyTargetsNow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets now!", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargetsNow, self ) - self.EscortMenuReportNearbyTargetsOn = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets on", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, self, true ) - self.EscortMenuReportNearbyTargetsOff = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets off", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, self, false ) - - -- Attack Targets - self.EscortMenuAttackNearbyTargets = MENU_CLIENT:New( self.EscortClient, "Attack targets", self.EscortMenu ) - - - self.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, Seconds ) - - return self -end - ---- Defines a menu slot to let the escort attack its detected targets using assisted attack from another escort joined also with the client. --- This menu will appear under **Request assistance from**. --- Note that this method needs to be preceded with the method MenuReportTargets. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuAssistedAttack() - self:F() - - -- Request assistance from other escorts. - -- This is very useful to let f.e. an escorting ship attack a target detected by an escorting plane... - self.EscortMenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, "Request assistance from", self.EscortMenu ) - - return self -end - ---- Defines a menu to let the escort set its rules of engagement. --- All rules of engagement will appear under the menu **ROE**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuROE( MenuTextFormat ) - self:F( MenuTextFormat ) - - if not self.EscortMenuROE then - -- Rules of Engagement - self.EscortMenuROE = MENU_CLIENT:New( self.EscortClient, "ROE", self.EscortMenu ) - if self.EscortGroup:OptionROEHoldFirePossible() then - self.EscortMenuROEHoldFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Hold Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEHoldFire(), "Holding weapons!" ) - end - if self.EscortGroup:OptionROEReturnFirePossible() then - self.EscortMenuROEReturnFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Return Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEReturnFire(), "Returning fire!" ) - end - if self.EscortGroup:OptionROEOpenFirePossible() then - self.EscortMenuROEOpenFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Open Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEOpenFire(), "Opening fire on designated targets!!" ) - end - if self.EscortGroup:OptionROEWeaponFreePossible() then - self.EscortMenuROEWeaponFree = MENU_CLIENT_COMMAND:New( self.EscortClient, "Weapon Free", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEWeaponFree(), "Opening fire on targets of opportunity!" ) - end - end - - return self -end - - ---- Defines a menu to let the escort set its evasion when under threat. --- All rules of engagement will appear under the menu **Evasion**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuEvasion( MenuTextFormat ) - self:F( MenuTextFormat ) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuEvasion then - -- Reaction to Threats - self.EscortMenuEvasion = MENU_CLIENT:New( self.EscortClient, "Evasion", self.EscortMenu ) - if self.EscortGroup:OptionROTNoReactionPossible() then - self.EscortMenuEvasionNoReaction = MENU_CLIENT_COMMAND:New( self.EscortClient, "Fight until death", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTNoReaction(), "Fighting until death!" ) - end - if self.EscortGroup:OptionROTPassiveDefensePossible() then - self.EscortMenuEvasionPassiveDefense = MENU_CLIENT_COMMAND:New( self.EscortClient, "Use flares, chaff and jammers", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTPassiveDefense(), "Defending using jammers, chaff and flares!" ) - end - if self.EscortGroup:OptionROTEvadeFirePossible() then - self.EscortMenuEvasionEvadeFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Evade enemy fire", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTEvadeFire(), "Evading on enemy fire!" ) - end - if self.EscortGroup:OptionROTVerticalPossible() then - self.EscortMenuOptionEvasionVertical = MENU_CLIENT_COMMAND:New( self.EscortClient, "Go below radar and evade fire", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTVertical(), "Evading on enemy fire with vertical manoeuvres!" ) - end - end - end - - return self -end - ---- Defines a menu to let the escort resume its mission from a waypoint on its route. --- All rules of engagement will appear under the menu **Resume mission from**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuResumeMission() - self:F() - - if not self.EscortMenuResumeMission then - -- Mission Resume Menu Root - self.EscortMenuResumeMission = MENU_CLIENT:New( self.EscortClient, "Resume mission from", self.EscortMenu ) - end - - return self -end - - ---- @param #MENUPARAM MenuParam -function ESCORT:_HoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT - - self.FollowScheduler:Stop( self.FollowSchedule ) - - local PointFrom = {} - local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() - PointFrom = {} - PointFrom.x = GroupVec3.x - PointFrom.y = GroupVec3.z - PointFrom.speed = 250 - PointFrom.type = AI.Task.WaypointType.TURNING_POINT - PointFrom.alt = GroupVec3.y - PointFrom.alt_type = AI.Task.AltitudeType.BARO - - local OrbitPoint = OrbitUnit:GetVec2() - local PointTo = {} - PointTo.x = OrbitPoint.x - PointTo.y = OrbitPoint.y - PointTo.speed = 250 - PointTo.type = AI.Task.WaypointType.TURNING_POINT - PointTo.alt = OrbitHeight - PointTo.alt_type = AI.Task.AltitudeType.BARO - PointTo.task = EscortGroup:TaskOrbitCircleAtVec2( OrbitPoint, OrbitHeight, 0 ) - - local Points = { PointFrom, PointTo } - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTPassiveDefense() - - EscortGroup:SetTask( EscortGroup:TaskRoute( Points ) ) - EscortGroup:MessageToClient( "Orbiting at location.", 10, EscortClient ) - -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_JoinUpAndFollow( Distance ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.Distance = Distance - - self:JoinUpAndFollow( EscortGroup, EscortClient, self.Distance ) -end - ---- JoinsUp and Follows a CLIENT. --- @param Functional.Escort#ESCORT self --- @param Wrapper.Group#GROUP EscortGroup --- @param Wrapper.Client#CLIENT EscortClient --- @param Dcs.DCSTypes#Distance Distance -function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) - self:F( { EscortGroup, EscortClient, Distance } ) - - self.FollowScheduler:Stop( self.FollowSchedule ) - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTPassiveDefense() - - self.EscortMode = ESCORT.MODE.FOLLOW - - self.CT1 = 0 - self.GT1 = 0 - self.FollowScheduler:Start( self.FollowSchedule ) - - EscortGroup:MessageToClient( "Rejoining and Following at " .. Distance .. "!", 30, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_Flare( Color, Message ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - EscortGroup:GetUnit(1):Flare( Color ) - EscortGroup:MessageToClient( Message, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_Smoke( Color, Message ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - EscortGroup:GetUnit(1):Smoke( Color ) - EscortGroup:MessageToClient( Message, 10, EscortClient ) -end - - ---- @param #MENUPARAM MenuParam -function ESCORT:_ReportNearbyTargetsNow() - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self:_ReportTargetsScheduler() - -end - -function ESCORT:_SwitchReportNearbyTargets( ReportTargets ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.ReportTargets = ReportTargets - - if self.ReportTargets then - if not self.ReportTargetsScheduler then - self.ReportTargetsScheduler:Schedule( self, self._ReportTargetsScheduler, {}, 1, 30 ) - end - else - routines.removeFunction( self.ReportTargetsScheduler ) - self.ReportTargetsScheduler = nil - end -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_ScanTargets( ScanDuration ) - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - local EscortClient = self.EscortClient - - self.FollowScheduler:Stop( self.FollowSchedule ) - - if EscortGroup:IsHelicopter() then - EscortGroup:PushTask( - EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 200, 20 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ), 1 ) - elseif EscortGroup:IsAirPlane() then - EscortGroup:PushTask( - EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 1000, 500 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ), 1 ) - end - - EscortGroup:MessageToClient( "Scanning targets for " .. ScanDuration .. " seconds.", ScanDuration, EscortClient ) - - if self.EscortMode == ESCORT.MODE.FOLLOW then - self.FollowScheduler:Start( self.FollowSchedule ) - end - -end - ---- @param Wrapper.Group#GROUP EscortGroup -function _Resume( EscortGroup ) - env.info( '_Resume' ) - - local Escort = EscortGroup:GetState( EscortGroup, "Escort" ) - env.info( "EscortMode = " .. Escort.EscortMode ) - if Escort.EscortMode == ESCORT.MODE.FOLLOW then - Escort:JoinUpAndFollow( EscortGroup, Escort.EscortClient, Escort.Distance ) - end - -end - ---- @param #ESCORT self --- @param #number DetectedItemID -function ESCORT:_AttackTarget( DetectedItemID ) - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - self:E( EscortGroup ) - - local EscortClient = self.EscortClient - - self.FollowScheduler:Stop( self.FollowSchedule ) - - if EscortGroup:IsAir() then - EscortGroup:OptionROEOpenFire() - EscortGroup:OptionROTPassiveDefense() - EscortGroup:SetState( EscortGroup, "Escort", self ) - - local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroup:TaskAttackUnit( DetectedUnit ) - end - end, Tasks - ) - - Tasks[#Tasks+1] = EscortGroup:TaskFunction( 1, 2, "_Resume", { "''" } ) - - EscortGroup:SetTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - else - - local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) - end - end, Tasks - ) - - EscortGroup:SetTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - end - - EscortGroup:MessageToClient( "Engaging Designated Unit!", 10, EscortClient ) - -end - ---- --- @param #number DetectedItemID -function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItemID ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.FollowScheduler:Stop( self.FollowSchedule ) - - if EscortGroupAttack:IsAir() then - EscortGroupAttack:OptionROEOpenFire() - EscortGroupAttack:OptionROTVertical() - - local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroupAttack:TaskAttackUnit( DetectedUnit ) - end - end, Tasks - ) - - Tasks[#Tasks+1] = EscortGroupAttack:TaskOrbitCircle( 500, 350 ) - - EscortGroupAttack:SetTask( - EscortGroupAttack:TaskCombo( - Tasks - ), 1 - ) - - else - local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroupAttack:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) - end - end, Tasks - ) - - EscortGroupAttack:SetTask( - EscortGroupAttack:TaskCombo( - Tasks - ), 1 - ) - - end - - EscortGroupAttack:MessageToClient( "Assisting with the destroying the enemy unit!", 10, EscortClient ) - -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_ROE( EscortROEFunction, EscortROEMessage ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - pcall( function() EscortROEFunction() end ) - EscortGroup:MessageToClient( EscortROEMessage, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_ROT( EscortROTFunction, EscortROTMessage ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - pcall( function() EscortROTFunction() end ) - EscortGroup:MessageToClient( EscortROTMessage, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_ResumeMission( WayPoint ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.FollowScheduler:Stop( self.FollowSchedule ) - - local WayPoints = EscortGroup:GetTaskRoute() - self:T( WayPoint, WayPoints ) - - for WayPointIgnore = 1, WayPoint do - table.remove( WayPoints, 1 ) - end - - SCHEDULER:New( EscortGroup, EscortGroup.SetTask, { EscortGroup:TaskRoute( WayPoints ) }, 1 ) - - EscortGroup:MessageToClient( "Resuming mission from waypoint " .. WayPoint .. ".", 10, EscortClient ) -end - ---- Registers the waypoints --- @param #ESCORT self --- @return #table -function ESCORT:RegisterRoute() - self:F() - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - - local TaskPoints = EscortGroup:GetTaskRoute() - - self:T( TaskPoints ) - - return TaskPoints -end - ---- @param Functional.Escort#ESCORT self -function ESCORT:_FollowScheduler() - self:F( { self.FollowDistance } ) - - self:T( {self.EscortClient.UnitName, self.EscortGroup.GroupName } ) - if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - - local ClientUnit = self.EscortClient:GetClientGroupUnit() - local GroupUnit = self.EscortGroup:GetUnit( 1 ) - local FollowDistance = self.FollowDistance - - self:T( {ClientUnit.UnitName, GroupUnit.UnitName } ) - - if self.CT1 == 0 and self.GT1 == 0 then - self.CV1 = ClientUnit:GetVec3() - self:T( { "self.CV1", self.CV1 } ) - self.CT1 = timer.getTime() - self.GV1 = GroupUnit:GetVec3() - self.GT1 = timer.getTime() - else - local CT1 = self.CT1 - local CT2 = timer.getTime() - local CV1 = self.CV1 - local CV2 = ClientUnit:GetVec3() - self.CT1 = CT2 - self.CV1 = CV2 - - local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 - local CT = CT2 - CT1 - - local CS = ( 3600 / CT ) * ( CD / 1000 ) - - self:T2( { "Client:", CS, CD, CT, CV2, CV1, CT2, CT1 } ) - - local GT1 = self.GT1 - local GT2 = timer.getTime() - local GV1 = self.GV1 - local GV2 = GroupUnit:GetVec3() - self.GT1 = GT2 - self.GV1 = GV2 - - local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 - local GT = GT2 - GT1 - - local GS = ( 3600 / GT ) * ( GD / 1000 ) - - self:T2( { "Group:", GS, GD, GT, GV2, GV1, GT2, GT1 } ) - - -- Calculate the group direction vector - local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } - - -- Calculate GH2, GH2 with the same height as CV2. - local GH2 = { x = GV2.x, y = CV2.y, z = GV2.z } - - -- Calculate the angle of GV to the orthonormal plane - local alpha = math.atan2( GV.z, GV.x ) - - -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. - -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) - local CVI = { x = CV2.x + FollowDistance * math.cos(alpha), - y = GH2.y, - z = CV2.z + FollowDistance * math.sin(alpha), - } - - -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. - local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } - - -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. - -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. - -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... - local DVu = { x = DV.x / FollowDistance, y = DV.y / FollowDistance, z = DV.z / FollowDistance } - - -- Now we can calculate the group destination vector GDV. - local GDV = { x = DVu.x * CS * 8 + CVI.x, y = CVI.y, z = DVu.z * CS * 8 + CVI.z } - - if self.SmokeDirectionVector == true then - trigger.action.smoke( GDV, trigger.smokeColor.Red ) - end - - self:T2( { "CV2:", CV2 } ) - self:T2( { "CVI:", CVI } ) - self:T2( { "GDV:", GDV } ) - - -- Measure distance between client and group - local CatchUpDistance = ( ( GDV.x - GV2.x )^2 + ( GDV.y - GV2.y )^2 + ( GDV.z - GV2.z )^2 ) ^ 0.5 - - -- The calculation of the Speed would simulate that the group would take 30 seconds to overcome - -- the requested Distance). - local Time = 10 - local CatchUpSpeed = ( CatchUpDistance - ( CS * 8.4 ) ) / Time - - local Speed = CS + CatchUpSpeed - if Speed < 0 then - Speed = 0 - end - - self:T( { "Client Speed, Escort Speed, Speed, FollowDistance, Time:", CS, GS, Speed, FollowDistance, Time } ) - - -- Now route the escort to the desired point with the desired speed. - self.EscortGroup:RouteToVec3( GDV, Speed / 3.6 ) -- DCS models speed in Mps (Miles per second) - end - - return true - end - - return false -end - - ---- Report Targets Scheduler. --- @param #ESCORT self -function ESCORT:_ReportTargetsScheduler() - self:F( self.EscortGroup:GetName() ) - - if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - - if true then - - local EscortGroupName = self.EscortGroup:GetName() - - self.EscortMenuAttackNearbyTargets:RemoveSubMenus() - - if self.EscortMenuTargetAssistance then - self.EscortMenuTargetAssistance:RemoveSubMenus() - end - - local DetectedItems = self.Detection:GetDetectedItems() - self:E( DetectedItems ) - - local DetectedTargets = false - - local DetectedMsgs = {} - - for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do - - local ClientEscortTargets = EscortGroupData.Detection - - for DetectedItemID, DetectedItem in ipairs( DetectedItems ) do - self:E( { DetectedItemID, DetectedItem } ) - -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. - - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItemID ) - - if ClientEscortGroupName == EscortGroupName then - - DetectedMsgs[#DetectedMsgs+1] = DetectedItemReportSummary - - MENU_CLIENT_COMMAND:New( self.EscortClient, - DetectedItemReportSummary, - self.EscortMenuAttackNearbyTargets, - ESCORT._AttackTarget, - self, - DetectedItemID - ) - else - if self.EscortMenuTargetAssistance then - - self:T( DetectedItemReportSummary ) - local MenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance ) - MENU_CLIENT_COMMAND:New( self.EscortClient, - DetectedItemReportSummary, - MenuTargetAssistance, - ESCORT._AssistTarget, - self, - EscortGroupData.EscortGroup, - DetectedItemID - ) - end - end - - DetectedTargets = true - - end - end - self:E( DetectedMsgs ) - if DetectedTargets then - self.EscortGroup:MessageToClient( "Detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), 20, self.EscortClient ) - else - self.EscortGroup:MessageToClient( "No targets detected.", 10, self.EscortClient ) - end - - return true - else --- local EscortGroupName = self.EscortGroup:GetName() --- local EscortTargets = self.EscortGroup:GetDetectedTargets() --- --- local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets --- --- local EscortTargetMessages = "" --- for EscortTargetID, EscortTarget in pairs( EscortTargets ) do --- local EscortObject = EscortTarget.object --- self:T( EscortObject ) --- if EscortObject and EscortObject:isExist() and EscortObject.id_ < 50000000 then --- --- local EscortTargetUnit = UNIT:Find( EscortObject ) --- local EscortTargetUnitName = EscortTargetUnit:GetName() --- --- --- --- -- local EscortTargetIsDetected, --- -- EscortTargetIsVisible, --- -- EscortTargetLastTime, --- -- EscortTargetKnowType, --- -- EscortTargetKnowDistance, --- -- EscortTargetLastPos, --- -- EscortTargetLastVelocity --- -- = self.EscortGroup:IsTargetDetected( EscortObject ) --- -- --- -- self:T( { EscortTargetIsDetected, --- -- EscortTargetIsVisible, --- -- EscortTargetLastTime, --- -- EscortTargetKnowType, --- -- EscortTargetKnowDistance, --- -- EscortTargetLastPos, --- -- EscortTargetLastVelocity } ) --- --- --- local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3() --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + --- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + --- ( EscortTargetUnitVec3.z - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- --- self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } ) --- --- if Distance <= 15 then --- --- if not ClientEscortTargets[EscortTargetUnitName] then --- ClientEscortTargets[EscortTargetUnitName] = {} --- end --- ClientEscortTargets[EscortTargetUnitName].AttackUnit = EscortTargetUnit --- ClientEscortTargets[EscortTargetUnitName].visible = EscortTarget.visible --- ClientEscortTargets[EscortTargetUnitName].type = EscortTarget.type --- ClientEscortTargets[EscortTargetUnitName].distance = EscortTarget.distance --- else --- if ClientEscortTargets[EscortTargetUnitName] then --- ClientEscortTargets[EscortTargetUnitName] = nil --- end --- end --- end --- end --- --- self:T( { "Sorting Targets Table:", ClientEscortTargets } ) --- table.sort( ClientEscortTargets, function( a, b ) return a.Distance < b.Distance end ) --- self:T( { "Sorted Targets Table:", ClientEscortTargets } ) --- --- -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. --- self.EscortMenuAttackNearbyTargets:RemoveSubMenus() --- --- if self.EscortMenuTargetAssistance then --- self.EscortMenuTargetAssistance:RemoveSubMenus() --- end --- --- --for MenuIndex = 1, #self.EscortMenuAttackTargets do --- -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } ) --- -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove() --- --end --- --- --- if ClientEscortTargets then --- for ClientEscortTargetUnitName, ClientEscortTargetData in pairs( ClientEscortTargets ) do --- --- for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do --- --- if ClientEscortTargetData and ClientEscortTargetData.AttackUnit:IsAlive() then --- --- local EscortTargetMessage = "" --- local EscortTargetCategoryName = ClientEscortTargetData.AttackUnit:GetCategoryName() --- local EscortTargetCategoryType = ClientEscortTargetData.AttackUnit:GetTypeName() --- if ClientEscortTargetData.type then --- EscortTargetMessage = EscortTargetMessage .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at " --- else --- EscortTargetMessage = EscortTargetMessage .. "Unknown target at " --- end --- --- local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3() --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + --- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + --- ( EscortTargetUnitVec3.z - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- --- self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } ) --- if ClientEscortTargetData.visible == false then --- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " estimated km" --- else --- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km" --- end --- --- if ClientEscortTargetData.visible then --- EscortTargetMessage = EscortTargetMessage .. ", visual" --- end --- --- if ClientEscortGroupName == EscortGroupName then --- --- MENU_CLIENT_COMMAND:New( self.EscortClient, --- EscortTargetMessage, --- self.EscortMenuAttackNearbyTargets, --- ESCORT._AttackTarget, --- { ParamSelf = self, --- ParamUnit = ClientEscortTargetData.AttackUnit --- } --- ) --- EscortTargetMessages = EscortTargetMessages .. "\n - " .. EscortTargetMessage --- else --- if self.EscortMenuTargetAssistance then --- local MenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance ) --- MENU_CLIENT_COMMAND:New( self.EscortClient, --- EscortTargetMessage, --- MenuTargetAssistance, --- ESCORT._AssistTarget, --- self, --- EscortGroupData.EscortGroup, --- ClientEscortTargetData.AttackUnit --- ) --- end --- end --- else --- ClientEscortTargetData = nil --- end --- end --- end --- --- if EscortTargetMessages ~= "" and self.ReportTargets == true then --- self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient ) --- else --- self.EscortGroup:MessageToClient( "No targets detected!", 20, self.EscortClient ) --- end --- end --- --- if self.EscortMenuResumeMission then --- self.EscortMenuResumeMission:RemoveSubMenus() --- --- -- if self.EscortMenuResumeWayPoints then --- -- for MenuIndex = 1, #self.EscortMenuResumeWayPoints do --- -- self:T( { "Remove Menu:", self.EscortMenuResumeWayPoints[MenuIndex] } ) --- -- self.EscortMenuResumeWayPoints[MenuIndex] = self.EscortMenuResumeWayPoints[MenuIndex]:Remove() --- -- end --- -- end --- --- local TaskPoints = self:RegisterRoute() --- for WayPointID, WayPoint in pairs( TaskPoints ) do --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + --- ( WayPoint.y - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- MENU_CLIENT_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } ) --- end --- end --- --- return true - end - end - - return false -end ---- This module contains the MISSILETRAINER class. --- --- === --- --- 1) @{MissileTrainer#MISSILETRAINER} class, extends @{Base#BASE} --- =============================================================== --- The @{#MISSILETRAINER} class uses the DCS world messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, --- the class will destroy the missile within a certain range, to avoid damage to your aircraft. --- It suports the following functionality: --- --- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes. --- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range � --- * Provide alerts when a missile would have killed your aircraft. --- * Provide alerts when the missile self destructs. --- * Enable / Disable and Configure the Missile Trainer using the various menu options. --- --- When running a mission where MISSILETRAINER is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players: --- --- * **Messages**: Menu to configure all messages. --- * **Messages On**: Show all messages. --- * **Messages Off**: Disable all messages. --- * **Tracking**: Menu to configure missile tracking messages. --- * **To All**: Shows missile tracking messages to all players. --- * **To Target**: Shows missile tracking messages only to the player where the missile is targetted at. --- * **Tracking On**: Show missile tracking messages. --- * **Tracking Off**: Disable missile tracking messages. --- * **Frequency Increase**: Increases the missile tracking message frequency with one second. --- * **Frequency Decrease**: Decreases the missile tracking message frequency with one second. --- * **Alerts**: Menu to configure alert messages. --- * **To All**: Shows alert messages to all players. --- * **To Target**: Shows alert messages only to the player where the missile is (was) targetted at. --- * **Hits On**: Show missile hit alert messages. --- * **Hits Off**: Disable missile hit alert messages. --- * **Launches On**: Show missile launch messages. --- * **Launches Off**: Disable missile launch messages. --- * **Details**: Menu to configure message details. --- * **Range On**: Shows range information when a missile is fired to a target. --- * **Range Off**: Disable range information when a missile is fired to a target. --- * **Bearing On**: Shows bearing information when a missile is fired to a target. --- * **Bearing Off**: Disable bearing information when a missile is fired to a target. --- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured. --- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter. --- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter. --- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter. --- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter. --- --- --- 1.1) MISSILETRAINER construction methods: --- ----------------------------------------- --- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method: --- --- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed. --- --- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those. --- --- 1.2) MISSILETRAINER initialization methods: --- ------------------------------------------- --- A MISSILETRAINER object will behave differently based on the usage of initialization methods: --- --- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF. --- * @{#MISSILETRAINER.InitTrackingToAll}: Sets by default the missile tracking report for all players or only for those missiles targetted to you. --- * @{#MISSILETRAINER.InitTrackingOnOff}: Sets by default the display of missile tracking report to be ON or OFF. --- * @{#MISSILETRAINER.InitTrackingFrequency}: Increases, decreases the missile tracking message display frequency with the provided time interval in seconds. --- * @{#MISSILETRAINER.InitAlertsToAll}: Sets by default the display of alerts to be shown to all players or only to you. --- * @{#MISSILETRAINER.InitAlertsHitsOnOff}: Sets by default the display of hit alerts ON or OFF. --- * @{#MISSILETRAINER.InitAlertsLaunchesOnOff}: Sets by default the display of launch alerts ON or OFF. --- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF. --- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF. --- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu. --- --- === --- --- CREDITS --- ======= --- **Stuka (Danny)** Who you can search on the Eagle Dynamics Forums. --- Working together with Danny has resulted in the MISSILETRAINER class. --- Danny has shared his ideas and together we made a design. --- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback! --- --- @module MissileTrainer --- @author FlightControl - - ---- The MISSILETRAINER class --- @type MISSILETRAINER --- @field Core.Set#SET_CLIENT DBClients --- @extends Core.Base#BASE -MISSILETRAINER = { - ClassName = "MISSILETRAINER", - TrackingMissiles = {}, -} - -function MISSILETRAINER._Alive( Client, self ) - - if self.Briefing then - Client:Message( self.Briefing, 15, "Trainer" ) - end - - if self.MenusOnOff == true then - Client:Message( "Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).", 15, "Trainer" ) - - Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) -- Menu#MENU_CLIENT - - Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) - Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) - Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) - - Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu ) - Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } ) - Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } ) - Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = true } ) - Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = false } ) - Client.MenuTrackIncrease = MENU_CLIENT_COMMAND:New( Client, "Frequency Increase", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = -1 } ) - Client.MenuTrackDecrease = MENU_CLIENT_COMMAND:New( Client, "Frequency Decrease", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = 1 } ) - - Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu ) - Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } ) - Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } ) - Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = true } ) - Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = false } ) - Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = true } ) - Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = false } ) - - Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu ) - Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = true } ) - Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = false } ) - Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = true } ) - Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = false } ) - - Client.MenuDistance = MENU_CLIENT:New( Client, "Set distance to plane", Client.MainMenu ) - Client.MenuDistance50 = MENU_CLIENT_COMMAND:New( Client, "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } ) - Client.MenuDistance100 = MENU_CLIENT_COMMAND:New( Client, "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } ) - Client.MenuDistance150 = MENU_CLIENT_COMMAND:New( Client, "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } ) - Client.MenuDistance200 = MENU_CLIENT_COMMAND:New( Client, "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } ) - else - if Client.MainMenu then - Client.MainMenu:Remove() - end - end - - local ClientID = Client:GetID() - self:T( ClientID ) - if not self.TrackingMissiles[ClientID] then - self.TrackingMissiles[ClientID] = {} - end - self.TrackingMissiles[ClientID].Client = Client - if not self.TrackingMissiles[ClientID].MissileData then - self.TrackingMissiles[ClientID].MissileData = {} - end -end - ---- Creates the main object which is handling missile tracking. --- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed. --- @param #MISSILETRAINER self --- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player. --- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes. --- @return #MISSILETRAINER -function MISSILETRAINER:New( Distance, Briefing ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( Distance ) - - if Briefing then - self.Briefing = Briefing - end - - self.Schedulers = {} - self.SchedulerID = 0 - - self.MessageInterval = 2 - self.MessageLastTime = timer.getTime() - - self.Distance = Distance / 1000 - - self:HandleEvent( EVENTS.Shot ) - - self.DBClients = SET_CLIENT:New():FilterStart() - - --- for ClientID, Client in pairs( self.DBClients.Database ) do --- self:E( "ForEach:" .. Client.UnitName ) --- Client:Alive( self._Alive, self ) --- end --- - self.DBClients:ForEachClient( - function( Client ) - self:E( "ForEach:" .. Client.UnitName ) - Client:Alive( self._Alive, self ) - end - ) - - - --- self.DB:ForEachClient( --- --- @param Wrapper.Client#CLIENT Client --- function( Client ) --- --- ... actions ... --- --- end --- ) - - self.MessagesOnOff = true - - self.TrackingToAll = false - self.TrackingOnOff = true - self.TrackingFrequency = 3 - - self.AlertsToAll = true - self.AlertsHitsOnOff = true - self.AlertsLaunchesOnOff = true - - self.DetailsRangeOnOff = true - self.DetailsBearingOnOff = true - - self.MenusOnOff = true - - self.TrackingMissiles = {} - - self.TrackingScheduler = SCHEDULER:New( self, self._TrackMissiles, {}, 0.5, 0.05, 0 ) - - return self -end - --- Initialization methods. - - - ---- Sets by default the display of any message to be ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean MessagesOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitMessagesOnOff( MessagesOnOff ) - self:F( MessagesOnOff ) - - self.MessagesOnOff = MessagesOnOff - if self.MessagesOnOff == true then - MESSAGE:New( "Messages ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Messages OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the missile tracking report for all players or only for those missiles targetted to you. --- @param #MISSILETRAINER self --- @param #boolean TrackingToAll true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingToAll( TrackingToAll ) - self:F( TrackingToAll ) - - self.TrackingToAll = TrackingToAll - if self.TrackingToAll == true then - MESSAGE:New( "Missile tracking to all players ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Missile tracking to all players OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of missile tracking report to be ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean TrackingOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingOnOff( TrackingOnOff ) - self:F( TrackingOnOff ) - - self.TrackingOnOff = TrackingOnOff - if self.TrackingOnOff == true then - MESSAGE:New( "Missile tracking ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Missile tracking OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds. --- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update. --- @param #MISSILETRAINER self --- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency. --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency ) - self:F( TrackingFrequency ) - - self.TrackingFrequency = self.TrackingFrequency + TrackingFrequency - if self.TrackingFrequency < 0.5 then - self.TrackingFrequency = 0.5 - end - if self.TrackingFrequency then - MESSAGE:New( "Missile tracking frequency is " .. self.TrackingFrequency .. " seconds.", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of alerts to be shown to all players or only to you. --- @param #MISSILETRAINER self --- @param #boolean AlertsToAll true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsToAll( AlertsToAll ) - self:F( AlertsToAll ) - - self.AlertsToAll = AlertsToAll - if self.AlertsToAll == true then - MESSAGE:New( "Alerts to all players ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts to all players OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of hit alerts ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean AlertsHitsOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsHitsOnOff( AlertsHitsOnOff ) - self:F( AlertsHitsOnOff ) - - self.AlertsHitsOnOff = AlertsHitsOnOff - if self.AlertsHitsOnOff == true then - MESSAGE:New( "Alerts Hits ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts Hits OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of launch alerts ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean AlertsLaunchesOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsLaunchesOnOff( AlertsLaunchesOnOff ) - self:F( AlertsLaunchesOnOff ) - - self.AlertsLaunchesOnOff = AlertsLaunchesOnOff - if self.AlertsLaunchesOnOff == true then - MESSAGE:New( "Alerts Launches ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts Launches OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of range information of missiles ON of OFF. --- @param #MISSILETRAINER self --- @param #boolean DetailsRangeOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitRangeOnOff( DetailsRangeOnOff ) - self:F( DetailsRangeOnOff ) - - self.DetailsRangeOnOff = DetailsRangeOnOff - if self.DetailsRangeOnOff == true then - MESSAGE:New( "Range display ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Range display OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of bearing information of missiles ON of OFF. --- @param #MISSILETRAINER self --- @param #boolean DetailsBearingOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitBearingOnOff( DetailsBearingOnOff ) - self:F( DetailsBearingOnOff ) - - self.DetailsBearingOnOff = DetailsBearingOnOff - if self.DetailsBearingOnOff == true then - MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Enables / Disables the menus. --- @param #MISSILETRAINER self --- @param #boolean MenusOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitMenusOnOff( MenusOnOff ) - self:F( MenusOnOff ) - - self.MenusOnOff = MenusOnOff - if self.MenusOnOff == true then - MESSAGE:New( "Menus are ENABLED (only when a player rejoins a slot)", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Menus are DISABLED", 15, "Menu" ):ToAll() - end - - return self -end - - --- Menu functions - -function MISSILETRAINER._MenuMessages( MenuParameters ) - - local self = MenuParameters.MenuSelf - - if MenuParameters.MessagesOnOff ~= nil then - self:InitMessagesOnOff( MenuParameters.MessagesOnOff ) - end - - if MenuParameters.TrackingToAll ~= nil then - self:InitTrackingToAll( MenuParameters.TrackingToAll ) - end - - if MenuParameters.TrackingOnOff ~= nil then - self:InitTrackingOnOff( MenuParameters.TrackingOnOff ) - end - - if MenuParameters.TrackingFrequency ~= nil then - self:InitTrackingFrequency( MenuParameters.TrackingFrequency ) - end - - if MenuParameters.AlertsToAll ~= nil then - self:InitAlertsToAll( MenuParameters.AlertsToAll ) - end - - if MenuParameters.AlertsHitsOnOff ~= nil then - self:InitAlertsHitsOnOff( MenuParameters.AlertsHitsOnOff ) - end - - if MenuParameters.AlertsLaunchesOnOff ~= nil then - self:InitAlertsLaunchesOnOff( MenuParameters.AlertsLaunchesOnOff ) - end - - if MenuParameters.DetailsRangeOnOff ~= nil then - self:InitRangeOnOff( MenuParameters.DetailsRangeOnOff ) - end - - if MenuParameters.DetailsBearingOnOff ~= nil then - self:InitBearingOnOff( MenuParameters.DetailsBearingOnOff ) - end - - if MenuParameters.Distance ~= nil then - self.Distance = MenuParameters.Distance - MESSAGE:New( "Hit detection distance set to " .. self.Distance .. " meters", 15, "Menu" ):ToAll() - end - -end - ---- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @param #MISSILETRAINER self --- @param Core.Event#EVENTDATA EventData -function MISSILETRAINER:OnEventShot( EVentData ) - self:F( { EVentData } ) - - local TrainerSourceDCSUnit = EVentData.IniDCSUnit - local TrainerSourceDCSUnitName = EVentData.IniDCSUnitName - local TrainerWeapon = EVentData.Weapon -- Identify the weapon fired - local TrainerWeaponName = EVentData.WeaponName -- return weapon type - - self:T( "Missile Launched = " .. TrainerWeaponName ) - - local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target - if TrainerTargetDCSUnit then - local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit ) - local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill - - self:T(TrainerTargetDCSUnitName ) - - local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName ) - if Client then - - local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit ) - local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit ) - - if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then - - local Message = MESSAGE:New( - string.format( "%s launched a %s", - TrainerSourceUnit:GetTypeName(), - TrainerWeaponName - ) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" ) - - if self.AlertsToAll then - Message:ToAll() - else - Message:ToClient( Client ) - end - end - - local ClientID = Client:GetID() - self:T( ClientID ) - local MissileData = {} - MissileData.TrainerSourceUnit = TrainerSourceUnit - MissileData.TrainerWeapon = TrainerWeapon - MissileData.TrainerTargetUnit = TrainerTargetUnit - MissileData.TrainerWeaponTypeName = TrainerWeapon:getTypeName() - MissileData.TrainerWeaponLaunched = true - table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData ) - --self:T( self.TrackingMissiles ) - end - else - -- TODO: some weapons don't know the target unit... Need to develop a workaround for this. - if ( TrainerWeapon:getTypeName() == "9M311" ) then - SCHEDULER:New( TrainerWeapon, TrainerWeapon.destroy, {}, 1 ) - else - end - end -end - -function MISSILETRAINER:_AddRange( Client, TrainerWeapon ) - - local RangeText = "" - - if self.DetailsRangeOnOff then - - local PositionMissile = TrainerWeapon:getPoint() - local TargetVec3 = Client:GetVec3() - - local Range = ( ( PositionMissile.x - TargetVec3.x )^2 + - ( PositionMissile.y - TargetVec3.y )^2 + - ( PositionMissile.z - TargetVec3.z )^2 - ) ^ 0.5 / 1000 - - RangeText = string.format( ", at %4.2fkm", Range ) - end - - return RangeText -end - -function MISSILETRAINER:_AddBearing( Client, TrainerWeapon ) - - local BearingText = "" - - if self.DetailsBearingOnOff then - - local PositionMissile = TrainerWeapon:getPoint() - local TargetVec3 = Client:GetVec3() - - self:T2( { TargetVec3, PositionMissile }) - - local DirectionVector = { x = PositionMissile.x - TargetVec3.x, y = PositionMissile.y - TargetVec3.y, z = PositionMissile.z - TargetVec3.z } - local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) - --DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget ) - if DirectionRadians < 0 then - DirectionRadians = DirectionRadians + 2 * math.pi - end - local DirectionDegrees = DirectionRadians * 180 / math.pi - - BearingText = string.format( ", %d degrees", DirectionDegrees ) - end - - return BearingText -end - - -function MISSILETRAINER:_TrackMissiles() - self:F2() - - - local ShowMessages = false - if self.MessagesOnOff and self.MessageLastTime + self.TrackingFrequency <= timer.getTime() then - self.MessageLastTime = timer.getTime() - ShowMessages = true - end - - -- ALERTS PART - - -- Loop for all Player Clients to check the alerts and deletion of missiles. - for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do - - local Client = ClientData.Client - self:T2( { Client:GetName() } ) - - for MissileDataID, MissileData in pairs( ClientData.MissileData ) do - self:T3( MissileDataID ) - - local TrainerSourceUnit = MissileData.TrainerSourceUnit - local TrainerWeapon = MissileData.TrainerWeapon - local TrainerTargetUnit = MissileData.TrainerTargetUnit - local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName - local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - - if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then - local PositionMissile = TrainerWeapon:getPosition().p - local TargetVec3 = Client:GetVec3() - - local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 + - ( PositionMissile.y - TargetVec3.y )^2 + - ( PositionMissile.z - TargetVec3.z )^2 - ) ^ 0.5 / 1000 - - if Distance <= self.Distance then - -- Hit alert - TrainerWeapon:destroy() - if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then - - self:T( "killed" ) - - local Message = MESSAGE:New( - string.format( "%s launched by %s killed %s", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetTypeName(), - TrainerTargetUnit:GetPlayerName() - ), 15, "Hit Alert" ) - - if self.AlertsToAll == true then - Message:ToAll() - else - Message:ToClient( Client ) - end - - MissileData = nil - table.remove( ClientData.MissileData, MissileDataID ) - self:T(ClientData.MissileData) - end - end - else - if not ( TrainerWeapon and TrainerWeapon:isExist() ) then - if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then - -- Weapon does not exist anymore. Delete from Table - local Message = MESSAGE:New( - string.format( "%s launched by %s self destructed!", - TrainerWeaponTypeName, - TrainerSourceUnit:GetTypeName() - ), 5, "Tracking" ) - - if self.AlertsToAll == true then - Message:ToAll() - else - Message:ToClient( Client ) - end - end - MissileData = nil - table.remove( ClientData.MissileData, MissileDataID ) - self:T( ClientData.MissileData ) - end - end - end - end - - if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed. - - -- TRACKING PART - - -- For the current client, the missile range and bearing details are displayed To the Player Client. - -- For the other clients, the missile range and bearing details are displayed To the other Player Clients. - -- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information. - - -- Main Player Client loop - for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do - - local Client = ClientData.Client - self:T2( { Client:GetName() } ) - - - ClientData.MessageToClient = "" - ClientData.MessageToAll = "" - - -- Other Players Client loop - for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do - - for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do - self:T3( MissileDataID ) - - local TrainerSourceUnit = MissileData.TrainerSourceUnit - local TrainerWeapon = MissileData.TrainerWeapon - local TrainerTargetUnit = MissileData.TrainerTargetUnit - local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName - local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - - if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then - - if ShowMessages == true then - local TrackingTo - TrackingTo = string.format( " -> %s", - TrainerWeaponTypeName - ) - - if ClientDataID == TrackingDataID then - if ClientData.MessageToClient == "" then - ClientData.MessageToClient = "Missiles to You:\n" - end - ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" - else - if self.TrackingToAll == true then - if ClientData.MessageToAll == "" then - ClientData.MessageToAll = "Missiles to other Players:\n" - end - ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. " ( " .. TrainerTargetUnit:GetPlayerName() .. " )\n" - end - end - end - end - end - end - - -- Once the Player Client and the Other Player Client tracking messages are prepared, show them. - if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then - local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client ) - end - end - end - - return true -end ---- This module contains the AIRBASEPOLICE classes. --- --- === --- --- 1) @{AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Base#BASE} --- ================================================================== --- The @{AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. --- CLIENTS should not be allowed to: --- --- * Don't taxi faster than 40 km/h. --- * Don't take-off on taxiways. --- * Avoid to hit other planes on the airbase. --- * Obey ground control orders. --- --- 2) @{AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} --- ============================================================================================= --- All the airbases on the caucasus map can be monitored using this class. --- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. --- The following names can be given: --- * AnapaVityazevo --- * Batumi --- * Beslan --- * Gelendzhik --- * Gudauta --- * Kobuleti --- * KrasnodarCenter --- * KrasnodarPashkovsky --- * Krymsk --- * Kutaisi --- * MaykopKhanskaya --- * MineralnyeVody --- * Mozdok --- * Nalchik --- * Novorossiysk --- * SenakiKolkhi --- * SochiAdler --- * Soganlug --- * SukhumiBabushara --- * TbilisiLochini --- * Vaziani --- --- 3) @{AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} --- ============================================================================================= --- All the airbases on the NEVADA map can be monitored using this class. --- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. --- The following names can be given: --- * Nellis --- * McCarran --- * Creech --- * Groom Lake --- --- ### Contributions: Dutch Baron - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module AirbasePolice - - - - - ---- @type AIRBASEPOLICE_BASE --- @field Core.Set#SET_CLIENT SetClient --- @extends Core.Base#BASE - -AIRBASEPOLICE_BASE = { - ClassName = "AIRBASEPOLICE_BASE", - SetClient = nil, - Airbases = nil, - AirbaseNames = nil, -} - - ---- Creates a new AIRBASEPOLICE_BASE object. --- @param #AIRBASEPOLICE_BASE self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @param Airbases A table of Airbase Names. --- @return #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:New( SetClient, Airbases ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - self:E( { self.ClassName, SetClient, Airbases } ) - - self.SetClient = SetClient - self.Airbases = Airbases - - for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do - Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(SMOKECOLOR.Red):Flush() - end - end - --- -- Template --- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) --- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) --- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - - self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client - function( Client ) - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0) - Client:SetState( self, "Taxi", false ) - end - ) - - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, {}, 0, 2, 0.05 ) - - return self -end - ---- @type AIRBASEPOLICE_BASE.AirbaseNames --- @list <#string> - ---- Monitor a table of airbase names. --- @param #AIRBASEPOLICE_BASE self --- @param #AIRBASEPOLICE_BASE.AirbaseNames AirbaseNames A list of AirbaseNames to monitor. If this parameters is nil, then all airbases will be monitored. --- @return #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:Monitor( AirbaseNames ) - - if AirbaseNames then - if type( AirbaseNames ) == "table" then - self.AirbaseNames = AirbaseNames - else - self.AirbaseNames = { AirbaseNames } - end - end -end - ---- @param #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:_AirbaseMonitor() - - for AirbaseID, Airbase in pairs( self.Airbases ) do - - if not self.AirbaseNames or self.AirbaseNames[AirbaseID] then - - self:E( AirbaseID ) - - self.SetClient:ForEachClientInZone( Airbase.ZoneBoundary, - - --- @param Wrapper.Client#CLIENT Client - function( Client ) - - self:E( Client.UnitName ) - if Client:IsAlive() then - local NotInRunwayZone = true - for ZoneRunwayID, ZoneRunway in pairs( Airbase.ZoneRunways ) do - NotInRunwayZone = ( Client:IsNotInZone( ZoneRunway ) == true ) and NotInRunwayZone or false - end - - if NotInRunwayZone then - local Taxi = self:GetState( self, "Taxi" ) - self:E( Taxi ) - if Taxi == false then - Client:Message( "Welcome at " .. AirbaseID .. ". The maximum taxiing speed is " .. Airbase.MaximumSpeed " km/h.", 20, "ATC" ) - self:SetState( self, "Taxi", true ) - end - - -- TODO: GetVelocityKMH function usage - local VelocityVec3 = Client:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - -- MESSAGE:New( "Velocity = " .. Velocity, 1 ):ToAll() - local IsAboveRunway = Client:IsAboveRunway() - local IsOnGround = Client:InAir() == false - self:T( IsAboveRunway, IsOnGround ) - - if IsAboveRunway and IsOnGround then - - if Velocity > Airbase.MaximumSpeed then - local IsSpeeding = Client:GetState( self, "Speeding" ) - - if IsSpeeding == true then - local SpeedingWarnings = Client:GetState( self, "Warnings" ) - self:T( SpeedingWarnings ) - - if SpeedingWarnings <= 3 then - Client:Message( "You are speeding on the taxiway! Slow down or you will be removed from this airbase! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Warning " .. SpeedingWarnings .. " / 3" ) - Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) - else - MESSAGE:New( "Player " .. Client:GetPlayerName() .. " has been removed from the airbase, due to a speeding violation ...", 10, "Airbase Police" ):ToAll() - Client:Destroy() - trigger.action.setUserFlag( "AIRCRAFT_"..Client:GetID(), 100) - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - end - - else - Client:Message( "You are speeding on the taxiway, slow down now! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Attention! " ) - Client:SetState( self, "Speeding", true ) - Client:SetState( self, "Warnings", 1 ) - end - - else - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - end - end - - else - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - local Taxi = self:GetState( self, "Taxi" ) - if Taxi == true then - Client:Message( "You have progressed to the runway ... Await take-off clearance ...", 20, "ATC" ) - self:SetState( self, "Taxi", false ) - end - end - end - end - ) - end - end - - return true -end - - ---- @type AIRBASEPOLICE_CAUCASUS --- @field Core.Set#SET_CLIENT SetClient --- @extends #AIRBASEPOLICE_BASE - -AIRBASEPOLICE_CAUCASUS = { - ClassName = "AIRBASEPOLICE_CAUCASUS", - Airbases = { - AnapaVityazevo = { - PointsBoundary = { - [1]={["y"]=242234.85714287,["x"]=-6616.5714285726,}, - [2]={["y"]=241060.57142858,["x"]=-5585.142857144,}, - [3]={["y"]=243806.2857143,["x"]=-3962.2857142868,}, - [4]={["y"]=245240.57142858,["x"]=-4816.5714285726,}, - [5]={["y"]=244783.42857144,["x"]=-5630.8571428583,}, - [6]={["y"]=243800.57142858,["x"]=-5065.142857144,}, - [7]={["y"]=242232.00000001,["x"]=-6622.2857142868,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=242140.57142858,["x"]=-6478.8571428583,}, - [2]={["y"]=242188.57142858,["x"]=-6522.0000000011,}, - [3]={["y"]=244124.2857143,["x"]=-4344.0000000011,}, - [4]={["y"]=244068.2857143,["x"]=-4296.5714285726,}, - [5]={["y"]=242140.57142858,["x"]=-6480.0000000011,} - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Batumi = { - PointsBoundary = { - [1]={["y"]=617567.14285714,["x"]=-355313.14285715,}, - [2]={["y"]=616181.42857142,["x"]=-354800.28571429,}, - [3]={["y"]=616007.14285714,["x"]=-355128.85714286,}, - [4]={["y"]=618230,["x"]=-356914.57142858,}, - [5]={["y"]=618727.14285714,["x"]=-356166,}, - [6]={["y"]=617572.85714285,["x"]=-355308.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=616442.28571429,["x"]=-355090.28571429,}, - [2]={["y"]=618450.57142857,["x"]=-356522,}, - [3]={["y"]=618407.71428571,["x"]=-356584.85714286,}, - [4]={["y"]=618361.99999999,["x"]=-356554.85714286,}, - [5]={["y"]=618324.85714285,["x"]=-356599.14285715,}, - [6]={["y"]=618250.57142856,["x"]=-356543.42857143,}, - [7]={["y"]=618257.7142857,["x"]=-356496.28571429,}, - [8]={["y"]=618237.7142857,["x"]=-356459.14285715,}, - [9]={["y"]=616555.71428571,["x"]=-355258.85714286,}, - [10]={["y"]=616486.28571428,["x"]=-355280.57142858,}, - [11]={["y"]=616410.57142856,["x"]=-355227.71428572,}, - [12]={["y"]=616441.99999999,["x"]=-355179.14285715,}, - [13]={["y"]=616401.99999999,["x"]=-355147.71428572,}, - [14]={["y"]=616441.42857142,["x"]=-355092.57142858,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Beslan = { - PointsBoundary = { - [1]={["y"]=842082.57142857,["x"]=-148445.14285715,}, - [2]={["y"]=845237.71428572,["x"]=-148639.71428572,}, - [3]={["y"]=845232,["x"]=-148765.42857143,}, - [4]={["y"]=844220.57142857,["x"]=-149168.28571429,}, - [5]={["y"]=843274.85714286,["x"]=-149125.42857143,}, - [6]={["y"]=842077.71428572,["x"]=-148554,}, - [7]={["y"]=842083.42857143,["x"]=-148445.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=842104.57142857,["x"]=-148460.57142857,}, - [2]={["y"]=845225.71428572,["x"]=-148656,}, - [3]={["y"]=845220.57142858,["x"]=-148750,}, - [4]={["y"]=842098.85714286,["x"]=-148556.28571429,}, - [5]={["y"]=842104,["x"]=-148460.28571429,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Gelendzhik = { - PointsBoundary = { - [1]={["y"]=297856.00000001,["x"]=-51151.428571429,}, - [2]={["y"]=299044.57142858,["x"]=-49720.000000001,}, - [3]={["y"]=298861.71428572,["x"]=-49580.000000001,}, - [4]={["y"]=298198.85714286,["x"]=-49842.857142858,}, - [5]={["y"]=297990.28571429,["x"]=-50151.428571429,}, - [6]={["y"]=297696.00000001,["x"]=-51054.285714286,}, - [7]={["y"]=297850.28571429,["x"]=-51160.000000001,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=297834.00000001,["x"]=-51107.428571429,}, - [2]={["y"]=297786.57142858,["x"]=-51068.857142858,}, - [3]={["y"]=298946.57142858,["x"]=-49686.000000001,}, - [4]={["y"]=298993.14285715,["x"]=-49725.714285715,}, - [5]={["y"]=297835.14285715,["x"]=-51107.714285715,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Gudauta = { - PointsBoundary = { - [1]={["y"]=517246.57142857,["x"]=-197850.28571429,}, - [2]={["y"]=516749.42857142,["x"]=-198070.28571429,}, - [3]={["y"]=515755.14285714,["x"]=-197598.85714286,}, - [4]={["y"]=515369.42857142,["x"]=-196538.85714286,}, - [5]={["y"]=515623.71428571,["x"]=-195618.85714286,}, - [6]={["y"]=515946.57142857,["x"]=-195510.28571429,}, - [7]={["y"]=517243.71428571,["x"]=-197858.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=517096.57142857,["x"]=-197804.57142857,}, - [2]={["y"]=515880.85714285,["x"]=-195590.28571429,}, - [3]={["y"]=515812.28571428,["x"]=-195628.85714286,}, - [4]={["y"]=517036.57142857,["x"]=-197834.57142857,}, - [5]={["y"]=517097.99999999,["x"]=-197807.42857143,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Kobuleti = { - PointsBoundary = { - [1]={["y"]=634427.71428571,["x"]=-318290.28571429,}, - [2]={["y"]=635033.42857143,["x"]=-317550.2857143,}, - [3]={["y"]=635864.85714286,["x"]=-317333.14285715,}, - [4]={["y"]=636967.71428571,["x"]=-317261.71428572,}, - [5]={["y"]=637144.85714286,["x"]=-317913.14285715,}, - [6]={["y"]=634630.57142857,["x"]=-318687.42857144,}, - [7]={["y"]=634424.85714286,["x"]=-318290.2857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=634509.71428571,["x"]=-318339.42857144,}, - [2]={["y"]=636767.42857143,["x"]=-317516.57142858,}, - [3]={["y"]=636790,["x"]=-317575.71428572,}, - [4]={["y"]=634531.42857143,["x"]=-318398.00000001,}, - [5]={["y"]=634510.28571429,["x"]=-318339.71428572,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - KrasnodarCenter = { - PointsBoundary = { - [1]={["y"]=366680.28571429,["x"]=11699.142857142,}, - [2]={["y"]=366654.28571429,["x"]=11225.142857142,}, - [3]={["y"]=367497.14285715,["x"]=11082.285714285,}, - [4]={["y"]=368025.71428572,["x"]=10396.57142857,}, - [5]={["y"]=369854.28571429,["x"]=11367.999999999,}, - [6]={["y"]=369840.00000001,["x"]=11910.857142856,}, - [7]={["y"]=366682.57142858,["x"]=11697.999999999,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=369205.42857144,["x"]=11789.142857142,}, - [2]={["y"]=369209.71428572,["x"]=11714.857142856,}, - [3]={["y"]=366699.71428572,["x"]=11581.714285713,}, - [4]={["y"]=366698.28571429,["x"]=11659.142857142,}, - [5]={["y"]=369208.85714286,["x"]=11788.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - KrasnodarPashkovsky = { - PointsBoundary = { - [1]={["y"]=386754,["x"]=6476.5714285703,}, - [2]={["y"]=389182.57142858,["x"]=8722.2857142846,}, - [3]={["y"]=388832.57142858,["x"]=9086.5714285703,}, - [4]={["y"]=386961.14285715,["x"]=7707.9999999989,}, - [5]={["y"]=385404,["x"]=9179.4285714274,}, - [6]={["y"]=383239.71428572,["x"]=7386.5714285703,}, - [7]={["y"]=383954,["x"]=6486.5714285703,}, - [8]={["y"]=385775.42857143,["x"]=8097.9999999989,}, - [9]={["y"]=386804,["x"]=7319.4285714274,}, - [10]={["y"]=386375.42857143,["x"]=6797.9999999989,}, - [11]={["y"]=386746.85714286,["x"]=6472.2857142846,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=385891.14285715,["x"]=8416.5714285703,}, - [2]={["y"]=385842.28571429,["x"]=8467.9999999989,}, - [3]={["y"]=384180.85714286,["x"]=6917.1428571417,}, - [4]={["y"]=384228.57142858,["x"]=6867.7142857132,}, - [5]={["y"]=385891.14285715,["x"]=8416.5714285703,}, - }, - [2] = { - [1]={["y"]=386714.85714286,["x"]=6674.857142856,}, - [2]={["y"]=386757.71428572,["x"]=6627.7142857132,}, - [3]={["y"]=389028.57142858,["x"]=8741.4285714275,}, - [4]={["y"]=388981.71428572,["x"]=8790.5714285703,}, - [5]={["y"]=386714.57142858,["x"]=6674.5714285703,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Krymsk = { - PointsBoundary = { - [1]={["y"]=293338.00000001,["x"]=-7575.4285714297,}, - [2]={["y"]=295199.42857144,["x"]=-5434.0000000011,}, - [3]={["y"]=295595.14285715,["x"]=-6239.7142857154,}, - [4]={["y"]=294152.2857143,["x"]=-8325.4285714297,}, - [5]={["y"]=293345.14285715,["x"]=-7596.8571428582,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=293522.00000001,["x"]=-7567.4285714297,}, - [2]={["y"]=293578.57142858,["x"]=-7616.0000000011,}, - [3]={["y"]=295246.00000001,["x"]=-5591.142857144,}, - [4]={["y"]=295187.71428573,["x"]=-5546.0000000011,}, - [5]={["y"]=293523.14285715,["x"]=-7568.2857142868,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Kutaisi = { - PointsBoundary = { - [1]={["y"]=682087.42857143,["x"]=-284512.85714286,}, - [2]={["y"]=685387.42857143,["x"]=-283662.85714286,}, - [3]={["y"]=685294.57142857,["x"]=-284977.14285715,}, - [4]={["y"]=682744.57142857,["x"]=-286505.71428572,}, - [5]={["y"]=682094.57142857,["x"]=-284527.14285715,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=682638,["x"]=-285202.28571429,}, - [2]={["y"]=685050.28571429,["x"]=-284507.42857144,}, - [3]={["y"]=685068.85714286,["x"]=-284578.85714286,}, - [4]={["y"]=682657.42857143,["x"]=-285264.28571429,}, - [5]={["y"]=682638.28571429,["x"]=-285202.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - MaykopKhanskaya = { - PointsBoundary = { - [1]={["y"]=456876.28571429,["x"]=-27665.42857143,}, - [2]={["y"]=457800,["x"]=-28392.857142858,}, - [3]={["y"]=459368.57142857,["x"]=-26378.571428573,}, - [4]={["y"]=459425.71428572,["x"]=-25242.857142858,}, - [5]={["y"]=458961.42857143,["x"]=-24964.285714287,}, - [6]={["y"]=456878.57142857,["x"]=-27667.714285715,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=457005.42857143,["x"]=-27668.000000001,}, - [2]={["y"]=459028.85714286,["x"]=-25168.857142858,}, - [3]={["y"]=459082.57142857,["x"]=-25216.857142858,}, - [4]={["y"]=457060,["x"]=-27714.285714287,}, - [5]={["y"]=457004.57142857,["x"]=-27669.714285715,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - MineralnyeVody = { - PointsBoundary = { - [1]={["y"]=703857.14285714,["x"]=-50226.000000002,}, - [2]={["y"]=707385.71428571,["x"]=-51911.714285716,}, - [3]={["y"]=707595.71428571,["x"]=-51434.857142859,}, - [4]={["y"]=707900,["x"]=-51568.857142859,}, - [5]={["y"]=707542.85714286,["x"]=-52326.000000002,}, - [6]={["y"]=706628.57142857,["x"]=-52568.857142859,}, - [7]={["y"]=705142.85714286,["x"]=-51790.285714288,}, - [8]={["y"]=703678.57142857,["x"]=-50611.714285716,}, - [9]={["y"]=703857.42857143,["x"]=-50226.857142859,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=703904,["x"]=-50352.571428573,}, - [2]={["y"]=707596.28571429,["x"]=-52094.571428573,}, - [3]={["y"]=707560.57142858,["x"]=-52161.714285716,}, - [4]={["y"]=703871.71428572,["x"]=-50420.571428573,}, - [5]={["y"]=703902,["x"]=-50352.000000002,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Mozdok = { - PointsBoundary = { - [1]={["y"]=832123.42857143,["x"]=-83608.571428573,}, - [2]={["y"]=835916.28571429,["x"]=-83144.285714288,}, - [3]={["y"]=835474.28571429,["x"]=-84170.571428573,}, - [4]={["y"]=832911.42857143,["x"]=-84470.571428573,}, - [5]={["y"]=832487.71428572,["x"]=-85565.714285716,}, - [6]={["y"]=831573.42857143,["x"]=-85351.42857143,}, - [7]={["y"]=832123.71428572,["x"]=-83610.285714288,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=832201.14285715,["x"]=-83699.428571431,}, - [2]={["y"]=832212.57142857,["x"]=-83780.571428574,}, - [3]={["y"]=835730.28571429,["x"]=-83335.714285717,}, - [4]={["y"]=835718.85714286,["x"]=-83246.571428574,}, - [5]={["y"]=832200.57142857,["x"]=-83700.000000002,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Nalchik = { - PointsBoundary = { - [1]={["y"]=759370,["x"]=-125502.85714286,}, - [2]={["y"]=761384.28571429,["x"]=-124177.14285714,}, - [3]={["y"]=761472.85714286,["x"]=-124325.71428572,}, - [4]={["y"]=761092.85714286,["x"]=-125048.57142857,}, - [5]={["y"]=760295.71428572,["x"]=-125685.71428572,}, - [6]={["y"]=759444.28571429,["x"]=-125734.28571429,}, - [7]={["y"]=759375.71428572,["x"]=-125511.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=759454.28571429,["x"]=-125551.42857143,}, - [2]={["y"]=759492.85714286,["x"]=-125610.85714286,}, - [3]={["y"]=761406.28571429,["x"]=-124304.28571429,}, - [4]={["y"]=761361.14285714,["x"]=-124239.71428572,}, - [5]={["y"]=759456,["x"]=-125552.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Novorossiysk = { - PointsBoundary = { - [1]={["y"]=278677.71428573,["x"]=-41656.571428572,}, - [2]={["y"]=278446.2857143,["x"]=-41453.714285715,}, - [3]={["y"]=278989.14285716,["x"]=-40188.000000001,}, - [4]={["y"]=279717.71428573,["x"]=-39968.000000001,}, - [5]={["y"]=280020.57142859,["x"]=-40208.000000001,}, - [6]={["y"]=278674.85714287,["x"]=-41660.857142858,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=278673.14285716,["x"]=-41615.142857144,}, - [2]={["y"]=278625.42857144,["x"]=-41570.571428572,}, - [3]={["y"]=279835.42857144,["x"]=-40226.000000001,}, - [4]={["y"]=279882.2857143,["x"]=-40270.000000001,}, - [5]={["y"]=278672.00000001,["x"]=-41614.857142858,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SenakiKolkhi = { - PointsBoundary = { - [1]={["y"]=646036.57142857,["x"]=-281778.85714286,}, - [2]={["y"]=646045.14285714,["x"]=-281191.71428571,}, - [3]={["y"]=647032.28571429,["x"]=-280598.85714285,}, - [4]={["y"]=647669.42857143,["x"]=-281273.14285714,}, - [5]={["y"]=648323.71428571,["x"]=-281370.28571428,}, - [6]={["y"]=648520.85714286,["x"]=-281978.85714285,}, - [7]={["y"]=646039.42857143,["x"]=-281783.14285714,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=646060.85714285,["x"]=-281736,}, - [2]={["y"]=646056.57142857,["x"]=-281631.71428571,}, - [3]={["y"]=648442.28571428,["x"]=-281840.28571428,}, - [4]={["y"]=648432.28571428,["x"]=-281918.85714286,}, - [5]={["y"]=646063.71428571,["x"]=-281738.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SochiAdler = { - PointsBoundary = { - [1]={["y"]=460642.28571428,["x"]=-164861.71428571,}, - [2]={["y"]=462820.85714285,["x"]=-163368.85714286,}, - [3]={["y"]=463649.42857142,["x"]=-163340.28571429,}, - [4]={["y"]=463835.14285714,["x"]=-164040.28571429,}, - [5]={["y"]=462535.14285714,["x"]=-165654.57142857,}, - [6]={["y"]=460678,["x"]=-165247.42857143,}, - [7]={["y"]=460635.14285714,["x"]=-164876,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=460831.42857143,["x"]=-165180,}, - [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, - [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, - [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, - [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, - }, - [2] = { - [1]={["y"]=460831.42857143,["x"]=-165180,}, - [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, - [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, - [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, - [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Soganlug = { - PointsBoundary = { - [1]={["y"]=894530.85714286,["x"]=-316928.28571428,}, - [2]={["y"]=896422.28571428,["x"]=-318622.57142857,}, - [3]={["y"]=896090.85714286,["x"]=-318934,}, - [4]={["y"]=894019.42857143,["x"]=-317119.71428571,}, - [5]={["y"]=894533.71428571,["x"]=-316925.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=894525.71428571,["x"]=-316964,}, - [2]={["y"]=896363.14285714,["x"]=-318634.28571428,}, - [3]={["y"]=896299.14285714,["x"]=-318702.85714286,}, - [4]={["y"]=894464,["x"]=-317031.71428571,}, - [5]={["y"]=894524.57142857,["x"]=-316963.71428571,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SukhumiBabushara = { - PointsBoundary = { - [1]={["y"]=562541.14285714,["x"]=-219852.28571429,}, - [2]={["y"]=562691.14285714,["x"]=-219395.14285714,}, - [3]={["y"]=564326.85714286,["x"]=-219523.71428571,}, - [4]={["y"]=566262.57142857,["x"]=-221166.57142857,}, - [5]={["y"]=566069.71428571,["x"]=-221580.85714286,}, - [6]={["y"]=562534,["x"]=-219873.71428571,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=562684,["x"]=-219779.71428571,}, - [2]={["y"]=562717.71428571,["x"]=-219718,}, - [3]={["y"]=566046.85714286,["x"]=-221376.57142857,}, - [4]={["y"]=566012.28571428,["x"]=-221446.57142857,}, - [5]={["y"]=562684.57142857,["x"]=-219782.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - TbilisiLochini = { - PointsBoundary = { - [1]={["y"]=895172.85714286,["x"]=-314667.42857143,}, - [2]={["y"]=895337.42857143,["x"]=-314143.14285714,}, - [3]={["y"]=895990.28571429,["x"]=-314036,}, - [4]={["y"]=897730.28571429,["x"]=-315284.57142857,}, - [5]={["y"]=897901.71428571,["x"]=-316284.57142857,}, - [6]={["y"]=897684.57142857,["x"]=-316618.85714286,}, - [7]={["y"]=895173.14285714,["x"]=-314667.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=895261.14285715,["x"]=-314652.28571428,}, - [2]={["y"]=897654.57142857,["x"]=-316523.14285714,}, - [3]={["y"]=897711.71428571,["x"]=-316450.28571429,}, - [4]={["y"]=895327.42857143,["x"]=-314568.85714286,}, - [5]={["y"]=895261.71428572,["x"]=-314656,}, - }, - [2] = { - [1]={["y"]=895605.71428572,["x"]=-314724.57142857,}, - [2]={["y"]=897639.71428572,["x"]=-316148,}, - [3]={["y"]=897683.42857143,["x"]=-316087.14285714,}, - [4]={["y"]=895650,["x"]=-314660,}, - [5]={["y"]=895606,["x"]=-314724.85714286,} - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Vaziani = { - PointsBoundary = { - [1]={["y"]=902122,["x"]=-318163.71428572,}, - [2]={["y"]=902678.57142857,["x"]=-317594,}, - [3]={["y"]=903275.71428571,["x"]=-317405.42857143,}, - [4]={["y"]=903418.57142857,["x"]=-317891.14285714,}, - [5]={["y"]=904292.85714286,["x"]=-318748.28571429,}, - [6]={["y"]=904542,["x"]=-319740.85714286,}, - [7]={["y"]=904042,["x"]=-320166.57142857,}, - [8]={["y"]=902121.42857143,["x"]=-318164.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=902239.14285714,["x"]=-318190.85714286,}, - [2]={["y"]=904014.28571428,["x"]=-319994.57142857,}, - [3]={["y"]=904064.85714285,["x"]=-319945.14285715,}, - [4]={["y"]=902294.57142857,["x"]=-318146,}, - [5]={["y"]=902247.71428571,["x"]=-318190.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - }, -} - ---- Creates a new AIRBASEPOLICE_CAUCASUS object. --- @param #AIRBASEPOLICE_CAUCASUS self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @return #AIRBASEPOLICE_CAUCASUS self -function AIRBASEPOLICE_CAUCASUS:New( SetClient ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AIRBASEPOLICE_BASE:New( SetClient, self.Airbases ) ) - - -- -- AnapaVityazevo - -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) - -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) - -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Batumi - -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) - -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) - -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Beslan - -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) - -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) - -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Gelendzhik - -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) - -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) - -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Gudauta - -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) - -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) - -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Kobuleti - -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) - -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) - -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- KrasnodarCenter - -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) - -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) - -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- KrasnodarPashkovsky - -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Krymsk - -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) - -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) - -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Kutaisi - -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) - -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) - -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- MaykopKhanskaya - -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) - -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) - -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- MineralnyeVody - -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) - -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) - -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Mozdok - -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) - -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) - -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Nalchik - -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) - -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) - -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Novorossiysk - -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) - -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) - -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SenakiKolkhi - -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) - -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) - -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SochiAdler - -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) - -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) - -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) - -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Soganlug - -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) - -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) - -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SukhumiBabushara - -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) - -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) - -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- TbilisiLochini - -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) - -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Vaziani - -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) - -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) - -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - - - -- Template - -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) - -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) - -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - - return self - -end - - - - ---- @type AIRBASEPOLICE_NEVADA --- @extends Functional.AirbasePolice#AIRBASEPOLICE_BASE -AIRBASEPOLICE_NEVADA = { - ClassName = "AIRBASEPOLICE_NEVADA", - Airbases = { - Nellis = { - PointsBoundary = { - [1]={["y"]=-17814.714285714,["x"]=-399823.14285714,}, - [2]={["y"]=-16875.857142857,["x"]=-398763.14285714,}, - [3]={["y"]=-16251.571428571,["x"]=-398988.85714286,}, - [4]={["y"]=-16163,["x"]=-398693.14285714,}, - [5]={["y"]=-16328.714285714,["x"]=-398034.57142857,}, - [6]={["y"]=-15943,["x"]=-397571.71428571,}, - [7]={["y"]=-15711.571428571,["x"]=-397551.71428571,}, - [8]={["y"]=-15748.714285714,["x"]=-396806,}, - [9]={["y"]=-16288.714285714,["x"]=-396517.42857143,}, - [10]={["y"]=-16751.571428571,["x"]=-396308.85714286,}, - [11]={["y"]=-17263,["x"]=-396234.57142857,}, - [12]={["y"]=-17577.285714286,["x"]=-396640.28571429,}, - [13]={["y"]=-17614.428571429,["x"]=-397400.28571429,}, - [14]={["y"]=-19405.857142857,["x"]=-399428.85714286,}, - [15]={["y"]=-19234.428571429,["x"]=-399683.14285714,}, - [16]={["y"]=-18708.714285714,["x"]=-399408.85714286,}, - [17]={["y"]=-18397.285714286,["x"]=-399657.42857143,}, - [18]={["y"]=-17814.428571429,["x"]=-399823.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-18687,["x"]=-399380.28571429,}, - [2]={["y"]=-18620.714285714,["x"]=-399436.85714286,}, - [3]={["y"]=-16217.857142857,["x"]=-396596.85714286,}, - [4]={["y"]=-16300.142857143,["x"]=-396530,}, - [5]={["y"]=-18687,["x"]=-399380.85714286,}, - }, - [2] = { - [1]={["y"]=-18451.571428572,["x"]=-399580.57142857,}, - [2]={["y"]=-18392.142857143,["x"]=-399628.57142857,}, - [3]={["y"]=-16011,["x"]=-396806.85714286,}, - [4]={["y"]=-16074.714285714,["x"]=-396751.71428572,}, - [5]={["y"]=-18451.571428572,["x"]=-399580.85714285,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - McCarran = { - PointsBoundary = { - [1]={["y"]=-29455.285714286,["x"]=-416277.42857142,}, - [2]={["y"]=-28860.142857143,["x"]=-416492,}, - [3]={["y"]=-25044.428571429,["x"]=-416344.85714285,}, - [4]={["y"]=-24580.142857143,["x"]=-415959.14285714,}, - [5]={["y"]=-25073,["x"]=-415630.57142857,}, - [6]={["y"]=-25087.285714286,["x"]=-415130.57142857,}, - [7]={["y"]=-25830.142857143,["x"]=-414866.28571428,}, - [8]={["y"]=-26658.714285715,["x"]=-414880.57142857,}, - [9]={["y"]=-26973,["x"]=-415273.42857142,}, - [10]={["y"]=-27380.142857143,["x"]=-415187.71428571,}, - [11]={["y"]=-27715.857142857,["x"]=-414144.85714285,}, - [12]={["y"]=-27551.571428572,["x"]=-413473.42857142,}, - [13]={["y"]=-28630.142857143,["x"]=-413201.99999999,}, - [14]={["y"]=-29494.428571429,["x"]=-415437.71428571,}, - [15]={["y"]=-29455.571428572,["x"]=-416277.71428571,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-29408.428571429,["x"]=-416016.28571428,}, - [2]={["y"]=-29408.142857144,["x"]=-416105.42857142,}, - [3]={["y"]=-24680.714285715,["x"]=-416003.14285713,}, - [4]={["y"]=-24681.857142858,["x"]=-415926.57142856,}, - [5]={["y"]=-29408.42857143,["x"]=-416016.57142856,}, - }, - [2] = { - [1]={["y"]=-28575.571428572,["x"]=-416303.14285713,}, - [2]={["y"]=-28575.571428572,["x"]=-416382.57142856,}, - [3]={["y"]=-25111.000000001,["x"]=-416309.7142857,}, - [4]={["y"]=-25111.000000001,["x"]=-416249.14285713,}, - [5]={["y"]=-28575.571428572,["x"]=-416303.7142857,}, - }, - [3] = { - [1]={["y"]=-29331.000000001,["x"]=-416275.42857141,}, - [2]={["y"]=-29259.000000001,["x"]=-416306.85714284,}, - [3]={["y"]=-28005.571428572,["x"]=-413449.7142857,}, - [4]={["y"]=-28068.714285715,["x"]=-413422.85714284,}, - [5]={["y"]=-29331.000000001,["x"]=-416275.7142857,}, - }, - [4] = { - [1]={["y"]=-29073.285714286,["x"]=-416386.57142856,}, - [2]={["y"]=-28997.285714286,["x"]=-416417.42857141,}, - [3]={["y"]=-27697.571428572,["x"]=-413464.57142856,}, - [4]={["y"]=-27767.857142858,["x"]=-413434.28571427,}, - [5]={["y"]=-29073.000000001,["x"]=-416386.85714284,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Creech = { - PointsBoundary = { - [1]={["y"]=-74522.714285715,["x"]=-360887.99999998,}, - [2]={["y"]=-74197,["x"]=-360556.57142855,}, - [3]={["y"]=-74402.714285715,["x"]=-359639.42857141,}, - [4]={["y"]=-74637,["x"]=-359279.42857141,}, - [5]={["y"]=-75759.857142857,["x"]=-359005.14285712,}, - [6]={["y"]=-75834.142857143,["x"]=-359045.14285712,}, - [7]={["y"]=-75902.714285714,["x"]=-359782.28571427,}, - [8]={["y"]=-76099.857142857,["x"]=-360399.42857141,}, - [9]={["y"]=-77314.142857143,["x"]=-360219.42857141,}, - [10]={["y"]=-77728.428571429,["x"]=-360445.14285713,}, - [11]={["y"]=-77585.571428571,["x"]=-360585.14285713,}, - [12]={["y"]=-76471.285714286,["x"]=-360819.42857141,}, - [13]={["y"]=-76325.571428571,["x"]=-360942.28571427,}, - [14]={["y"]=-74671.857142857,["x"]=-360927.7142857,}, - [15]={["y"]=-74522.714285714,["x"]=-360888.85714284,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-74237.571428571,["x"]=-360591.7142857,}, - [2]={["y"]=-74234.428571429,["x"]=-360493.71428571,}, - [3]={["y"]=-77605.285714286,["x"]=-360399.14285713,}, - [4]={["y"]=-77608.714285715,["x"]=-360498.85714285,}, - [5]={["y"]=-74237.857142857,["x"]=-360591.7142857,}, - }, - [2] = { - [1]={["y"]=-75807.571428572,["x"]=-359073.42857142,}, - [2]={["y"]=-74770.142857144,["x"]=-360581.71428571,}, - [3]={["y"]=-74641.285714287,["x"]=-360585.42857142,}, - [4]={["y"]=-75734.142857144,["x"]=-359023.14285714,}, - [5]={["y"]=-75807.285714287,["x"]=-359073.42857142,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - GroomLake = { - PointsBoundary = { - [1]={["y"]=-88916.714285714,["x"]=-289102.28571425,}, - [2]={["y"]=-87023.571428572,["x"]=-290388.57142857,}, - [3]={["y"]=-85916.428571429,["x"]=-290674.28571428,}, - [4]={["y"]=-87645.000000001,["x"]=-286567.14285714,}, - [5]={["y"]=-88380.714285715,["x"]=-286388.57142857,}, - [6]={["y"]=-89670.714285715,["x"]=-283524.28571428,}, - [7]={["y"]=-89797.857142858,["x"]=-283567.14285714,}, - [8]={["y"]=-88635.000000001,["x"]=-286749.99999999,}, - [9]={["y"]=-89177.857142858,["x"]=-287207.14285714,}, - [10]={["y"]=-89092.142857144,["x"]=-288892.85714285,}, - [11]={["y"]=-88917.000000001,["x"]=-289102.85714285,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-86039.000000001,["x"]=-290606.28571428,}, - [2]={["y"]=-85965.285714287,["x"]=-290573.99999999,}, - [3]={["y"]=-87692.714285715,["x"]=-286634.85714285,}, - [4]={["y"]=-87756.714285715,["x"]=-286663.99999999,}, - [5]={["y"]=-86038.714285715,["x"]=-290606.85714285,}, - }, - [2] = { - [1]={["y"]=-86808.428571429,["x"]=-290375.7142857,}, - [2]={["y"]=-86732.714285715,["x"]=-290344.28571427,}, - [3]={["y"]=-89672.714285714,["x"]=-283546.57142855,}, - [4]={["y"]=-89772.142857143,["x"]=-283587.71428569,}, - [5]={["y"]=-86808.142857143,["x"]=-290375.7142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - }, -} - ---- Creates a new AIRBASEPOLICE_NEVADA object. --- @param #AIRBASEPOLICE_NEVADA self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @return #AIRBASEPOLICE_NEVADA self -function AIRBASEPOLICE_NEVADA:New( SetClient ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AIRBASEPOLICE_BASE:New( SetClient, self.Airbases ) ) - --- -- Nellis --- local NellisBoundary = GROUP:FindByName( "Nellis Boundary" ) --- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local NellisRunway1 = GROUP:FindByName( "Nellis Runway 1" ) --- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local NellisRunway2 = GROUP:FindByName( "Nellis Runway 2" ) --- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- McCarran --- local McCarranBoundary = GROUP:FindByName( "McCarran Boundary" ) --- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local McCarranRunway1 = GROUP:FindByName( "McCarran Runway 1" ) --- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway2 = GROUP:FindByName( "McCarran Runway 2" ) --- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway3 = GROUP:FindByName( "McCarran Runway 3" ) --- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway4 = GROUP:FindByName( "McCarran Runway 4" ) --- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- Creech --- local CreechBoundary = GROUP:FindByName( "Creech Boundary" ) --- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local CreechRunway1 = GROUP:FindByName( "Creech Runway 1" ) --- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local CreechRunway2 = GROUP:FindByName( "Creech Runway 2" ) --- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- Groom Lake --- local GroomLakeBoundary = GROUP:FindByName( "GroomLake Boundary" ) --- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local GroomLakeRunway1 = GROUP:FindByName( "GroomLake Runway 1" ) --- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local GroomLakeRunway2 = GROUP:FindByName( "GroomLake Runway 2" ) --- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -end - - - - - - --- This module contains the DETECTION classes. --- --- === --- --- # 1) @{#DETECTION_BASE} class, extends @{Fsm#FSM} --- --- The @{#DETECTION_BASE} class defines the core functions to administer detected objects. --- The @{#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). --- --- ## 1.1) DETECTION_BASE constructor --- --- Construct a new DETECTION_BASE instance using the @{#DETECTION_BASE.New}() method. --- --- ## 1.2) DETECTION_BASE initialization --- --- By default, detection will return detected objects with all the detection sensors available. --- However, you can ask how the objects were found with specific detection methods. --- If you use one of the below methods, the detection will work with the detection method specified. --- You can specify to apply multiple detection methods. --- --- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: --- --- * @{#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. --- * @{#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. --- * @{#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. --- * @{#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. --- * @{#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. --- * @{#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. --- --- ## 1.3) DETECTION_BASE derived classes group the detected units into a DetectedItems[] list --- --- DETECTION_BASE derived classes build a list called DetectedItems[], which is essentially a first later --- of grouping of detected units. Each DetectedItem within the DetectedItems[] list contains --- a SET_UNIT object that contains the detected units that belong to that group. --- --- Derived classes will apply different methods to group the detected units. --- Examples are per area, per quadrant, per distance, per type. --- See further the derived DETECTION classes on which grouping methods are currently supported. --- --- Various methods exist how to retrieve the grouped items from a DETECTION_BASE derived class: --- --- * The method @{Detection#DETECTION_BASE.GetDetectedItems}() retrieves the DetectedItems[] list. --- * A DetectedItem from the DetectedItems[] list can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedItem}( DetectedItemIndex ). --- Note that this method returns a DetectedItem element from the list, that contains a Set variable and further information --- about the DetectedItem that is set by the DETECTION_BASE derived classes, used to group the DetectedItem. --- * A DetectedSet from the DetectedItems[] list can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSet}( DetectedItemIndex ). --- This method retrieves the Set from a DetectedItem element from the DetectedItem list (DetectedItems[ DetectedItemIndex ].Set ). --- --- ## 1.4) Apply additional Filters to fine-tune the detected objects --- --- By default, DCS World will return any object that is in LOS and within "visual reach", or detectable through one of the electronic detection means. --- That being said, the DCS World detection algorithm can sometimes be unrealistic. --- Especially for a visual detection, DCS World is able to report within 1 second a detailed detection of a group of 20 units (including types of the units) that are 10 kilometers away, using only visual capabilities. --- Additionally, trees and other obstacles are not accounted during the DCS World detection. --- --- Therefore, an additional (optional) filtering has been built into the DETECTION_BASE class, that can be set for visual detected units. --- For electronic detection, this filtering is not applied, only for visually detected targets. --- --- The following additional filtering can be applied for visual filtering: --- --- * A probability factor per kilometer distance. --- * A probability factor based on the alpha angle between the detected object and the unit detecting. --- A detection from a higher altitude allows for better detection than when on the ground. --- * Define a probability factor for "cloudy zones", which are zones where forests or villages are located. In these zones, detection will be much more difficult. --- The mission designer needs to define these cloudy zones within the mission, and needs to register these zones in the DETECTION_ objects additing a probability factor per zone. --- --- I advise however, that, when you first use the DETECTION derived classes, that you don't use these filters. --- Only when you experience unrealistic behaviour in your missions, these filters could be applied. --- --- ### 1.4.1 ) Distance visual detection probability --- --- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly. --- Also, the speed of accurate detection plays a role. --- --- A distance probability factor between 0 and 1 can be given, that will model a linear extrapolated probability over 10 km distance. --- --- For example, if a probability factor of 0.6 (60%) is given, the extrapolated probabilities over 15 kilometers would like like: --- 1 km: 96%, 2 km: 92%, 3 km: 88%, 4 km: 84%, 5 km: 80%, 6 km: 76%, 7 km: 72%, 8 km: 68%, 9 km: 64%, 10 km: 60%, 11 km: 56%, 12 km: 52%, 13 km: 48%, 14 km: 44%, 15 km: 40%. --- --- Note that based on this probability factor, not only the detection but also the **type** of the unit will be applied! --- --- Use the method @{Detection#DETECTION_BASE.SetDistanceProbability}() to set the probability factor upon a 10 km distance. --- --- ### 1.4.2 ) Alpha Angle visual detection probability --- --- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. --- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. --- --- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. --- --- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: --- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% --- --- Use the method @{Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. --- --- ### 1.4.3 ) Cloudy Zones detection probability --- --- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully. --- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission --- zones that reflect cloudy areas where detected units may not be so easily visually detected. --- --- Use the method @{Detection#DETECTION_BASE.SetZoneProbability}() to set for a defined number of zones, the probability factors. --- --- Note however, that the more zones are defined to be "cloudy" within a detection, the more performance it will take --- from the DETECTION_BASE to calculate the presence of the detected unit within each zone. --- Expecially for ZONE_POLYGON, try to limit the amount of nodes of the polygon! --- --- Typically, this kind of filter would be applied for very specific areas were a detection needs to be very realisting for --- AI not to detect so easily targets within a forrest or village rich area. --- --- ## 1.5 ) Accept / Reject detected units --- --- DETECTION_BASE can accept or reject successful detections based on the location of the detected object, --- if it is located in range or located inside or outside of specific zones. --- --- ### 1.5.1 ) Detection acceptance of within range limit --- --- A range can be set that will limit a successful detection for a unit. --- Use the method @{Detection#DETECTION_BASE.SetAcceptRange}() to apply a range in meters till where detected units will be accepted. --- --- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. --- --- -- Build a detect object. --- local Detection = DETECTION_BASE:New( SetGroup ) --- --- -- This will accept detected units if the range is below 5000 meters. --- Detection:SetAcceptRange( 5000 ) --- --- -- Start the Detection. --- Detection:Start() --- --- --- ### 1.5.2 ) Detection acceptance if within zone(s). --- --- Specific ZONE_BASE object(s) can be given as a parameter, which will only accept a detection if the unit is within the specified ZONE_BASE object(s). --- Use the method @{Detection#DETECTION_BASE.SetAcceptZones}() will accept detected units if they are within the specified zones. --- --- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. --- --- -- Search fo the zones where units are to be accepted. --- local ZoneAccept1 = ZONE:New( "AcceptZone1" ) --- local ZoneAccept2 = ZONE:New( "AcceptZone2" ) --- --- -- Build a detect object. --- local Detection = DETECTION_BASE:New( SetGroup ) --- --- -- This will accept detected units by Detection when the unit is within ZoneAccept1 OR ZoneAccept2. --- Detection:SetAcceptZones( { ZoneAccept1, ZoneAccept2 } ) --- --- -- Start the Detection. --- Detection:Start() --- --- ### 1.5.3 ) Detection rejectance if within zone(s). --- --- Specific ZONE_BASE object(s) can be given as a parameter, which will reject detection if the unit is within the specified ZONE_BASE object(s). --- Use the method @{Detection#DETECTION_BASE.SetRejectZones}() will reject detected units if they are within the specified zones. --- An example of how to use the method is shown below. --- --- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. --- --- -- Search fo the zones where units are to be rejected. --- local ZoneReject1 = ZONE:New( "RejectZone1" ) --- local ZoneReject2 = ZONE:New( "RejectZone2" ) --- --- -- Build a detect object. --- local Detection = DETECTION_BASE:New( SetGroup ) --- --- -- This will reject detected units by Detection when the unit is within ZoneReject1 OR ZoneReject2. --- Detection:SetRejectZones( { ZoneReject1, ZoneReject2 } ) --- --- -- Start the Detection. --- Detection:Start() --- --- ## 1.6) DETECTION_BASE is a Finite State Machine --- --- Various Events and State Transitions can be tailored using DETECTION_BASE. --- --- ### 1.6.1) DETECTION_BASE States --- --- * **Detecting**: The detection is running. --- * **Stopped**: The detection is stopped. --- --- ### 1.6.2) DETECTION_BASE Events --- --- * **Start**: Start the detection process. --- * **Detect**: Detect new units. --- * **Detected**: New units have been detected. --- * **Stop**: Stop the detection process. --- --- === --- --- # 2) @{Detection#DETECTION_UNITS} class, extends @{Detection#DETECTION_BASE} --- --- The @{Detection#DETECTION_UNITS} class will detect units within the battle zone. --- It will build a DetectedItems list filled with DetectedItems. Each DetectedItem will contain a field Set, which contains a @{Set#SET_UNIT} containing ONE @{UNIT} object reference. --- Beware that when the amount of units detected is large, the DetectedItems list will be large also. --- --- # 3) @{Detection#DETECTION_TYPES} class, extends @{Detection#DETECTION_BASE} --- --- The @{Detection#DETECTION_TYPES} class will detect units within the battle zone. --- It will build a DetectedItems[] list filled with DetectedItems, grouped by the type of units detected. --- Each DetectedItem will contain a field Set, which contains a @{Set#SET_UNIT} containing ONE @{UNIT} object reference. --- Beware that when the amount of different types detected is large, the DetectedItems[] list will be large also. --- --- # 4) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} --- --- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), --- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. --- The class is group the detected units within zones given a DetectedZoneRange parameter. --- A set with multiple detected zones will be created as there are groups of units detected. --- --- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones --- --- The methods to manage the DetectedItems[].Set(s) are implemented in @{Detection#DECTECTION_BASE} and --- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Detection#DETECTION_AREAS}. --- --- Retrieve the DetectedItems[].Set with the method @{Detection#DETECTION_BASE.GetDetectedSet}(). A @{Set#SET_UNIT} object will be returned. --- --- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Detection#DETECTION_BASE.GetDetectionZones}(). --- To understand the amount of zones created, use the method @{Detection#DETECTION_BASE.GetDetectionZoneCount}(). --- If you want to obtain a specific zone from the DetectedZones, use the method @{Detection#DETECTION_BASE.GetDetectionZone}() with a given index. --- --- ## 4.4) Flare or Smoke detected units --- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. --- --- ## 4.5) Flare or Smoke or Bound detected zones --- --- Use the methods: --- --- * @{Detection#DETECTION_AREAS.FlareDetectedZones}() to flare in a color --- * @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to smoke in a color --- * @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to bound with a tire with a white flag --- --- the detected zones when a new detection has taken place. --- --- === --- --- ### Contributions: --- --- * Mechanist : Early concept of DETECTION_AREAS. --- --- ### Authors: --- --- * FlightControl : Analysis, Design, Programming, Testing --- --- @module Detection - - -do -- DETECTION_BASE - - --- DETECTION_BASE class - -- @type DETECTION_BASE - -- @field Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. - -- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. - -- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. - -- @field #number DetectionRun - -- @extends Core.Fsm#FSM - DETECTION_BASE = { - ClassName = "DETECTION_BASE", - DetectionSetGroup = nil, - DetectionRange = nil, - DetectedObjects = {}, - DetectionRun = 0, - DetectedObjectsIdentified = {}, - DetectedItems = {}, - } - - --- @type DETECTION_BASE.DetectedObjects - -- @list <#DETECTION_BASE.DetectedObject> - - --- @type DETECTION_BASE.DetectedObject - -- @field #string Name - -- @field #boolean Visible - -- @field #string Type - -- @field #number Distance - -- @field #boolean Identified - - --- @type DETECTION_BASE.DetectedItems - -- @list <#DETECTION_BASE.DetectedItem> - - --- @type DETECTION_BASE.DetectedItem - -- @field Core.Set#SET_UNIT Set - -- @field Core.Set#SET_UNIT Set -- The Set of Units in the detected area. - -- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. - -- @field #boolean Changed Documents if the detected area has changes. - -- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). - -- @field #number ItemID -- The identifier of the detected area. - -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. - -- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. - - - --- DETECTION constructor. - -- @param #DETECTION_BASE self - -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @return #DETECTION_BASE self - function DETECTION_BASE:New( DetectionSetGroup ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New() ) -- #DETECTION_BASE - - self.DetectedItemCount = 0 - self.DetectedItemMax = 0 - self.DetectedItems = {} - - self.DetectionSetGroup = DetectionSetGroup - - self.DetectionInterval = 30 - - self:InitDetectVisual( true ) - self:InitDetectOptical( false ) - self:InitDetectRadar( false ) - self:InitDetectRWR( false ) - self:InitDetectIRST( false ) - self:InitDetectDLINK( false ) - - -- Create FSM transitions. - - self:SetStartState( "Stopped" ) - self.CountryID = DetectionSetGroup:GetFirst():GetCountry() - - self:AddTransition( "Stopped", "Start", "Detecting") - - --- OnLeave Transition Handler for State Stopped. - -- @function [parent=#DETECTION_BASE] OnLeaveStopped - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Stopped. - -- @function [parent=#DETECTION_BASE] OnEnterStopped - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Start. - -- @function [parent=#DETECTION_BASE] OnBeforeStart - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Start. - -- @function [parent=#DETECTION_BASE] OnAfterStart - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Start. - -- @function [parent=#DETECTION_BASE] Start - -- @param #DETECTION_BASE self - - --- Asynchronous Event Trigger for Event Start. - -- @function [parent=#DETECTION_BASE] __Start - -- @param #DETECTION_BASE self - -- @param #number Delay The delay in seconds. - - --- OnLeave Transition Handler for State Detecting. - -- @function [parent=#DETECTION_BASE] OnLeaveDetecting - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Detecting. - -- @function [parent=#DETECTION_BASE] OnEnterDetecting - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - self:AddTransition( "Detecting", "Detect", "Detecting" ) - self:AddTransition( "Detecting", "DetectionGroup", "Detecting" ) - - --- OnBefore Transition Handler for Event Detect. - -- @function [parent=#DETECTION_BASE] OnBeforeDetect - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Detect. - -- @function [parent=#DETECTION_BASE] OnAfterDetect - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Detect. - -- @function [parent=#DETECTION_BASE] Detect - -- @param #DETECTION_BASE self - - --- Asynchronous Event Trigger for Event Detect. - -- @function [parent=#DETECTION_BASE] __Detect - -- @param #DETECTION_BASE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Detecting", "Detected", "Detecting" ) - - --- OnBefore Transition Handler for Event Detected. - -- @function [parent=#DETECTION_BASE] OnBeforeDetected - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Detected. - -- @function [parent=#DETECTION_BASE] OnAfterDetected - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Detected. - -- @function [parent=#DETECTION_BASE] Detected - -- @param #DETECTION_BASE self - - --- Asynchronous Event Trigger for Event Detected. - -- @function [parent=#DETECTION_BASE] __Detected - -- @param #DETECTION_BASE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "*", "Stop", "Stopped" ) - - --- OnBefore Transition Handler for Event Stop. - -- @function [parent=#DETECTION_BASE] OnBeforeStop - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Stop. - -- @function [parent=#DETECTION_BASE] OnAfterStop - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Stop. - -- @function [parent=#DETECTION_BASE] Stop - -- @param #DETECTION_BASE self - - --- Asynchronous Event Trigger for Event Stop. - -- @function [parent=#DETECTION_BASE] __Stop - -- @param #DETECTION_BASE self - -- @param #number Delay The delay in seconds. - - --- OnLeave Transition Handler for State Stopped. - -- @function [parent=#DETECTION_BASE] OnLeaveStopped - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Stopped. - -- @function [parent=#DETECTION_BASE] OnEnterStopped - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - return self - end - - do -- State Transition Handling - - --- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - function DETECTION_BASE:onafterStart(From,Event,To) - self:__Detect(0.1) - end - - --- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - function DETECTION_BASE:onafterDetect(From,Event,To) - self:E( {From,Event,To}) - - local DetectDelay = 0.1 - self.DetectionCount = 0 - self.DetectionRun = 0 - self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table - - self.DetectionSetGroup:Flush() - - for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - self:E( {DetectionGroupData}) - self:__DetectionGroup( DetectDelay, DetectionGroupData ) -- Process each detection asynchronously. - self.DetectionCount = self.DetectionCount + 1 - DetectDelay = DetectDelay + 0.1 - end - end - - --- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Wrapper.Group#GROUP DetectionGroup The Group detecting. - function DETECTION_BASE:onafterDetectionGroup( From, Event, To, DetectionGroup ) - self:E( {From,Event,To}) - - self.DetectionRun = self.DetectionRun + 1 - - local HasDetectedObjects = false - - if DetectionGroup:IsAlive() then - - self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) - - local DetectionGroupName = DetectionGroup:GetName() - - local DetectedUnits = {} - - local DetectedTargets = DetectionGroup:GetDetectedTargets( - self.DetectVisual, - self.DetectOptical, - self.DetectRadar, - self.DetectIRST, - self.DetectRWR, - self.DetectDLINK - ) - - self:T( DetectedTargets ) - - for DetectionObjectID, Detection in pairs( DetectedTargets ) do - local DetectedObject = Detection.object -- Dcs.DCSWrapper.Object#Object - self:T2( DetectedObject ) - - if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then - - local DetectionAccepted = true - - local DetectedObjectName = DetectedObject:getName() - - local DetectedObjectVec3 = DetectedObject:getPoint() - local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z } - local DetectionGroupVec3 = DetectionGroup:GetVec3() - local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z } - - local Distance = ( ( DetectedObjectVec3.x - DetectionGroupVec3.x )^2 + - ( DetectedObjectVec3.y - DetectionGroupVec3.y )^2 + - ( DetectedObjectVec3.z - DetectionGroupVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T( { "Detected Target", DetectionGroupName, DetectedObjectName, Distance } ) - - -- Calculate Acceptance - - if self.AcceptRange and Distance > self.AcceptRange then - DetectionAccepted = false - end - - if self.AcceptZones then - for AcceptZoneID, AcceptZone in pairs( self.AcceptZones ) do - local AcceptZone = AcceptZone -- Core.Zone#ZONE_BASE - if AcceptZone:IsPointVec2InZone( DetectedObjectVec2 ) == false then - DetectionAccepted = false - end - end - end - - if self.RejectZones then - for RejectZoneID, RejectZone in pairs( self.RejectZones ) do - local RejectZone = RejectZone -- Core.Zone#ZONE_BASE - if RejectZone:IsPointVec2InZone( DetectedObjectVec2 ) == true then - DetectionAccepted = false - end - end - end - - -- Calculate additional probabilities - - if not self.DetectedObjects[DetectedObjectName] and Detection.visible and self.DistanceProbability then - local DistanceFactor = Distance / 4 - local DistanceProbabilityReversed = ( 1 - self.DistanceProbability ) * DistanceFactor - local DistanceProbability = 1 - DistanceProbabilityReversed - DistanceProbability = DistanceProbability * 30 / 300 - local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, DistanceProbability } ) - if Probability > DistanceProbability then - DetectionAccepted = false - end - end - - if not self.DetectedObjects[DetectedObjectName] and Detection.visible and self.AlphaAngleProbability then - local NormalVec2 = { x = DetectedObjectVec2.x - DetectionGroupVec2.x, y = DetectedObjectVec2.y - DetectionGroupVec2.y } - local AlphaAngle = math.atan2( NormalVec2.y, NormalVec2.x ) - local Sinus = math.sin( AlphaAngle ) - local AlphaAngleProbabilityReversed = ( 1 - self.AlphaAngleProbability ) * ( 1 - Sinus ) - local AlphaAngleProbability = 1 - AlphaAngleProbabilityReversed - - AlphaAngleProbability = AlphaAngleProbability * 30 / 300 - - local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, AlphaAngleProbability } ) - if Probability > AlphaAngleProbability then - DetectionAccepted = false - end - - end - - if not self.DetectedObjects[DetectedObjectName] and Detection.visible and self.ZoneProbability then - - for ZoneDataID, ZoneData in pairs( self.ZoneProbability ) do - self:E({ZoneData}) - local ZoneObject = ZoneData[1] -- Core.Zone#ZONE_BASE - local ZoneProbability = ZoneData[2] -- #number - ZoneProbability = ZoneProbability * 30 / 300 - - if ZoneObject:IsPointVec2InZone( DetectedObjectVec2 ) == true then - local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, ZoneProbability } ) - if Probability > ZoneProbability then - DetectionAccepted = false - break - end - end - end - end - - if DetectionAccepted then - - HasDetectedObjects = true - - if not self.DetectedObjects[DetectedObjectName] then - self.DetectedObjects[DetectedObjectName] = {} - end - self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName - self.DetectedObjects[DetectedObjectName].Visible = Detection.visible - self.DetectedObjects[DetectedObjectName].Type = Detection.type - self.DetectedObjects[DetectedObjectName].Distance = Distance - - local DetectedUnit = UNIT:FindByName( DetectedObjectName ) - - DetectedUnits[DetectedObjectName] = DetectedUnit - else - -- if beyond the DetectionRange then nullify... - if self.DetectedObjects[DetectedObjectName] then - self.DetectedObjects[DetectedObjectName] = nil - end - end - end - - self:T2( self.DetectedObjects ) - end - - if HasDetectedObjects then - self:__Detected( 0.1, DetectedUnits ) - end - - end - - if self.DetectionCount > 0 and self.DetectionRun == self.DetectionCount then - self:__Detect( self.DetectionInterval ) - - self:T( "--> Create Detection Sets" ) - self:CreateDetectionSets() - end - - end - - - end - - do -- Initialization methods - - --- Detect Visual. - -- @param #DETECTION_BASE self - -- @param #boolean DetectVisual - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectVisual( DetectVisual ) - - self.DetectVisual = DetectVisual - end - - --- Detect Optical. - -- @param #DETECTION_BASE self - -- @param #boolean DetectOptical - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectOptical( DetectOptical ) - self:F2() - - self.DetectOptical = DetectOptical - end - - --- Detect Radar. - -- @param #DETECTION_BASE self - -- @param #boolean DetectRadar - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectRadar( DetectRadar ) - self:F2() - - self.DetectRadar = DetectRadar - end - - --- Detect IRST. - -- @param #DETECTION_BASE self - -- @param #boolean DetectIRST - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectIRST( DetectIRST ) - self:F2() - - self.DetectIRST = DetectIRST - end - - --- Detect RWR. - -- @param #DETECTION_BASE self - -- @param #boolean DetectRWR - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectRWR( DetectRWR ) - self:F2() - - self.DetectRWR = DetectRWR - end - - --- Detect DLINK. - -- @param #DETECTION_BASE self - -- @param #boolean DetectDLINK - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectDLINK( DetectDLINK ) - self:F2() - - self.DetectDLINK = DetectDLINK - end - - end - - do - - --- Set the detection interval time in seconds. - -- @param #DETECTION_BASE self - -- @param #number DetectionInterval Interval in seconds. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetDetectionInterval( DetectionInterval ) - self:F2() - - self.DetectionInterval = DetectionInterval - - return self - end - - end - - do -- Accept / Reject detected units - - --- Accept detections if within a range in meters. - -- @param #DETECTION_BASE self - -- @param #number AcceptRange Accept a detection if the unit is within the AcceptRange in meters. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetAcceptRange( AcceptRange ) - self:F2() - - self.AcceptRange = AcceptRange - - return self - end - - --- Accept detections if within the specified zone(s). - -- @param #DETECTION_BASE self - -- @param AcceptZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetAcceptZones( AcceptZones ) - self:F2() - - if type( AcceptZones ) == "table" then - self.AcceptZones = AcceptZones - else - self.AcceptZones = { AcceptZones } - end - - return self - end - - --- Reject detections if within the specified zone(s). - -- @param #DETECTION_BASE self - -- @param RejectZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetRejectZones( RejectZones ) - self:F2() - - if type( RejectZones ) == "table" then - self.RejectZones = RejectZones - else - self.RejectZones = { RejectZones } - end - - return self - end - - end - - do -- Probability methods - - --- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly. - -- Also, the speed of accurate detection plays a role. - -- A distance probability factor between 0 and 1 can be given, that will model a linear extrapolated probability over 10 km distance. - -- For example, if a probability factor of 0.6 (60%) is given, the extrapolated probabilities over 15 kilometers would like like: - -- 1 km: 96%, 2 km: 92%, 3 km: 88%, 4 km: 84%, 5 km: 80%, 6 km: 76%, 7 km: 72%, 8 km: 68%, 9 km: 64%, 10 km: 60%, 11 km: 56%, 12 km: 52%, 13 km: 48%, 14 km: 44%, 15 km: 40%. - -- @param #DETECTION_BASE self - -- @param DistanceProbability The probability factor. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetDistanceProbability( DistanceProbability ) - self:F2() - - self.DistanceProbability = DistanceProbability - - return self - end - - - --- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. - -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. - -- - -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. - -- - -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: - -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% - -- @param #DETECTION_BASE self - -- @param AlphaAngleProbability The probability factor. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetAlphaAngleProbability( AlphaAngleProbability ) - self:F2() - - self.AlphaAngleProbability = AlphaAngleProbability - - return self - end - - --- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully. - -- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission - -- zones that reflect cloudy areas where detected units may not be so easily visually detected. - -- @param #DETECTION_BASE self - -- @param ZoneArray Aray of a The ZONE_BASE object and a ZoneProbability pair.. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetZoneProbability( ZoneArray ) - self:F2() - - self.ZoneProbability = ZoneArray - - return self - end - - - end - - do -- Change processing - - --- Accepts changes from the detected item. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #DETECTION_BASE self - function DETECTION_BASE:AcceptChanges( DetectedItem ) - - DetectedItem.Changed = false - DetectedItem.Changes = {} - - return self - end - - --- Add a change to the detected zone. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @param #string ChangeCode - -- @return #DETECTION_BASE self - function DETECTION_BASE:AddChangeItem( DetectedItem, ChangeCode, ItemUnitType ) - - DetectedItem.Changed = true - local ItemID = DetectedItem.ItemID - - DetectedItem.Changes = DetectedItem.Changes or {} - DetectedItem.Changes[ChangeCode] = DetectedItem.Changes[ChangeCode] or {} - DetectedItem.Changes[ChangeCode].ItemID = ItemID - DetectedItem.Changes[ChangeCode].ItemUnitType = ItemUnitType - - self:T( { "Change on Detection Item:", DetectedItem.ItemID, ChangeCode, ItemUnitType } ) - - return self - end - - - --- Add a change to the detected zone. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @param #string ChangeCode - -- @param #string ChangeUnitType - -- @return #DETECTION_BASE self - function DETECTION_BASE:AddChangeUnit( DetectedItem, ChangeCode, ChangeUnitType ) - - DetectedItem.Changed = true - local ItemID = DetectedItem.ItemID - - DetectedItem.Changes = DetectedItem.Changes or {} - DetectedItem.Changes[ChangeCode] = DetectedItem.Changes[ChangeCode] or {} - DetectedItem.Changes[ChangeCode][ChangeUnitType] = DetectedItem.Changes[ChangeCode][ChangeUnitType] or 0 - DetectedItem.Changes[ChangeCode][ChangeUnitType] = DetectedItem.Changes[ChangeCode][ChangeUnitType] + 1 - DetectedItem.Changes[ChangeCode].ItemID = ItemID - - self:T( { "Change on Detection Item:", DetectedItem.ItemID, ChangeCode, ChangeUnitType } ) - - return self - end - - - end - - do -- Threat - - --- Returns if there are friendlies nearby the FAC units ... - -- @param #DETECTION_BASE self - -- @return #boolean trhe if there are friendlies nearby - function DETECTION_BASE:IsFriendliesNearBy( DetectedItem ) - - self:T3( DetectedItem.FriendliesNearBy ) - return DetectedItem.FriendliesNearBy or false - end - - --- Background worker function to determine if there are friendlies nearby ... - -- @param #DETECTION_BASE self - function DETECTION_BASE:ReportFriendliesNearBy( ReportGroupData ) - self:F2() - - local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = ReportGroupData.DetectedItem.Set - local DetectedUnit = DetectedSet:GetFirst() - - DetectedItem.FriendliesNearBy = false - - local SphereSearch = { - id = world.VolumeType.SPHERE, - params = { - point = DetectedUnit:GetVec3(), - radius = 6000, - } - - } - - --- @param Dcs.DCSWrapper.Unit#Unit FoundDCSUnit - -- @param Wrapper.Group#GROUP ReportGroup - -- @param Set#SET_GROUP ReportSetGroup - local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - - local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = ReportGroupData.DetectedItem.Set - local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - local ReportSetGroup = ReportGroupData.ReportSetGroup - - local EnemyCoalition = DetectedUnit:GetCoalition() - - local FoundUnitCoalition = FoundDCSUnit:getCoalition() - local FoundUnitName = FoundDCSUnit:getName() - local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() - local EnemyUnitName = DetectedUnit:GetName() - local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil - - self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - - if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then - DetectedItem.FriendliesNearBy = true - return false - end - - return true - end - - world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) - - end - - end - - --- Determines if a detected object has already been identified during detection processing. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedObject DetectedObject - -- @return #boolean true if already identified. - function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) - self:F3( DetectedObject.Name ) - - local DetectedObjectName = DetectedObject.Name - local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true - self:T3( DetectedObjectIdentified ) - return DetectedObjectIdentified - end - - --- Identifies a detected object during detection processing. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedObject DetectedObject - function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) - self:F( { "Identified:", DetectedObject.Name } ) - - local DetectedObjectName = DetectedObject.Name - self.DetectedObjectsIdentified[DetectedObjectName] = true - end - - --- UnIdentify a detected object during detection processing. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedObject DetectedObject - function DETECTION_BASE:UnIdentifyDetectedObject( DetectedObject ) - - local DetectedObjectName = DetectedObject.Name - self.DetectedObjectsIdentified[DetectedObjectName] = false - end - - --- UnIdentify all detected objects during detection processing. - -- @param #DETECTION_BASE self - function DETECTION_BASE:UnIdentifyAllDetectedObjects() - - self.DetectedObjectsIdentified = {} -- Table will be garbage collected. - end - - --- Gets a detected object with a given name. - -- @param #DETECTION_BASE self - -- @param #string ObjectName - -- @return #DETECTION_BASE.DetectedObject - function DETECTION_BASE:GetDetectedObject( ObjectName ) - self:F( ObjectName ) - - if ObjectName then - local DetectedObject = self.DetectedObjects[ObjectName] - - -- Only return detected objects that are alive! - local DetectedUnit = UNIT:FindByName( ObjectName ) - if DetectedUnit and DetectedUnit:IsAlive() then - if self:IsDetectedObjectIdentified( DetectedObject ) == false then - return DetectedObject - end - end - end - - return nil - end - - - --- Adds a new DetectedItem to the DetectedItems list. - -- The DetectedItem is a table and contains a SET_UNIT in the field Set. - -- @param #DETECTION_BASE self - -- @param #string DetectedItemIndex The index of the DetectedItem. - -- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added. - -- @return #DETECTION_BASE.DetectedItem - function DETECTION_BASE:AddDetectedItem( DetectedItemIndex, Set ) - - local DetectedItem = {} - self.DetectedItemCount = self.DetectedItemCount + 1 - self.DetectedItemMax = self.DetectedItemMax + 1 - - if DetectedItemIndex then - self.DetectedItems[DetectedItemIndex] = DetectedItem - else - self.DetectedItems[self.DetectedItemCount] = DetectedItem - end - - DetectedItem.Set = Set or SET_UNIT:New() - DetectedItem.ItemID = self.DetectedItemMax - DetectedItem.Removed = false - - return DetectedItem - end - - --- Adds a new DetectedItem to the DetectedItems list. - -- The DetectedItem is a table and contains a SET_UNIT in the field Set. - -- @param #DETECTION_BASE self - -- @param #string DetectedItemIndex The index of the DetectedItem. - -- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added. - -- @param Core.Zone#ZONE_UNIT Zone (optional) The Zone to be added where the Units are located. - -- @return #DETECTION_BASE.DetectedItem - function DETECTION_BASE:AddDetectedItemZone( DetectedItemIndex, Set, Zone ) - - local DetectedItem = self:AddDetectedItem( DetectedItemIndex, Set ) - - DetectedItem.Zone = Zone - - return DetectedItem - end - - --- Removes an existing DetectedItem from the DetectedItems list. - -- The DetectedItem is a table and contains a SET_UNIT in the field Set. - -- @param #DETECTION_BASE self - -- @param #number DetectedItemIndex The index or position in the DetectedItems list where the item needs to be removed. - function DETECTION_BASE:RemoveDetectedItem( DetectedItemIndex ) - - self.DetectedItemCount = self.DetectedItemCount - 1 - self.DetectedItems[DetectedItemIndex] = nil - end - - - --- Get the detected @{Set#SET_BASE}s. - -- @param #DETECTION_BASE self - -- @return #DETECTION_BASE.DetectedItems - function DETECTION_BASE:GetDetectedItems() - - return self.DetectedItems - end - - --- Get the amount of SETs with detected objects. - -- @param #DETECTION_BASE self - -- @return #number Count - function DETECTION_BASE:GetDetectedItemsCount() - - local DetectedCount = self.DetectedItemCount - return DetectedCount - end - - --- Get a detected item using a given numeric index. - -- @param #DETECTION_BASE self - -- @param #number Index - -- @return DETECTION_BASE.DetectedItem - function DETECTION_BASE:GetDetectedItem( Index ) - - local DetectedItem = self.DetectedItems[Index] - if DetectedItem then - return DetectedItem - end - - return nil - end - - --- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. - -- @param #DETECTION_BASE self - -- @param #number Index - -- @return Core.Set#SET_UNIT DetectedSet - function DETECTION_BASE:GetDetectedSet( Index ) - - local DetectedItem = self:GetDetectedItem( Index ) - local DetectedSetUnit = DetectedItem.Set - if DetectedSetUnit then - return DetectedSetUnit - end - - return nil - end - - do -- Zones - - --- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. - -- @param #DETECTION_BASE self - -- @param #number Index - -- @return Core.Zone#ZONE_UNIT DetectedZone - function DETECTION_BASE:GetDetectedZone( Index ) - - local DetectedZone = self.DetectedItems[Index].Zone - if DetectedZone then - return DetectedZone - end - - return nil - end - - end - - - --- Report summary of a detected item using a given numeric index. - -- @param #DETECTION_BASE self - -- @param Index - -- @return #string - function DETECTION_BASE:DetectedItemReportSummary( Index ) - self:F( Index ) - return nil - end - - --- Report detailed of a detectedion result. - -- @param #DETECTION_BASE self - -- @return #string - function DETECTION_BASE:DetectedReportDetailed() - self:F() - return nil - end - - --- Get the detection Groups. - -- @param #DETECTION_BASE self - -- @return Wrapper.Group#GROUP - function DETECTION_BASE:GetDetectionSetGroup() - - local DetectionSetGroup = self.DetectionSetGroup - return DetectionSetGroup - end - - --- Make a DetectionSet table. This function will be overridden in the derived clsses. - -- @param #DETECTION_BASE self - -- @return #DETECTION_BASE self - function DETECTION_BASE:CreateDetectionSets() - self:F2() - - self:E( "Error, in DETECTION_BASE class..." ) - - end - - - --- Schedule the DETECTION construction. - -- @param #DETECTION_BASE self - -- @param #number DelayTime The delay in seconds to wait the reporting. - -- @param #number RepeatInterval The repeat interval in seconds for the reporting to happen repeatedly. - -- @return #DETECTION_BASE self - function DETECTION_BASE:Schedule( DelayTime, RepeatInterval ) - self:F2() - - self.ScheduleDelayTime = DelayTime - self.ScheduleRepeatInterval = RepeatInterval - - self.DetectionScheduler = SCHEDULER:New( self, self._DetectionScheduler, { self, "Detection" }, DelayTime, RepeatInterval ) - return self - end - -end - -do -- DETECTION_UNITS - - --- DETECTION_UNITS class - -- @type DETECTION_UNITS - -- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are detected. - -- @extends #DETECTION_BASE - DETECTION_UNITS = { - ClassName = "DETECTION_UNITS", - DetectionRange = nil, - } - - --- DETECTION_UNITS constructor. - -- @param Functional.Detection#DETECTION_UNITS self - -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @return Functional.Detection#DETECTION_UNITS self - function DETECTION_UNITS:New( DetectionSetGroup ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) -- #DETECTION_UNITS - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - self._BoundDetectedZones = false - - return self - end - - --- Make text documenting the changes of the detected zone. - -- @param #DETECTION_UNITS self - -- @param #DETECTION_UNITS.DetectedItem DetectedItem - -- @return #string The Changes text - function DETECTION_UNITS:GetChangeText( DetectedItem ) - self:F( DetectedItem ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - - end - - - --- Create the DetectedItems list from the DetectedObjects table. - -- For each DetectedItem, a one field array is created containing the Unit detected. - -- @param #DETECTION_UNITS self - -- @return #DETECTION_UNITS self - function DETECTION_UNITS:CreateDetectionSets() - self:F2( #self.DetectedObjects ) - - -- Loop the current detected items, and check if each object still exists and is detected. - - for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do - - local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedTypeName = DetectedItem.Type - - for DetectedUnitName, DetectedUnitData in pairs( DetectedItemSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - - local DetectedObject = nil - self:E( DetectedUnit ) - if DetectedUnit:IsAlive() then - --self:E(DetectedUnit:GetName()) - DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) - end - if DetectedObject then - - -- Yes, the DetectedUnit is still detected or exists. Flag as identified. - self:IdentifyDetectedObject( DetectedObject ) - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedItem, "RU", DetectedUnitName ) - DetectedItemSet:Remove( DetectedUnitName ) - end - end - end - - - -- Now we need to loop through the unidentified detected units and add these... These are all new items. - for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - if DetectedObject then - self:T( { "Detected Unit #", DetectedUnitName } ) - - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - - if DetectedUnit then - local DetectedTypeName = DetectedUnit:GetTypeName() - local DetectedItem = self:GetDetectedItem( DetectedUnitName ) - if not DetectedItem then - self:T( "Added new DetectedItem" ) - DetectedItem = self:AddDetectedItem( DetectedUnitName ) - DetectedItem.Type = DetectedUnit:GetTypeName() - DetectedItem.Name = DetectedObjectData.Name - DetectedItem.Visible = DetectedObjectData.Visible - DetectedItem.Distance = DetectedObjectData.Distance - end - - DetectedItem.Set:AddUnit( DetectedUnit ) - self:AddChangeUnit( DetectedItem, "AU", DetectedTypeName ) - end - end - end - - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set - - self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - --self:NearestFAC( DetectedItem ) - end - - end - - --- Report summary of a DetectedItem using a given numeric index. - -- @param #DETECTION_UNITS self - -- @param Index - -- @return #string - function DETECTION_UNITS:DetectedItemReportSummary( Index ) - self:F( Index ) - - local DetectedItem = self:GetDetectedItem( Index ) - local DetectedSet = self:GetDetectedSet( Index ) - - self:T( DetectedSet ) - if DetectedSet then - local ReportSummary = "" - local UnitDistanceText = "" - local UnitCategoryText = "" - - local DetectedItemUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - - if DetectedItemUnit then - self:T(DetectedItemUnit) - - local UnitCategoryName = DetectedItemUnit:GetCategoryName() - local UnitCategoryType = DetectedItemUnit:GetTypeName() - - if DetectedItem.Type then - UnitCategoryText = UnitCategoryName .. " (" .. UnitCategoryType .. ") at " - else - UnitCategoryText = "Unknown target at " - end - - if DetectedItem.Visible == false then - UnitDistanceText = string.format( "%.2f", DetectedItem.Distance ) .. " estimated km" - else - UnitDistanceText = string.format( "%.2f", DetectedItem.Distance ) .. " km, visual contact" - end - - ReportSummary = string.format( - "%s%s", - UnitCategoryText, - UnitDistanceText - ) - end - - self:T( ReportSummary ) - - return ReportSummary - end - end - - --- Report detailed of a detection result. - -- @param #DETECTION_UNITS self - -- @return #string - function DETECTION_UNITS:DetectedReportDetailed() - self:F() - - local Report = REPORT:New( "Detected units:" ) - for DetectedItemID, DetectedItem in ipairs( self.DetectedItems ) do - local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem - local ReportSummary = self:DetectedItemReportSummary( DetectedItemID ) - Report:Add( ReportSummary ) - end - - local ReportText = Report:Text() - - return ReportText - end - -end - -do -- DETECTION_TYPES - - --- DETECTION_TYPES class - -- @type DETECTION_TYPES - -- @extends #DETECTION_BASE - DETECTION_TYPES = { - ClassName = "DETECTION_TYPES", - DetectionRange = nil, - } - - --- DETECTION_TYPES constructor. - -- @param Functional.Detection#DETECTION_TYPES self - -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Recce role. - -- @return Functional.Detection#DETECTION_TYPES self - function DETECTION_TYPES:New( DetectionSetGroup ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) -- #DETECTION_TYPES - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - self._BoundDetectedZones = false - - return self - end - - --- Make text documenting the changes of the detected zone. - -- @param #DETECTION_TYPES self - -- @param #DETECTION_TYPES.DetectedItem DetectedItem - -- @return #string The Changes text - function DETECTION_TYPES:GetChangeText( DetectedItem ) - self:F( DetectedItem ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - - end - - - --- Create the DetectedItems list from the DetectedObjects table. - -- For each DetectedItem, a one field array is created containing the Unit detected. - -- @param #DETECTION_TYPES self - -- @return #DETECTION_TYPES self - function DETECTION_TYPES:CreateDetectionSets() - self:F2( #self.DetectedObjects ) - - -- Loop the current detected items, and check if each object still exists and is detected. - - for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do - - local DetectedItemSet = DetectedItem:GetSet() -- Core.Set#SET_UNIT - local DetectedTypeName = DetectedItem.Type - - for DetectedUnitName, DetectedUnitData in pairs( DetectedItemSet ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - - local DetectedObject = nil - if DetectedUnit:IsAlive() then - --self:E(DetectedUnit:GetName()) - DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) - end - if DetectedObject then - - -- Yes, the DetectedUnit is still detected or exists. Flag as identified. - self:IdentifyDetectedObject( DetectedObject ) - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedItem, "RU", DetectedUnitName ) - DetectedItemSet:Remove( DetectedUnitName ) - end - end - end - - - -- Now we need to loop through the unidentified detected units and add these... These are all new items. - for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - if DetectedObject then - self:T( { "Detected Unit #", DetectedUnitName } ) - - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - - if DetectedUnit then - local DetectedTypeName = DetectedUnit:GetTypeName() - local DetectedItem = self:GetDetectedItem( DetectedTypeName ) - if not DetectedItem then - DetectedItem = self:AddDetectedItem( DetectedTypeName ) - DetectedItem.Type = DetectedUnit:GetTypeName() - end - - DetectedItem.Set:AddUnit( DetectedUnit ) - self:AddChangeUnit( DetectedItem, "AU", DetectedTypeName ) - end - end - end - - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set - - self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - --self:NearestFAC( DetectedItem ) - end - - end - - --- Report summary of a DetectedItem using a given numeric index. - -- @param #DETECTION_TYPES self - -- @param Index - -- @return #string - function DETECTION_TYPES:DetectedItemReportSummary( DetectedTypeName ) - self:F( DetectedTypeName ) - - local DetectedItem = self:GetDetectedItem( DetectedTypeName ) - local DetectedSet = self:GetDetectedSet( DetectedTypeName ) - - self:T( DetectedItem ) - if DetectedItem then - - local ThreatLevelA2G = DetectedSet:CalculateThreatLevelA2G() - - local ReportSummary = string.format( - "Type #%s - Threat Level [%s] (%2d)", - DetectedItem.Type, - string.rep( "â– ", ThreatLevelA2G ), - ThreatLevelA2G - ) - self:T( ReportSummary ) - - return ReportSummary - end - end - - --- Report detailed of a detection result. - -- @param #DETECTION_TYPES self - -- @return #string - function DETECTION_TYPES:DetectedReportDetailed() - self:F() - - local Report = REPORT:New( "Detected types:" ) - for DetectedItemTypeName, DetectedItem in pairs( self.DetectedItems ) do - local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem - local ReportSummary = self:DetectedItemReportSummary( DetectedItemTypeName ) - Report:Add( ReportSummary ) - end - - local ReportText = Report:Text() - - return ReportText - end - -end - - -do -- DETECTION_AREAS - - --- DETECTION_AREAS class - -- @type DETECTION_AREAS - -- @field Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. - -- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. - -- @extends #DETECTION_BASE - DETECTION_AREAS = { - ClassName = "DETECTION_AREAS", - DetectionZoneRange = nil, - } - - - --- DETECTION_AREAS constructor. - -- @param #DETECTION_AREAS self - -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @param Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. - -- @return #DETECTION_AREAS - function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) - - self.DetectionZoneRange = DetectionZoneRange - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - self._BoundDetectedZones = false - - return self - end - - --- Report summary of a detected item using a given numeric index. - -- @param #DETECTION_AREAS self - -- @param Index - -- @return #string - function DETECTION_AREAS:DetectedItemReportSummary( Index ) - self:F( Index ) - - local DetectedItem = self:GetDetectedItem( Index ) - if DetectedItem then - local DetectedSet = self:GetDetectedSet( Index ) - local ThreatLevelA2G = self:GetTreatLevelA2G( DetectedItem ) - local ReportSummaryItem - - local DetectedZone = self:GetDetectedZone( Index ) - local DetectedItemPointVec3 = DetectedZone:GetPointVec3() - local DetectedItemPointLL = DetectedItemPointVec3:ToStringLL( 3, true ) - local ReportSummary = string.format( - "%s - Threat Level [%s] (%2d)", - DetectedItemPointLL, - string.rep( "â– ", ThreatLevelA2G ), - ThreatLevelA2G - ) - - return ReportSummary - end - - return nil - end - - - --- Returns if there are friendlies nearby the FAC units ... - -- @param #DETECTION_AREAS self - -- @return #boolean trhe if there are friendlies nearby - function DETECTION_AREAS:IsFriendliesNearBy( DetectedItem ) - - self:T3( DetectedItem.FriendliesNearBy ) - return DetectedItem.FriendliesNearBy or false - end - - --- Calculate the maxium A2G threat level of the DetectedItem. - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedItem ) - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( DetectedItem.Set:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - DetectedItem.MaxThreatLevelA2G = MaxThreatLevelA2G - - end - - --- Find the nearest FAC of the DetectedItem. - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return Wrapper.Unit#UNIT The nearest FAC unit - function DETECTION_AREAS:NearestFAC( DetectedItem ) - - local NearestFAC = nil - local MinDistance = 1000000000 -- Units are not further than 1000000 km away from an area :-) - - for FACGroupName, FACGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do - local FACUnit = FACUnitData -- Wrapper.Unit#UNIT - if FACUnit:IsActive() then - local Vec3 = FACUnit:GetVec3() - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetVec3() ) ) - if Distance < MinDistance then - MinDistance = Distance - NearestFAC = FACUnit - end - end - end - end - - DetectedItem.NearestFAC = NearestFAC - - end - - --- Returns the A2G threat level of the units in the DetectedItem - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #number a scale from 0 to 10. - function DETECTION_AREAS:GetTreatLevelA2G( DetectedItem ) - - self:T3( DetectedItem.MaxThreatLevelA2G ) - return DetectedItem.MaxThreatLevelA2G - end - - - - --- Smoke the detected units - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:SmokeDetectedUnits() - self:F2() - - self._SmokeDetectedUnits = true - return self - end - - --- Flare the detected units - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:FlareDetectedUnits() - self:F2() - - self._FlareDetectedUnits = true - return self - end - - --- Smoke the detected zones - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:SmokeDetectedZones() - self:F2() - - self._SmokeDetectedZones = true - return self - end - - --- Flare the detected zones - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:FlareDetectedZones() - self:F2() - - self._FlareDetectedZones = true - return self - end - - --- Bound the detected zones - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:BoundDetectedZones() - self:F2() - - self._BoundDetectedZones = true - return self - end - - --- Make text documenting the changes of the detected zone. - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #string The Changes text - function DETECTION_AREAS:GetChangeText( DetectedItem ) - self:F( DetectedItem ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - - if ChangeCode == "AA" then - MT[#MT+1] = "Detected new area " .. ChangeData.ItemID .. ". The center target is a " .. ChangeData.ItemUnitType .. "." - end - - if ChangeCode == "RAU" then - MT[#MT+1] = "Changed area " .. ChangeData.ItemID .. ". Removed the center target." - end - - if ChangeCode == "AAU" then - MT[#MT+1] = "Changed area " .. ChangeData.ItemID .. ". The new center target is a " .. ChangeData.ItemUnitType "." - end - - if ChangeCode == "RA" then - MT[#MT+1] = "Removed old area " .. ChangeData.ItemID .. ". No more targets in this area." - end - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Detected for area " .. ChangeData.ItemID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Removed for area " .. ChangeData.ItemID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - - end - - - --- Make a DetectionSet table. This function will be overridden in the derived clsses. - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:CreateDetectionSets() - self:F2() - - - self:T( "Checking Detected Items for new Detected Units ..." ) - -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. - -- Regroup when needed, split groups when needed. - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - if DetectedItem then - - self:T( { "Detected Item ID:", DetectedItemID } ) - - - local DetectedSet = DetectedItem.Set - - local AreaExists = false -- This flag will determine of the detected area is still existing. - - -- First test if the center unit is detected in the detection area. - self:T3( { "Zone Center Unit:", DetectedItem.Zone.ZoneUNIT.UnitName } ) - local DetectedZoneObject = self:GetDetectedObject( DetectedItem.Zone.ZoneUNIT.UnitName ) - self:T3( { "Detected Zone Object:", DetectedItem.Zone:GetName(), DetectedZoneObject } ) - - if DetectedZoneObject then - - --self:IdentifyDetectedObject( DetectedZoneObject ) - AreaExists = true - - - - else - -- The center object of the detected area has not been detected. Find an other unit of the set to become the center of the area. - -- First remove the center unit from the set. - DetectedSet:RemoveUnitsByName( DetectedItem.Zone.ZoneUNIT.UnitName ) - - self:AddChangeItem( DetectedItem, 'RAU', "Dummy" ) - - -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. - for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) - - -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. - -- If the DetectedUnit was already identified, DetectedObject will be nil. - if DetectedObject then - self:IdentifyDetectedObject( DetectedObject ) - AreaExists = true - - DetectedItem.Zone:BoundZone( 12, self.CountryID, true) - - -- Assign the Unit as the new center unit of the detected area. - DetectedItem.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) - - self:AddChangeItem( DetectedItem, "AAU", DetectedItem.Zone.ZoneUNIT:GetTypeName() ) - - -- We don't need to add the DetectedObject to the area set, because it is already there ... - break - end - end - end - - -- Now we've determined the center unit of the area, now we can iterate the units in the detected area. - -- Note that the position of the area may have moved due to the center unit repositioning. - -- If no center unit was identified, then the detected area does not exist anymore and should be deleted, as there are no valid units that can be the center unit. - if AreaExists then - - -- ok, we found the center unit of the area, now iterate through the detected area set and see which units are still within the center unit zone ... - -- Those units within the zone are flagged as Identified. - -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. - for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - local DetectedObject = nil - if DetectedUnit:IsAlive() then - --self:E(DetectedUnit:GetName()) - DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) - end - if DetectedObject then - - -- Check if the DetectedUnit is within the DetectedItem.Zone - if DetectedUnit:IsInZone( DetectedItem.Zone ) then - - -- Yes, the DetectedUnit is within the DetectedItem.Zone, no changes, DetectedUnit can be kept within the Set. - self:IdentifyDetectedObject( DetectedObject ) - - else - -- No, the DetectedUnit is not within the DetectedItem.Zone, remove DetectedUnit from the Set. - DetectedSet:Remove( DetectedUnitName ) - self:AddChangeUnit( DetectedItem, "RU", DetectedUnit:GetTypeName() ) - end - - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedItem, "RU", "destroyed target" ) - DetectedSet:Remove( DetectedUnitName ) - - -- The DetectedObject has been identified, because it does not exist ... - -- self:IdentifyDetectedObject( DetectedObject ) - end - end - else - DetectedItem.Zone:BoundZone( 12, self.CountryID, true) - self:RemoveDetectedItem( DetectedItemID ) - self:AddChangeItem( DetectedItem, "RA" ) - end - end - end - - -- We iterated through the existing detection areas and: - -- - We checked which units are still detected in each detection area. Those units were flagged as Identified. - -- - We recentered the detection area to new center units where it was needed. - -- - -- Now we need to loop through the unidentified detected units and see where they belong: - -- - They can be added to a new detection area and become the new center unit. - -- - They can be added to a new detection area. - for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - - if DetectedObject then - - -- We found an unidentified unit outside of any existing detection area. - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - - local AddedToDetectionArea = false - - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - if DetectedItem then - self:T( "Detection Area #" .. DetectedItem.ItemID ) - local DetectedSet = DetectedItem.Set - if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedItem.Zone ) then - self:IdentifyDetectedObject( DetectedObject ) - DetectedSet:AddUnit( DetectedUnit ) - AddedToDetectionArea = true - self:AddChangeUnit( DetectedItem, "AU", DetectedUnit:GetTypeName() ) - end - end - end - - if AddedToDetectionArea == false then - - -- New detection area - local DetectedItem = self:AddDetectedItemZone( nil, - SET_UNIT:New(), - ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - ) - --self:E( DetectedItem.Zone.ZoneUNIT.UnitName ) - DetectedItem.Set:AddUnit( DetectedUnit ) - self:AddChangeItem( DetectedItem, "AA", DetectedUnit:GetTypeName() ) - end - end - end - - -- Now all the tests should have been build, now make some smoke and flares... - -- We also report here the friendlies within the detected areas. - - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - self:CalculateThreatLevelA2G( DetectedItem ) -- Calculate A2G threat level - self:NearestFAC( DetectedItem ) - - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedZone.ZoneUNIT:SmokeRed() - end - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit ) - if DetectedUnit:IsAlive() then - self:T( "Detected Set #" .. DetectedItem.ItemID .. ":" .. DetectedUnit:GetName() ) - if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then - DetectedUnit:FlareGreen() - end - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedUnit:SmokeGreen() - end - end - end - ) - if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) ) - end - if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then - DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) - end - - if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then - DetectedZone:BoundZone( 12, self.CountryID ) - end - end - - end - -end ---- Single-Player:**No** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**All** -- **AI Balancing will replace in multi player missions --- non-occupied human slots with AI groups, in order to provide an engaging simulation environment, --- even when there are hardly any players in the mission.** --- --- ![Banner Image](..\Presentations\AI_Balancer\Dia1.JPG) --- --- === --- --- # 1) @{AI_Balancer#AI_BALANCER} class, extends @{Fsm#FSM_SET} --- --- The @{AI_Balancer#AI_BALANCER} class monitors and manages as many replacement AI groups as there are --- CLIENTS in a SET_CLIENT collection, which are not occupied by human players. --- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions. --- --- The parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM). --- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods. --- An explanation about state and event transition methods can be found in the @{FSM} module documentation. --- --- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following: --- --- * **@{#AI_BALANCER.OnAfterSpawned}**( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned. --- --- ## 1.1) AI_BALANCER construction --- --- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method: --- --- ## 1.2) AI_BALANCER is a FSM --- --- ![Process](..\Presentations\AI_Balancer\Dia13.JPG) --- --- ### 1.2.1) AI_BALANCER States --- --- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients. --- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference. --- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes. --- * **Destroying** ( Set, AIGroup ): The AI is being destroyed. --- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any. --- --- ### 1.2.2) AI_BALANCER Events --- --- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set. --- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference. --- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes. --- * **Destroy** ( Set, AIGroup ): The AI is being destroyed. --- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. --- --- ## 1.3) AI_BALANCER spawn interval for replacement AI --- --- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned. --- --- ## 1.4) AI_BALANCER returns AI to Airbases --- --- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default. --- However, there are 2 additional options that you can use to customize the destroy behaviour. --- When a human player joins a slot, you can configure to let the AI return to: --- --- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}. --- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}. --- --- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return, --- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed. --- --- === --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-01-17: There is still a problem with AI being destroyed, but not respawned. Need to check further upon that. --- --- 2017-01-08: AI_BALANCER:**InitSpawnInterval( Earliest, Latest )** added. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- * **SNAFU**: Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. None of the script code has been used however within the new AI_BALANCER moose class. --- --- ### Authors: --- --- * FlightControl: Framework Design & Programming and Documentation. --- --- @module AI_Balancer - ---- AI_BALANCER class --- @type AI_BALANCER --- @field Core.Set#SET_CLIENT SetClient --- @field Functional.Spawn#SPAWN SpawnAI --- @field Wrapper.Group#GROUP Test --- @extends Core.Fsm#FSM_SET -AI_BALANCER = { - ClassName = "AI_BALANCER", - PatrolZones = {}, - AIGroups = {}, - Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds. - Latest = 60, -- Latest a new AI can be spawned is in 60 seconds. -} - - - ---- Creates a new AI_BALANCER object --- @param #AI_BALANCER self --- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). --- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed. --- @return #AI_BALANCER -function AI_BALANCER:New( SetClient, SpawnAI ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER - - -- TODO: Define the OnAfterSpawned event - self:SetStartState( "None" ) - self:AddTransition( "*", "Monitor", "Monitoring" ) - self:AddTransition( "*", "Spawn", "Spawning" ) - self:AddTransition( "Spawning", "Spawned", "Spawned" ) - self:AddTransition( "*", "Destroy", "Destroying" ) - self:AddTransition( "*", "Return", "Returning" ) - - self.SetClient = SetClient - self.SetClient:FilterOnce() - self.SpawnAI = SpawnAI - - self.SpawnQueue = {} - - self.ToNearestAirbase = false - self.ToHomeAirbase = false - - self:__Monitor( 1 ) - - return self -end - ---- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI. --- Provide 2 identical seconds if the interval should be a fixed amount of seconds. --- @param #AI_BALANCER self --- @param #number Earliest The earliest a new AI can be spawned in seconds. --- @param #number Latest The latest a new AI can be spawned in seconds. --- @return self -function AI_BALANCER:InitSpawnInterval( Earliest, Latest ) - - self.Earliest = Earliest - self.Latest = Latest - - return self -end - ---- Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- @param #AI_BALANCER self --- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. --- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to. -function AI_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) - - self.ToNearestAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange - self.ReturnAirbaseSet = ReturnAirbaseSet -end - ---- Returns the AI to the home @{Airbase#AIRBASE}. --- @param #AI_BALANCER self --- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. -function AI_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) - - self.ToHomeAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange -end - ---- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param #string ClientName --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName ) - - -- OK, Spawn a new group from the default SpawnAI object provided. - local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP - if AIGroup then - AIGroup:E( "Spawning new AIGroup" ) - --TODO: need to rework UnitName thing ... - - SetGroup:Add( ClientName, AIGroup ) - self.SpawnQueue[ClientName] = nil - - -- Fire the Spawned event. The first parameter is the AIGroup just Spawned. - -- Mission designers can catch this event to bind further actions to the AIGroup. - self:Spawned( AIGroup ) - end -end - ---- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup ) - - AIGroup:Destroy() - SetGroup:Flush() - SetGroup:Remove( ClientName ) - SetGroup:Flush() -end - ---- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup ) - - local AIGroupTemplate = AIGroup:GetTemplate() - if self.ToHomeAirbase == true then - local WayPointCount = #AIGroupTemplate.route.points - local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) - AIGroup:SetCommand( SwitchWayPointCommand ) - AIGroup:MessageToRed( "Returning to home base ...", 30 ) - else - -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) - local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:T( ClosestAirbase.AirbaseName ) - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - end - -end - - ---- @param #AI_BALANCER self -function AI_BALANCER:onenterMonitoring( SetGroup ) - - self:T2( { self.SetClient:Count() } ) - --self.SetClient:Flush() - - self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client - function( Client ) - self:T3(Client.ClientName) - - local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP - if Client:IsAlive() then - - if AIGroup and AIGroup:IsAlive() == true then - - if self.ToNearestAirbase == false and self.ToHomeAirbase == false then - self:Destroy( Client.UnitName, AIGroup ) - else - -- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group. - -- If there is a CLIENT, the AI stays engaged and will not return. - -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. - - local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) - - self:T2( RangeZone ) - - _DATABASE:ForEachPlayer( - --- @param Wrapper.Unit#UNIT RangeTestUnit - function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) - self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) - if RangeTestUnit:IsInZone( RangeZone ) == true then - self:T2( "in zone" ) - if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then - self:T2( "in range" ) - PlayerInRange.Value = true - end - end - end, - - --- @param Core.Zone#ZONE_RADIUS RangeZone - -- @param Wrapper.Group#GROUP AIGroup - function( RangeZone, AIGroup, PlayerInRange ) - if PlayerInRange.Value == false then - self:Return( AIGroup ) - end - end - , RangeZone, AIGroup, PlayerInRange - ) - - end - self.Set:Remove( Client.UnitName ) - end - else - if not AIGroup or not AIGroup:IsAlive() == true then - self:T( "Client " .. Client.UnitName .. " not alive." ) - if not self.SpawnQueue[Client.UnitName] then - -- Spawn a new AI taking into account the spawn interval Earliest, Latest - self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName ) - self.SpawnQueue[Client.UnitName] = true - self:E( "New AI Spawned for Client " .. Client.UnitName ) - end - end - end - return true - end - ) - - self:__Monitor( 10 ) -end - - - ---- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**Air** -- --- **Air Patrolling or Staging.** --- --- ![Banner Image](..\Presentations\AI_PATROL\Dia1.JPG) --- --- === --- --- # 1) @{#AI_PATROL_ZONE} class, extends @{Fsm#FSM_CONTROLLABLE} --- --- The @{#AI_PATROL_ZONE} class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}. --- --- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) --- --- The AI_PATROL_ZONE is assigned a @{Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_PATROL\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_PATROL\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia9.JPG) --- ----- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or --- use derived AI_ classes to model AI offensive or defensive behaviour. --- --- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) --- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) --- --- ## 1.1) AI_PATROL_ZONE constructor --- --- * @{#AI_PATROL_ZONE.New}(): Creates a new AI_PATROL_ZONE object. --- --- ## 1.2) AI_PATROL_ZONE is a FSM --- --- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) --- --- ### 1.2.1) AI_PATROL_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Returning** ( Group ): The AI is returning to Base. --- * **Stopped** ( Group ): The process is stopped. --- * **Crashed** ( Group ): The AI has crashed or is dead. --- --- ### 1.2.2) AI_PATROL_ZONE Events --- --- * **Start** ( Group ): Start the process. --- * **Stop** ( Group ): Stop the process. --- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone. --- * **RTB** ( Group ): Route the AI to the home base. --- * **Detect** ( Group ): The AI is detecting targets. --- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. --- --- ## 1.3) Set or Get the AI controllable --- --- * @{#AI_PATROL_ZONE.SetControllable}(): Set the AIControllable. --- * @{#AI_PATROL_ZONE.GetControllable}(): Get the AIControllable. --- --- ## 1.4) Set the Speed and Altitude boundaries of the AI controllable --- --- * @{#AI_PATROL_ZONE.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol. --- * @{#AI_PATROL_ZONE.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol. --- --- ## 1.5) Manage the detection process of the AI controllable --- --- The detection process of the AI controllable can be manipulated. --- Detection requires an amount of CPU power, which has an impact on your mission performance. --- Only put detection on when absolutely necessary, and the frequency of the detection can also be set. --- --- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. --- --- The detection frequency can be set with @{#AI_PATROL_ZONE.SetDetectionInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection. --- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Unit}s detected by the AI. --- --- The detection can be filtered to potential targets in a specific zone. --- Use the method @{#AI_PATROL_ZONE.SetDetectionZone}() to set the zone where targets need to be detected. --- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected --- according the weather conditions. --- --- ## 1.6) Manage the "out of fuel" in the AI_PATROL_ZONE --- --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, --- while a new AI is targetted to the AI_PATROL_ZONE. --- Once the time is finished, the old AI will return to the base. --- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place. --- --- ## 1.7) Manage "damage" behaviour of the AI in the AI_PATROL_ZONE --- --- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on. --- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB). --- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place. --- --- ==== --- --- # **OPEN ISSUES** --- --- 2017-01-17: When Spawned AI is located at an airbase, it will be routed first back to the airbase after take-off. --- --- 2016-01-17: --- -- Fixed problem with AI returning to base too early and unexpected. --- -- ReSpawning of AI will reset the AI_PATROL and derived classes. --- -- Checked the correct workings of SCHEDULER, and it DOES work correctly. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-01-17: Rename of class: **AI\_PATROL\_ZONE** is the new name for the old _AI\_PATROLZONE_. --- --- 2017-01-15: Complete revision. AI_PATROL_ZONE is the base class for other AI_PATROL like classes. --- --- 2016-09-01: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Testing and API concept review. --- --- ### Authors: --- --- * **FlightControl**: Design & Programming. --- --- @module AI_Patrol - ---- AI_PATROL_ZONE class --- @type AI_PATROL_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. --- @field Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @field Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @field Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @field Functional.Spawn#SPAWN CoordTest --- @extends Core.Fsm#FSM_CONTROLLABLE -AI_PATROL_ZONE = { - ClassName = "AI_PATROL_ZONE", -} - ---- Creates a new AI_PATROL_ZONE object --- @param #AI_PATROL_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_PATROL_ZONE self --- @usage --- -- Define a new AI_PATROL_ZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolSpawn = SPAWN:New( 'Patrol Group' ) --- PatrolArea = AI_PATROL_ZONE:New( PatrolZone, 3000, 6000, 600, 900 ) -function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_PATROL_ZONE - - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - -- defafult PatrolAltType to "RADIO" if not specified - self.PatrolAltType = PatrolAltType or "RADIO" - - self:SetDetectionInterval( 30 ) - - self.CheckStatus = true - - self:ManageFuel( .2, 60 ) - self:ManageDamage( 1 ) - - - self.DetectedUnits = {} -- This table contains the targets detected during patrol. - - self:SetStartState( "None" ) - - self:AddTransition( "*", "Stop", "Stopped" ) - ---- OnLeave Transition Handler for State Stopped. --- @function [parent=#AI_PATROL_ZONE] OnLeaveStopped --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Stopped. --- @function [parent=#AI_PATROL_ZONE] OnEnterStopped --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- OnBefore Transition Handler for Event Stop. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStop --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Stop. --- @function [parent=#AI_PATROL_ZONE] OnAfterStop --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Stop. --- @function [parent=#AI_PATROL_ZONE] Stop --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Stop. --- @function [parent=#AI_PATROL_ZONE] __Stop --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "None", "Start", "Patrolling" ) - ---- OnBefore Transition Handler for Event Start. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStart --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Start. --- @function [parent=#AI_PATROL_ZONE] OnAfterStart --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Start. --- @function [parent=#AI_PATROL_ZONE] Start --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Start. --- @function [parent=#AI_PATROL_ZONE] __Start --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Patrolling. --- @function [parent=#AI_PATROL_ZONE] OnLeavePatrolling --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Patrolling. --- @function [parent=#AI_PATROL_ZONE] OnEnterPatrolling --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Route. --- @function [parent=#AI_PATROL_ZONE] OnBeforeRoute --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Route. --- @function [parent=#AI_PATROL_ZONE] OnAfterRoute --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Route. --- @function [parent=#AI_PATROL_ZONE] Route --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Route. --- @function [parent=#AI_PATROL_ZONE] __Route --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Status. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStatus --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Status. --- @function [parent=#AI_PATROL_ZONE] OnAfterStatus --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Status. --- @function [parent=#AI_PATROL_ZONE] Status --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Status. --- @function [parent=#AI_PATROL_ZONE] __Status --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Detect", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Detect. --- @function [parent=#AI_PATROL_ZONE] OnBeforeDetect --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Detect. --- @function [parent=#AI_PATROL_ZONE] OnAfterDetect --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Detect. --- @function [parent=#AI_PATROL_ZONE] Detect --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Detect. --- @function [parent=#AI_PATROL_ZONE] __Detect --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Detected", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Detected. --- @function [parent=#AI_PATROL_ZONE] OnBeforeDetected --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Detected. --- @function [parent=#AI_PATROL_ZONE] OnAfterDetected --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Detected. --- @function [parent=#AI_PATROL_ZONE] Detected --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Detected. --- @function [parent=#AI_PATROL_ZONE] __Detected --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "RTB", "Returning" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event RTB. --- @function [parent=#AI_PATROL_ZONE] OnBeforeRTB --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event RTB. --- @function [parent=#AI_PATROL_ZONE] OnAfterRTB --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event RTB. --- @function [parent=#AI_PATROL_ZONE] RTB --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event RTB. --- @function [parent=#AI_PATROL_ZONE] __RTB --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Returning. --- @function [parent=#AI_PATROL_ZONE] OnLeaveReturning --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Returning. --- @function [parent=#AI_PATROL_ZONE] OnEnterReturning --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - - self:AddTransition( "*", "Eject", "*" ) - self:AddTransition( "*", "Crash", "Crashed" ) - self:AddTransition( "*", "PilotDead", "*" ) - - return self -end - - - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_PATROL_ZONE self --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_PATROL_ZONE self --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - --- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. - ---- Set the detection on. The AI will detect for targets. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionOn() - self:F2() - - self.DetectOn = true -end - ---- Set the detection off. The AI will NOT detect for targets. --- However, the list of already detected targets will be kept and can be enquired! --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionOff() - self:F2() - - self.DetectOn = false -end - ---- Set the status checking off. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetStatusOff() - self:F2() - - self.CheckStatus = false -end - ---- Activate the detection. The AI will detect for targets if the Detection is switched On. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionActivated() - self:F2() - - self:ClearDetectedUnits() - self.DetectActivated = true - self:__Detect( -self.DetectInterval ) -end - ---- Deactivate the detection. The AI will NOT detect for targets. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionDeactivated() - self:F2() - - self:ClearDetectedUnits() - self.DetectActivated = false -end - ---- Set the interval in seconds between each detection executed by the AI. --- The list of already detected targets will be kept and updated. --- Newly detected targets will be added, but already detected targets that were --- not detected in this cycle, will NOT be removed! --- The default interval is 30 seconds. --- @param #AI_PATROL_ZONE self --- @param #number Seconds The interval in seconds. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionInterval( Seconds ) - self:F2() - - if Seconds then - self.DetectInterval = Seconds - else - self.DetectInterval = 30 - end -end - ---- Set the detection zone where the AI is detecting targets. --- @param #AI_PATROL_ZONE self --- @param Core.Zone#ZONE DetectionZone The zone where to detect targets. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionZone( DetectionZone ) - self:F2() - - if DetectionZone then - self.DetectZone = DetectionZone - else - self.DetectZone = nil - end -end - ---- Gets a list of @{Unit#UNIT}s that were detected by the AI. --- No filtering is applied, so, ANY detected UNIT can be in this list. --- It is up to the mission designer to use the @{Unit} class and methods to filter the targets. --- @param #AI_PATROL_ZONE self --- @return #table The list of @{Unit#UNIT}s -function AI_PATROL_ZONE:GetDetectedUnits() - self:F2() - - return self.DetectedUnits -end - ---- Clears the list of @{Unit#UNIT}s that were detected by the AI. --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ClearDetectedUnits() - self:F2() - self.DetectedUnits = {} -end - ---- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE. --- Once the time is finished, the old AI will return to the base. --- @param #AI_PATROL_ZONE self --- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolManageFuel = true - self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - return self -end - ---- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base. --- However, damage cannot be foreseen early on. --- Therefore, when the damage treshold is reached, --- the AI will return immediately to the home base (RTB). --- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25. --- @param #AI_PATROL_ZONE self --- @param #number PatrolDamageTreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ManageDamage( PatrolDamageTreshold ) - - self.PatrolManageDamage = true - self.PatrolDamageTreshold = PatrolDamageTreshold - - return self -end - ---- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To ) - self:F2() - - self:__Route( 1 ) -- Route to the patrol point. The asynchronous trigger is important, because a spawned group and units takes at least one second to come live. - self:__Status( 60 ) -- Check status status every 30 seconds. - self:SetDetectionActivated() - - self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead ) - self:HandleEvent( EVENTS.Crash, self.OnCrash ) - self:HandleEvent( EVENTS.Ejection, self.OnEjection ) - - Controllable:OptionROEHoldFire() - Controllable:OptionROTVertical() - - self.Controllable:OnReSpawn( - function( PatrolGroup ) - self:E( "ReSpawn" ) - self:__Reset( 1 ) - self:__Route( 5 ) - end - ) - - self:SetDetectionOn() - -end - - ---- @param #AI_PATROL_ZONE self ---- @param Wrapper.Controllable#CONTROLLABLE Controllable -function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To ) - - return self.DetectOn and self.DetectActivated -end - ---- @param #AI_PATROL_ZONE self ---- @param Wrapper.Controllable#CONTROLLABLE Controllable -function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To ) - - local Detected = false - - local DetectedTargets = Controllable:GetDetectedTargets() - for TargetID, Target in pairs( DetectedTargets or {} ) do - local TargetObject = Target.object - - if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then - - local TargetUnit = UNIT:Find( TargetObject ) - local TargetUnitName = TargetUnit:GetName() - - if self.DetectionZone then - if TargetUnit:IsInZone( self.DetectionZone ) then - self:T( {"Detected ", TargetUnit } ) - if self.DetectedUnits[TargetUnit] == nil then - self.DetectedUnits[TargetUnit] = true - end - Detected = true - end - else - if self.DetectedUnits[TargetUnit] == nil then - self.DetectedUnits[TargetUnit] = true - end - Detected = true - end - end - end - - self:__Detect( -self.DetectInterval ) - - if Detected == true then - self:__Detected( 1.5 ) - end - -end - ---- @param Wrapper.Controllable#CONTROLLABLE AIControllable --- This statis method is called from the route path within the last task at the last waaypoint of the Controllable. --- Note that this method is required, as triggers the next route when patrolling for the Controllable. -function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable ) - - local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROL_ZONE - PatrolZone:__Route( 1 ) -end - - ---- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) - - self:F2() - - -- When RTB, don't allow anymore the routing. - if From == "RTB" then - return - end - - - if self.Controllable:IsAlive() then - -- Determine if the AIControllable is within the PatrolZone. - -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. - - local PatrolRoute = {} - - -- Calculate the current route point of the controllable as the start point of the route. - -- However, when the controllable is not in the air, - -- the controllable current waypoint is probably the airbase... - -- Thus, if we would take the current waypoint as the startpoint, upon take-off, the controllable flies - -- immediately back to the airbase, and this is not correct. - -- Therefore, when on a runway, get as the current route point a random point within the PatrolZone. - -- This will make the plane fly immediately to the patrol zone. - - if self.Controllable:InAir() == false then - self:E( "Not in the air, finding route path within PatrolZone" ) - local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TakeOffParking, - POINT_VEC3.RoutePointAction.FromParkingArea, - ToPatrolZoneSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - else - self:E( "In the air, finding route path within PatrolZone" ) - local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - end - - - --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - --self.CoordTest:SpawnFromVec3( ToTargetPointVec3:GetVec3() ) - - --ToTargetPointVec3:SmokeRed() - - PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( PatrolRoute ) - - --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "PatrolZone", self ) - self.Controllable:WayPointFunction( #PatrolRoute, 1, "AI_PATROL_ZONE:_NewPatrolRoute" ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 2 ) - end - -end - ---- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onbeforeStatus() - - return self.CheckStatus -end - ---- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterStatus() - self:F2() - - if self.Controllable and self.Controllable:IsAlive() then - - local RTB = false - - local Fuel = self.Controllable:GetUnit(1):GetFuel() - if Fuel < self.PatrolFuelTresholdPercentage then - self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" ) - local OldAIControllable = self.Controllable - local AIControllableTemplate = self.Controllable:GetTemplate() - - local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldAIControllable:SetTask( TimedOrbitTask, 10 ) - - RTB = true - else - end - - -- TODO: Check GROUP damage function. - local Damage = self.Controllable:GetLife() - if Damage <= self.PatrolDamageTreshold then - self:E( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" ) - RTB = true - end - - if RTB == true then - self:RTB() - else - self:__Status( 60 ) -- Execute the Patrol event after 30 seconds. - end - end -end - ---- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterRTB() - self:F2() - - if self.Controllable and self.Controllable:IsAlive() then - - self:SetDetectionOff() - self.CheckStatus = false - - local PatrolRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( PatrolRoute ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 1 ) - - end - -end - ---- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterDead() - self:SetDetectionOff() - self:SetStatusOff() -end - ---- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnCrash( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:E( self.Controllable:GetUnits() ) - if #self.Controllable:GetUnits() == 1 then - self:__Crash( 1, EventData ) - end - end -end - ---- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnEjection( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__Eject( 1, EventData ) - end -end - ---- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnPilotDead( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__PilotDead( 1, EventData ) - end -end ---- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**Air** -- --- **Provide Close Air Support to friendly ground troops.** --- --- ![Banner Image](..\Presentations\AI_CAS\Dia1.JPG) --- --- === --- --- # 1) @{#AI_CAS_ZONE} class, extends @{AI_Patrol#AI_PATROL_ZONE} --- --- @{#AI_CAS_ZONE} derives from the @{AI_Patrol#AI_PATROL_ZONE}, inheriting its methods and behaviour. --- --- The @{#AI_CAS_ZONE} class implements the core functions to provide Close Air Support in an Engage @{Zone} by an AIR @{Controllable} or @{Group}. --- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone. --- --- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG) --- --- The AI_CAS_ZONE is assigned a @{Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event. --- --- ![Start Event](..\Presentations\AI_CAS\Dia4.JPG) --- --- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, --- using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- --- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG) --- --- When the AI is commanded to provide Close Air Support (through the event **Engage**), the AI will fly towards the Engage Zone. --- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia6.JPG) --- --- The AI will detect the targets and will only destroy the targets within the Engage Zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia7.JPG) --- --- Every target that is destroyed, is reported< by the AI. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia8.JPG) --- --- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia9.JPG) --- --- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party: --- --- * a FAC --- * a timed event --- * a menu option selected by a human --- * a condition --- * others ... --- --- ![Engage Event](..\Presentations\AI_CAS\Dia10.JPG) --- --- When the AI has accomplished the CAS, it will fly back to the Patrol Zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia11.JPG) --- --- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone. --- It can be notified to go RTB through the **RTB** event. --- --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG) --- --- # 1.1) AI_CAS_ZONE constructor --- --- * @{#AI_CAS_ZONE.New}(): Creates a new AI_CAS_ZONE object. --- --- ## 1.2) AI_CAS_ZONE is a FSM --- --- ![Process](..\Presentations\AI_CAS\Dia2.JPG) --- --- ### 1.2.1) AI_CAS_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing CAS. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 1.2.2) AI_CAS_ZONE Events --- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_CAS_ZONE.Engage}**: Engage the AI to provide CAS in the Engage Zone, destroying any target it finds. --- * **@{#AI_CAS_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Unit}. --- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Unit}s assigned in the CAS task. --- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-01-15: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing. --- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Concept, Advice & Testing. --- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision. --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module AI_Cas - - ---- AI_CAS_ZONE class --- @type AI_CAS_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. --- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed. --- @extends AI.AI_Patrol#AI_PATROL_ZONE -AI_CAS_ZONE = { - ClassName = "AI_CAS_ZONE", -} - - - ---- Creates a new AI_CAS_ZONE object --- @param #AI_CAS_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_CAS_ZONE self -function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAS_ZONE - - self.EngageZone = EngageZone - self.Accomplished = false - - self:SetDetectionZone( self.EngageZone ) - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_CAS_ZONE] OnBeforeEngage - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_CAS_ZONE] OnAfterEngage - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAS_ZONE] Engage - -- @param #AI_CAS_ZONE self - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAS_ZONE] __Engage - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_CAS_ZONE] OnEnterEngaging --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_CAS_ZONE] OnBeforeFired - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_CAS_ZONE] OnAfterFired - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAS_ZONE] Fired - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAS_ZONE] __Fired - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] OnBeforeDestroy - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] OnAfterDestroy - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] Destroy - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] __Destroy - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_CAS_ZONE] OnBeforeAbort - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_CAS_ZONE] OnAfterAbort - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAS_ZONE] Abort - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAS_ZONE] __Abort - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] OnBeforeAccomplish - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] OnAfterAccomplish - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] Accomplish - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] __Accomplish - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - - ---- Set the Engage Zone where the AI is performing CAS. Note that if the EngageZone is changed, the AI needs to re-detect targets. --- @param #AI_CAS_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAS. --- @return #AI_CAS_ZONE self -function AI_CAS_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - - - ---- onafter State Transition for Event Start. --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To ) - - -- Call the parent Start event handler - self:GetParent(self).onafterStart( self, Controllable, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) - - self:SetDetectionDeactivated() -- When not engaging, set the detection off. -end - ---- @param Wrapper.Controllable#CONTROLLABLE AIControllable -function _NewEngageRoute( AIControllable ) - - AIControllable:T( "NewEngageRoute" ) - local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cas#AI_CAS_ZONE - EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection ) -end - - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To ) - self:E("onafterTarget") - - if Controllable:IsAlive() then - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - if Detected == true then - self:E( {"Target: ", DetectedUnit } ) - self.DetectedUnits[DetectedUnit] = false - local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) - self.Controllable:PushTask( AttackTask, 1 ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - self:__Target( -10 ) - - end -end - - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. --- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, - EngageSpeed, - EngageAltitude, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection ) - self:F("onafterEngage") - - self.EngageSpeed = EngageSpeed or 400 - self.EngageAltitude = EngageAltitude or 2000 - self.EngageWeaponExpend = EngageWeaponExpend - self.EngageAttackQty = EngageAttackQty - self.EngageDirection = EngageDirection - - if Controllable:IsAlive() then - - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - --- if self.Controllable:IsNotInZone( self.EngageZone ) then --- --- -- Find a random 2D point in EngageZone. --- local ToEngageZoneVec2 = self.EngageZone:GetRandomVec2() --- self:T2( ToEngageZoneVec2 ) --- --- -- Obtain a 3D @{Point} from the 2D point + altitude. --- local ToEngageZonePointVec3 = POINT_VEC3:New( ToEngageZoneVec2.x, self.EngageAltitude, ToEngageZoneVec2.y ) --- --- -- Create a route point of type air. --- local ToEngageZoneRoutePoint = ToEngageZonePointVec3:RoutePointAir( --- self.PatrolAltType, --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, --- self.EngageSpeed, --- true --- ) --- --- EngageRoute[#EngageRoute+1] = ToEngageZoneRoutePoint --- --- end --- - --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in EngageZone. - local ToTargetVec2 = self.EngageZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - self.EngageSpeed, - true - ) - - --ToTargetPointVec3:SmokeBlue() - - EngageRoute[#EngageRoute+1] = ToTargetRoutePoint - - - Controllable:OptionROEOpenFire() - Controllable:OptionROTVertical() - --- local AttackTasks = {} --- --- for DetectedUnitID, DetectedUnit in pairs( self.DetectedUnits ) do --- local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT --- self:T( DetectedUnit ) --- if DetectedUnit:IsAlive() then --- if DetectedUnit:IsInZone( self.EngageZone ) then --- self:E( {"Engaging ", DetectedUnit } ) --- AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) --- end --- else --- self.DetectedUnits[DetectedUnit] = nil --- end --- end --- --- EngageRoute[1].task = Controllable:TaskCombo( AttackTasks ) - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( EngageRoute ) - - --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "EngageZone", self ) - - self.Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1 ) - - self:SetDetectionInterval( 2 ) - self:SetDetectionActivated() - self:__Target( -2 ) -- Start Targetting - end -end - - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionDeactivated() -end - - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_CAS_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - - ---- @param #AI_CAS_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_CAS_ZONE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end - - ---- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**Air** -- **Execute Combat Air Patrol (CAP).** --- --- ![Banner Image](..\Presentations\AI_CAP\Dia1.JPG) --- --- === --- --- # 1) @{#AI_CAP_ZONE} class, extends @{AI_CAP#AI_PATROL_ZONE} --- --- The @{#AI_CAP_ZONE} class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group} --- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- --- ![Process](..\Presentations\AI_CAP\Dia3.JPG) --- --- The AI_CAP_ZONE is assigned a @{Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_CAP\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_CAP\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_CAP\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_CAP\Dia9.JPG) --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- ![Process](..\Presentations\AI_CAP\Dia10.JPG) --- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_CAP\Dia13.JPG) --- --- ## 1.1) AI_CAP_ZONE constructor --- --- * @{#AI_CAP_ZONE.New}(): Creates a new AI_CAP_ZONE object. --- --- ## 1.2) AI_CAP_ZONE is a FSM --- --- ![Process](..\Presentations\AI_CAP\Dia2.JPG) --- --- ### 1.2.1) AI_CAP_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the bogeys. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 1.2.2) AI_CAP_ZONE Events --- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_CAP_ZONE.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_CAP_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Unit}. --- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. --- --- ## 1.3) Set the Range of Engagement --- --- ![Range](..\Presentations\AI_CAP\Dia11.JPG) --- --- An optional range can be set in meters, --- that will define when the AI will engage with the detected airborne enemy targets. --- The range can be beyond or smaller than the range of the Patrol Zone. --- The range is applied at the position of the AI. --- Use the method @{AI_CAP#AI_CAP_ZONE.SetEngageRange}() to define that range. --- --- ## 1.4) Set the Zone of Engagement --- --- ![Zone](..\Presentations\AI_CAP\Dia12.JPG) --- --- An optional @{Zone} can be set, --- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{AI_Cap#AI_CAP_ZONE.SetEngageZone}() to define that Zone. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-01-15: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing. --- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Concept, Advice & Testing. --- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision. --- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing. --- * **[Delta99](https://forums.eagle.ru/member.php?u=125166): Testing. --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module AI_Cap - - ---- AI_CAP_ZONE class --- @type AI_CAP_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. --- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed. --- @extends AI.AI_Patrol#AI_PATROL_ZONE -AI_CAP_ZONE = { - ClassName = "AI_CAP_ZONE", -} - - - ---- Creates a new AI_CAP_ZONE object --- @param #AI_CAP_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAP_ZONE - - self.Accomplished = false - self.Engaging = false - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_CAP_ZONE] OnBeforeEngage - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_CAP_ZONE] OnAfterEngage - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAP_ZONE] Engage - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAP_ZONE] __Engage - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_CAP_ZONE] OnLeaveEngaging --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_CAP_ZONE] OnEnterEngaging --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_CAP_ZONE] OnBeforeFired - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_CAP_ZONE] OnAfterFired - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAP_ZONE] Fired - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAP_ZONE] __Fired - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] OnBeforeDestroy - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] OnAfterDestroy - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] Destroy - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] __Destroy - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_CAP_ZONE] OnBeforeAbort - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_CAP_ZONE] OnAfterAbort - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAP_ZONE] Abort - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAP_ZONE] __Abort - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] OnBeforeAccomplish - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] OnAfterAccomplish - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] Accomplish - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] __Accomplish - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - - ---- Set the Engage Zone which defines where the AI will engage bogies. --- @param #AI_CAP_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP. --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - ---- Set the Engage Range when the AI will engage with airborne enemies. --- @param #AI_CAP_ZONE self --- @param #number EngageRange The Engage Range. --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:SetEngageRange( EngageRange ) - self:F2() - - if EngageRange then - self.EngageRange = EngageRange - else - self.EngageRange = nil - end -end - ---- onafter State Transition for Event Start. --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To ) - - -- Call the parent Start event handler - self:GetParent(self).onafterStart( self, Controllable, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) - -end - --- todo: need to fix this global function - ---- @param Wrapper.Controllable#CONTROLLABLE AIControllable -function _NewEngageCapRoute( AIControllable ) - - AIControllable:T( "NewEngageRoute" ) - local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_CAP_ZONE - EngageZone:__Engage( 1 ) -end - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To ) - - if From ~= "Engaging" then - - local Engage = false - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( DetectedUnit ) - if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then - Engage = true - break - end - end - - if Engage == true then - self:E( 'Detected -> Engaging' ) - self:__Engage( 1 ) - end - end -end - - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - - - - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) - - if Controllable:IsAlive() then - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToEngageZoneSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = ToTargetPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - - Controllable:OptionROEOpenFire() - Controllable:OptionROTPassiveDefense() - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( { DetectedUnit, DetectedUnit:IsAlive(), DetectedUnit:IsAir() } ) - if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then - if self.EngageZone then - if DetectedUnit:IsInZone( self.EngageZone ) then - self:E( {"Within Zone and Engaging ", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - else - if self.EngageRange then - if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3() ) <= self.EngageRange then - self:E( {"Within Range and Engaging", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - else - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( EngageRoute ) - - - if #AttackTasks == 0 then - self:E("No targets found -> Going back to Patrolling") - self:__Abort( 1 ) - self:__Route( 1 ) - self:SetDetectionActivated() - else - EngageRoute[1].task = Controllable:TaskCombo( AttackTasks ) - - --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "EngageZone", self ) - - self.Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageCapRoute" ) - - self:SetDetectionDeactivated() - end - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 2 ) - - end -end - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionOff() -end - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_CAP_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - ---- @param #AI_CAP_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_CAP_ZONE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end ----Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**Ground** -- --- **Management of logical cargo objects, that can be transported from and to transportation carriers.** --- --- ![Banner Image](..\Presentations\AI_CARGO\CARGO.JPG) --- --- === --- --- Cargo can be of various forms, always are composed out of ONE object ( one unit or one static or one slingload crate ): --- --- * AI_CARGO_UNIT, represented by a @{Unit} in a @{Group}: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost. --- * CARGO_STATIC, represented by a @{Static}: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost. --- * AI_CARGO_PACKAGE, contained in a @{Unit} of a @{Group}: Cargo can be contained within a Unit of a Group. The cargo can be **delivered** by the @{Unit}. If the Unit is destroyed, the cargo will be destroyed also. --- * AI_CARGO_PACKAGE, Contained in a @{Static}: Cargo can be contained within a Static. The cargo can be **collected** from the @Static. If the @{Static} is destroyed, the cargo will be destroyed. --- * CARGO_SLINGLOAD, represented by a @{Cargo} that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost. --- --- * AI_CARGO_GROUPED, represented by a Group of CARGO_UNITs. --- --- # 1) @{#AI_CARGO} class, extends @{Fsm#FSM_PROCESS} --- --- The @{#AI_CARGO} class defines the core functions that defines a cargo object within MOOSE. --- A cargo is a logical object defined that is available for transport, and has a life status within a simulation. --- --- The AI_CARGO is a state machine: it manages the different events and states of the cargo. --- All derived classes from AI_CARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states. --- --- ## 1.2.1) AI_CARGO Events: --- --- * @{#AI_CARGO.Board}( ToCarrier ): Boards the cargo to a carrier. --- * @{#AI_CARGO.Load}( ToCarrier ): Loads the cargo into a carrier, regardless of its position. --- * @{#AI_CARGO.UnBoard}( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2. --- * @{#AI_CARGO.UnLoad}( ToPointVec2 ): UnLoads the cargo from a carrier. --- * @{#AI_CARGO.Dead}( Controllable ): The cargo is dead. The cargo process will be ended. --- --- ## 1.2.2) AI_CARGO States: --- --- * **UnLoaded**: The cargo is unloaded from a carrier. --- * **Boarding**: The cargo is currently boarding (= running) into a carrier. --- * **Loaded**: The cargo is loaded into a carrier. --- * **UnBoarding**: The cargo is currently unboarding (=running) from a carrier. --- * **Dead**: The cargo is dead ... --- * **End**: The process has come to an end. --- --- ## 1.2.3) AI_CARGO state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Leaving** the state. --- The state transition method needs to start with the name **OnLeave + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **Entering** the state. --- The state transition method needs to start with the name **OnEnter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- # 2) #AI_CARGO_UNIT class --- --- The AI_CARGO_UNIT class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. --- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. --- --- # 5) #AI_CARGO_GROUPED class --- --- The AI_CARGO_GROUPED class defines a cargo that is represented by a group of UNIT objects within the simulator, and can be transported by a carrier. --- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. --- --- This module is still under construction, but is described above works already, and will keep working ... --- --- @module Cargo - --- Events - --- Board - ---- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#AI_CARGO] Board --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - ---- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#AI_CARGO] __Board --- @param #AI_CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - - --- UnBoard - ---- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. --- The cargo must be in the **Loaded** state. --- @function [parent=#AI_CARGO] UnBoard --- @param #AI_CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. - ---- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. --- The cargo must be in the **Loaded** state. --- @function [parent=#AI_CARGO] __UnBoard --- @param #AI_CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. - - --- Load - ---- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#AI_CARGO] Load --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - ---- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#AI_CARGO] __Load --- @param #AI_CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - - --- UnLoad - ---- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. --- The cargo must be in the **Loaded** state. --- @function [parent=#AI_CARGO] UnLoad --- @param #AI_CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. - ---- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. --- The cargo must be in the **Loaded** state. --- @function [parent=#AI_CARGO] __UnLoad --- @param #AI_CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. - --- State Transition Functions - --- UnLoaded - ---- @function [parent=#AI_CARGO] OnLeaveUnLoaded --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#AI_CARGO] OnEnterUnLoaded --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- Loaded - ---- @function [parent=#AI_CARGO] OnLeaveLoaded --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#AI_CARGO] OnEnterLoaded --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- Boarding - ---- @function [parent=#AI_CARGO] OnLeaveBoarding --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#AI_CARGO] OnEnterBoarding --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- UnBoarding - ---- @function [parent=#AI_CARGO] OnLeaveUnBoarding --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#AI_CARGO] OnEnterUnBoarding --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - - --- TODO: Find all Carrier objects and make the type of the Carriers Wrapper.Unit#UNIT in the documentation. - -CARGOS = {} - -do -- AI_CARGO - - --- @type AI_CARGO - -- @extends Core.Fsm#FSM_PROCESS - -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. - -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. - -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. - -- @field #number ReportRadius (optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier. - -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. - -- @field Wrapper.Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... - -- @field Wrapper.Controllable#CONTROLLABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... - -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. - -- @field #boolean Moveable This flag defines if the cargo is moveable. - -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. - -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. - AI_CARGO = { - ClassName = "AI_CARGO", - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, - } - ---- @type AI_CARGO.CargoObjects --- @map < #string, Wrapper.Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. - - ---- AI_CARGO Constructor. This class is an abstract class and should not be instantiated. --- @param #AI_CARGO self --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO -function AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) - - local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:SetStartState( "UnLoaded" ) - self:AddTransition( "UnLoaded", "Board", "Boarding" ) - self:AddTransition( "Boarding", "Boarding", "Boarding" ) - self:AddTransition( "Boarding", "Load", "Loaded" ) - self:AddTransition( "UnLoaded", "Load", "Loaded" ) - self:AddTransition( "Loaded", "UnBoard", "UnBoarding" ) - self:AddTransition( "UnBoarding", "UnBoarding", "UnBoarding" ) - self:AddTransition( "UnBoarding", "UnLoad", "UnLoaded" ) - self:AddTransition( "Loaded", "UnLoad", "UnLoaded" ) - - - self.Type = Type - self.Name = Name - self.Weight = Weight - self.ReportRadius = ReportRadius - self.NearRadius = NearRadius - self.CargoObject = nil - self.CargoCarrier = nil - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.Name] = self - - return self -end - - ---- Template method to spawn a new representation of the AI_CARGO in the simulator. --- @param #AI_CARGO self --- @return #AI_CARGO -function AI_CARGO:Spawn( PointVec2 ) - self:F() - -end - - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #AI_CARGO self --- @param Core.Point#POINT_VEC2 PointVec2 --- @return #boolean -function AI_CARGO:IsNear( PointVec2 ) - self:F( { PointVec2 } ) - - local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - -end - -do -- AI_CARGO_REPRESENTABLE - - --- @type AI_CARGO_REPRESENTABLE - -- @extends #AI_CARGO - AI_CARGO_REPRESENTABLE = { - ClassName = "AI_CARGO_REPRESENTABLE" - } - ---- AI_CARGO_REPRESENTABLE Constructor. --- @param #AI_CARGO_REPRESENTABLE self --- @param Wrapper.Controllable#Controllable CargoObject --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_REPRESENTABLE -function AI_CARGO_REPRESENTABLE:New( CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - return self -end - ---- Route a cargo unit to a PointVec2. --- @param #AI_CARGO_REPRESENTABLE self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #number Speed --- @return #AI_CARGO_REPRESENTABLE -function AI_CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) - self:F2( ToPointVec2 ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - return self -end - -end -- AI_CARGO - -do -- AI_CARGO_UNIT - - --- @type AI_CARGO_UNIT - -- @extends #AI_CARGO_REPRESENTABLE - AI_CARGO_UNIT = { - ClassName = "AI_CARGO_UNIT" - } - ---- AI_CARGO_UNIT Constructor. --- @param #AI_CARGO_UNIT self --- @param Wrapper.Unit#UNIT CargoUnit --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_UNIT -function AI_CARGO_UNIT:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_UNIT - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoUnit ) - self.CargoObject = CargoUnit - - self:T( self.ClassName ) - - return self -end - ---- Enter UnBoarding State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function AI_CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local DeployDistance = 5 - local RouteDistance = 60 - - if From == "Loaded" then - - local CargoCarrierPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, CargoDeployHeading ) - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 - ToPointVec2 = ToPointVec2 or CargoRoutePointVec2 - - local FromPointVec2 = CargoCarrierPointVec2 - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - self.CargoCarrier = nil - - local Points = {} - Points[#Points+1] = FromPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - self:__UnBoarding( 1, ToPointVec2 ) - end - end - -end - ---- Leave UnBoarding State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function AI_CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - if self:IsNear( ToPointVec2 ) then - return true - else - self:__UnBoarding( 1, ToPointVec2 ) - end - return false - end - -end - ---- UnBoard Event. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function AI_CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - end - - self:__UnLoad( 1, ToPointVec2 ) - -end - - - ---- Enter UnLoaded State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 -function AI_CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "Loaded" then - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - ToPointVec2 = ToPointVec2 or POINT_VEC2:New( CargoDeployPointVec2:GetX(), CargoDeployPointVec2:GetY() ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - -end - - - ---- Enter Boarding State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - local Speed = 10 - local Angle = 180 - local Distance = 5 - - if From == "UnLoaded" then - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - end - -end - ---- Leave Boarding State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_UNIT:onleaveBoarding( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - if self:IsNear( CargoCarrier:GetPointVec2() ) then - self:__Load( 1, CargoCarrier ) - return true - else - self:__Boarding( 1, CargoCarrier ) - end - return false -end - ---- Loaded State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier ) - self:F() - - self.CargoCarrier = CargoCarrier - - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self:T("Destroying") - self.CargoObject:Destroy() - end -end - - ---- Board Event. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier ) - self:F() - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - self:Load( CargoCarrier ) - end - -end - -end - -do -- AI_CARGO_PACKAGE - - --- @type AI_CARGO_PACKAGE - -- @extends #AI_CARGO_REPRESENTABLE - AI_CARGO_PACKAGE = { - ClassName = "AI_CARGO_PACKAGE" - } - ---- AI_CARGO_PACKAGE Constructor. --- @param #AI_CARGO_PACKAGE self --- @param Wrapper.Unit#UNIT CargoCarrier The UNIT carrying the package. --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_PACKAGE -function AI_CARGO_PACKAGE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_PACKAGE - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoCarrier ) - self.CargoCarrier = CargoCarrier - - return self -end - ---- Board Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number Angle -function AI_CARGO_PACKAGE:onafterOnBoard( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. - if not self.CargoInAir then - - local Points = {} - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:Boarded( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #AI_CARGO_PACKAGE self --- @param Wrapper.Unit#UNIT CargoCarrier --- @return #boolean -function AI_CARGO_PACKAGE:IsNear( CargoCarrier ) - self:F() - - local CargoCarrierPoint = CargoCarrier:GetPointVec2() - - local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - ---- Boarded Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_PACKAGE:onafterOnBoarded( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:__Load( 1, CargoCarrier, Speed, LoadDistance, Angle ) - else - self:__Boarded( 1, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - end -end - ---- UnBoard Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param #number Speed --- @param #number UnLoadDistance --- @param #number UnBoardDistance --- @param #number Radius --- @param #number Angle -function AI_CARGO_PACKAGE:onafterUnBoard( From, Event, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) - - local Points = {} - - local StartPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = CargoCarrier:TaskRoute( Points ) - CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:__UnBoarded( 1 , CargoCarrier, Speed ) - -end - ---- UnBoarded Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_PACKAGE:onafterUnBoarded( From, Event, To, CargoCarrier, Speed ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:__UnLoad( 1, CargoCarrier, Speed ) - else - self:__UnBoarded( 1, CargoCarrier, Speed ) - end -end - ---- Load Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number LoadDistance --- @param #number Angle -function AI_CARGO_PACKAGE:onafterLoad( From, Event, To, CargoCarrier, Speed, LoadDistance, Angle ) - self:F() - - self.CargoCarrier = CargoCarrier - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - ---- UnLoad Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param #number Distance --- @param #number Angle -function AI_CARGO_PACKAGE:onafterUnLoad( From, Event, To, CargoCarrier, Speed, Distance, Angle ) - self:F() - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - self.CargoCarrier = CargoCarrier - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - - -end - -do -- AI_CARGO_GROUP - - --- @type AI_CARGO_GROUP - -- @extends AI.AI_Cargo#AI_CARGO - -- @field Set#SET_BASE CargoSet A set of cargo objects. - -- @field #string Name A string defining the name of the cargo group. The name is the unique identifier of the cargo. - AI_CARGO_GROUP = { - ClassName = "AI_CARGO_GROUP", - } - ---- AI_CARGO_GROUP constructor. --- @param #AI_CARGO_GROUP self --- @param Core.Set#Set_BASE CargoSet --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_GROUP -function AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, 0, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUP - self:F( { Type, Name, ReportRadius, NearRadius } ) - - self.CargoSet = CargoSet - - - return self -end - -end -- AI_CARGO_GROUP - -do -- AI_CARGO_GROUPED - - --- @type AI_CARGO_GROUPED - -- @extends AI.AI_Cargo#AI_CARGO_GROUP - AI_CARGO_GROUPED = { - ClassName = "AI_CARGO_GROUPED", - } - ---- AI_CARGO_GROUPED constructor. --- @param #AI_CARGO_GROUPED self --- @param Core.Set#Set_BASE CargoSet --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_GROUPED -function AI_CARGO_GROUPED:New( CargoSet, Type, Name, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUPED - self:F( { Type, Name, ReportRadius, NearRadius } ) - - return self -end - ---- Enter Boarding State. --- @param #AI_CARGO_GROUPED self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onenterBoarding( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - if From == "UnLoaded" then - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - Cargo:__Board( 1, CargoCarrier ) - end - ) - - self:__Boarding( 1, CargoCarrier ) - end - -end - ---- Enter Loaded State. --- @param #AI_CARGO_GROUPED self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onenterLoaded( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - if From == "UnLoaded" then - -- For each Cargo object within the AI_CARGO_GROUPED, load each cargo to the CargoCarrier. - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - Cargo:Load( CargoCarrier ) - end - end -end - ---- Leave Boarding State. --- @param #AI_CARGO_GROUPED self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onleaveBoarding( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - local Boarded = true - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - self:T( Cargo.current ) - if not Cargo:is( "Loaded" ) then - Boarded = false - end - end - - if not Boarded then - self:__Boarding( 1, CargoCarrier ) - else - self:__Load( 1, CargoCarrier ) - end - return Boarded -end - ---- Enter UnBoarding State. --- @param #AI_CARGO_GROUPED self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onenterUnBoarding( From, Event, To, ToPointVec2 ) - self:F() - - local Timer = 1 - - if From == "Loaded" then - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - Cargo:__UnBoard( Timer, ToPointVec2 ) - Timer = Timer + 10 - end - ) - - self:__UnBoarding( 1, ToPointVec2 ) - end - -end - ---- Leave UnBoarding State. --- @param #AI_CARGO_GROUPED self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onleaveUnBoarding( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - local UnBoarded = true - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - self:T( Cargo.current ) - if not Cargo:is( "UnLoaded" ) then - UnBoarded = false - end - end - - if UnBoarded then - return true - else - self:__UnBoarding( 1, ToPointVec2 ) - end - - return false - end - -end - ---- UnBoard Event. --- @param #AI_CARGO_GROUPED self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onafterUnBoarding( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - self:__UnLoad( 1, ToPointVec2 ) -end - - - ---- Enter UnLoaded State. --- @param #AI_CARGO_GROUPED self --- @param Core.Point#POINT_VEC2 --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onenterUnLoaded( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - if From == "Loaded" then - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - Cargo:UnLoad( ToPointVec2 ) - end - ) - - end - -end - -end -- AI_CARGO_GROUPED - - - ---- (SP) (MP) (FSM) Accept or reject process for player (task) assignments. --- --- === --- --- # @{#ACT_ASSIGN} FSM template class, extends @{Fsm#FSM_PROCESS} --- --- ## ACT_ASSIGN state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ASSIGN **Events**: --- --- These are the events defined in this class: --- --- * **Start**: Start the tasking acceptance process. --- * **Assign**: Assign the task. --- * **Reject**: Reject the task.. --- --- ### ACT_ASSIGN **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ASSIGN **States**: --- --- * **UnAssigned**: The player has not accepted the task. --- * **Assigned (*)**: The player has accepted the task. --- * **Rejected (*)**: The player has not accepted the task. --- * **Waiting**: The process is awaiting player feedback. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ASSIGN state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} --- --- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task. --- --- ## 1.1) ACT_ASSIGN_ACCEPT constructor: --- --- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object. --- --- === --- --- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} --- --- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option. --- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. --- The assignment type also allows to reject the task. --- --- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor: --- ----------------------------------------- --- --- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object. --- --- === --- --- @module Assign - - -do -- ACT_ASSIGN - - --- ACT_ASSIGN class - -- @type ACT_ASSIGN - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends Core.Fsm#FSM_PROCESS - ACT_ASSIGN = { - ClassName = "ACT_ASSIGN", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #ACT_ASSIGN self - -- @return #ACT_ASSIGN The task acceptance process. - function ACT_ASSIGN:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "UnAssigned", "Start", "Waiting" ) - self:AddTransition( "Waiting", "Assign", "Assigned" ) - self:AddTransition( "Waiting", "Reject", "Rejected" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Assigned" ) - self:AddEndState( "Rejected" ) - self:AddEndState( "Failed" ) - - self:SetStartState( "UnAssigned" ) - - return self - end - -end -- ACT_ASSIGN - - - -do -- ACT_ASSIGN_ACCEPT - - --- ACT_ASSIGN_ACCEPT class - -- @type ACT_ASSIGN_ACCEPT - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIGN - ACT_ASSIGN_ACCEPT = { - ClassName = "ACT_ASSIGN_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #ACT_ASSIGN_ACCEPT self - -- @param #string TaskBriefing - function ACT_ASSIGN_ACCEPT:New( TaskBriefing ) - - local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT - - self.TaskBriefing = TaskBriefing - - return self - end - - function ACT_ASSIGN_ACCEPT:Init( FsmAssign ) - - self.TaskBriefing = FsmAssign.TaskBriefing - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit, From, Event, To } ) - - self:__Assign( 1 ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, From, Event, To ) - env.info( "in here" ) - self:E( { ProcessUnit, From, Event, To } ) - - local ProcessGroup = ProcessUnit:GetGroup() - - self:Message( "You are assigned to the task " .. self.Task:GetName() ) - - self.Task:Assign( ProcessUnit, self.Task ) - end - -end -- ACT_ASSIGN_ACCEPT - - -do -- ACT_ASSIGN_MENU_ACCEPT - - --- ACT_ASSIGN_MENU_ACCEPT class - -- @type ACT_ASSIGN_MENU_ACCEPT - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIGN - ACT_ASSIGN_MENU_ACCEPT = { - ClassName = "ACT_ASSIGN_MENU_ACCEPT", - } - - --- Init. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskName - -- @param #string TaskBriefing - -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:New( TaskName, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT - - self.TaskName = TaskName - self.TaskBriefing = TaskBriefing - - return self - end - - function ACT_ASSIGN_MENU_ACCEPT:Init( FsmAssign ) - - self.TaskName = FsmAssign.TaskName - self.TaskBriefing = FsmAssign.TaskBriefing - end - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskName - -- @param #string TaskBriefing - -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:Init( TaskName, TaskBriefing ) - - self.TaskBriefing = TaskBriefing - self.TaskName = TaskName - - return self - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit, From, Event, To } ) - - self:Message( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled." ) - - local ProcessGroup = ProcessUnit:GetGroup() - - self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.TaskName .. " acceptance" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.TaskName, self.Menu, self.MenuAssign, self ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.TaskName, self.Menu, self.MenuReject, self ) - end - - --- Menu function. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuAssign() - self:E( ) - - self:__Assign( 1 ) - end - - --- Menu function. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuReject() - self:E( ) - - self:__Reject( 1 ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit.UnitNameFrom, Event, To } ) - - self.Menu:Remove() - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit.UnitName, From, Event, To } ) - - self.Menu:Remove() - --TODO: need to resolve this problem ... it has to do with the events ... - --self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event - ProcessUnit:Destroy() - end - -end -- ACT_ASSIGN_MENU_ACCEPT ---- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. --- --- === --- --- # @{#ACT_ROUTE} FSM class, extends @{Fsm#FSM_PROCESS} --- --- ## ACT_ROUTE state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ROUTE **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. The process will go into the Report state. --- * **Report**: The process is reporting to the player the route to be followed. --- * **Route**: The process is routing the controllable. --- * **Pause**: The process is pausing the route of the controllable. --- * **Arrive**: The controllable has arrived at a route point. --- * **More**: There are more route points that need to be followed. The process will go back into the Report state. --- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state. --- --- ### ACT_ROUTE **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ROUTE **States**: --- --- * **None**: The controllable did not receive route commands. --- * **Arrived (*)**: The controllable has arrived at a route point. --- * **Aborted (*)**: The controllable has aborted the route path. --- * **Routing**: The controllable is understay to the route point. --- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around. --- * **Success (*)**: All route points were reached. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ROUTE state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Fsm.Route#ACT_ROUTE} --- --- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Controllable} player @{Unit} to a @{Zone}. --- The player receives on perioding times messages with the coordinates of the route to follow. --- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended. --- --- # 1.1) ACT_ROUTE_ZONE constructor: --- --- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object. --- --- === --- --- @module Route - - -do -- ACT_ROUTE - - --- ACT_ROUTE class - -- @type ACT_ROUTE - -- @field Tasking.Task#TASK TASK - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE Zone - -- @extends Core.Fsm#FSM_PROCESS - ACT_ROUTE = { - ClassName = "ACT_ROUTE", - } - - - --- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE. - -- @param #ACT_ROUTE self - -- @return #ACT_ROUTE self - function ACT_ROUTE:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "None", "Start", "Routing" ) - self:AddTransition( "*", "Report", "Reporting" ) - self:AddTransition( "*", "Route", "Routing" ) - self:AddTransition( "Routing", "Pause", "Pausing" ) - self:AddTransition( "*", "Abort", "Aborted" ) - self:AddTransition( "Routing", "Arrive", "Arrived" ) - self:AddTransition( "Arrived", "Success", "Success" ) - self:AddTransition( "*", "Fail", "Failed" ) - self:AddTransition( "", "", "" ) - self:AddTransition( "", "", "" ) - - self:AddEndState( "Arrived" ) - self:AddEndState( "Failed" ) - - self:SetStartState( "None" ) - - return self - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE:onafterStart( ProcessUnit, From, Event, To ) - - - self:__Route( 1 ) - end - - --- Check if the controllable has arrived. - -- @param #ACT_ROUTE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @return #boolean - function ACT_ROUTE:onfuncHasArrived( ProcessUnit ) - return false - end - - --- StateMachine callback function - -- @param #ACT_ROUTE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To ) - self:F( { "BeforeRoute 1", self.DisplayCount, self.DisplayInterval } ) - - if ProcessUnit:IsAlive() then - self:F( "BeforeRoute 2" ) - local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic - if self.DisplayCount >= self.DisplayInterval then - self:T( { HasArrived = HasArrived } ) - if not HasArrived then - self:Report() - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - self:T( { DisplayCount = self.DisplayCount } ) - - if HasArrived then - self:__Arrive( 1 ) - else - self:__Route( 1 ) - end - - return HasArrived -- if false, then the event will not be executed... - end - - return false - - end - -end -- ACT_ROUTE - - -do -- ACT_ROUTE_POINT - - --- ACT_ROUTE_POINT class - -- @type ACT_ROUTE_POINT - -- @field Tasking.Task#TASK TASK - -- @extends #ACT_ROUTE - ACT_ROUTE_POINT = { - ClassName = "ACT_ROUTE_POINT", - } - - - --- Creates a new routing state machine. - -- The task will route a controllable to a PointVec2 until the controllable is within the Range. - -- @param #ACT_ROUTE_POINT self - -- @param Core.Point#POINT_VEC2 The PointVec2 to Target. - -- @param #number Range The Distance to Target. - -- @param Core.Zone#ZONE_BASE Zone - function ACT_ROUTE_POINT:New( PointVec2, Range ) - local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_POINT - - self.PointVec2 = PointVec2 - self.Range = Range or 0 - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - - return self - end - - function ACT_ROUTE_POINT:Init( FsmRoute ) - - self.PointVec2 = FsmRoute.PointVec2 - self.Range = FsmRoute.Range or 0 - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - end - - --- Set PointVec2 - -- @param #ACT_ROUTE_POINT self - -- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 to route to. - function ACT_ROUTE_POINT:SetPointVec2( PointVec2 ) - self:F2( { PointVec2 } ) - self.PointVec2 = PointVec2 - end - - --- Get PointVec2 - -- @param #ACT_ROUTE_POINT self - -- @return Core.Point#POINT_VEC2 PointVec2 The PointVec2 to route to. - function ACT_ROUTE_POINT:GetPointVec2() - self:F2( { self.PointVec2 } ) - return self.PointVec2 - end - - --- Set Range around PointVec2 - -- @param #ACT_ROUTE_POINT self - -- @param #number Range The Range to consider the arrival. Default is 10000 meters. - function ACT_ROUTE_POINT:SetRange( Range ) - self:F2( { self.Range } ) - self.Range = Range or 10000 - end - - --- Get Range around PointVec2 - -- @param #ACT_ROUTE_POINT self - -- @return #number The Range to consider the arrival. Default is 10000 meters. - function ACT_ROUTE_POINT:GetRange() - return self.Range - end - - --- Method override to check if the controllable has arrived. - -- @param #ACT_ROUTE_POINT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @return #boolean - function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit ) - - local Distance = self.PointVec2:Get2DDistance( ProcessUnit:GetPointVec2() ) - - if Distance <= self.Range then - local RouteText = "You have arrived." - self:Message( RouteText ) - return true - end - - return false - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE_POINT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE_POINT:onenterReporting( ProcessUnit, From, Event, To ) - - local TaskUnitPointVec2 = ProcessUnit:GetPointVec2() - local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( self.PointVec2 ) .. " km." - self:Message( RouteText ) - end - -end -- ACT_ROUTE_POINT - - -do -- ACT_ROUTE_ZONE - - --- ACT_ROUTE_ZONE class - -- @type ACT_ROUTE_ZONE - -- @field Tasking.Task#TASK TASK - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE Zone - -- @extends #ACT_ROUTE - ACT_ROUTE_ZONE = { - ClassName = "ACT_ROUTE_ZONE", - } - - - --- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE. - -- @param #ACT_ROUTE_ZONE self - -- @param Core.Zone#ZONE_BASE Zone - function ACT_ROUTE_ZONE:New( Zone ) - local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE - - self.Zone = Zone - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - - return self - end - - function ACT_ROUTE_ZONE:Init( FsmRoute ) - - self.Zone = FsmRoute.Zone - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - end - - --- Set Zone - -- @param #ACT_ROUTE_ZONE self - -- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to. - function ACT_ROUTE_ZONE:SetZone( Zone ) - self.Zone = Zone - end - - --- Get Zone - -- @param #ACT_ROUTE_ZONE self - -- @return Core.Zone#ZONE_BASE Zone The Zone object where to route to. - function ACT_ROUTE_ZONE:GetZone() - return self.Zone - end - - --- Method override to check if the controllable has arrived. - -- @param #ACT_ROUTE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @return #boolean - function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit ) - - if ProcessUnit:IsInZone( self.Zone ) then - local RouteText = "You have arrived within the zone." - self:Message( RouteText ) - end - - return ProcessUnit:IsInZone( self.Zone ) - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE_ZONE:onenterReporting( ProcessUnit, From, Event, To ) - - local ZoneVec2 = self.Zone:GetVec2() - local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) - local TaskUnitVec2 = ProcessUnit:GetVec2() - local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) - local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km." - self:Message( RouteText ) - end - -end -- ACT_ROUTE_ZONE ---- (SP) (MP) (FSM) Account for (Detect, count and report) DCS events occuring on DCS objects (units). --- --- === --- --- # @{#ACT_ACCOUNT} FSM class, extends @{Fsm#FSM_PROCESS} --- --- ## ACT_ACCOUNT state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ACCOUNT **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. The process will go into the Report state. --- * **Event**: A relevant event has occured that needs to be accounted for. The process will go into the Account state. --- * **Report**: The process is reporting to the player the accounting status of the DCS events. --- * **More**: There are more DCS events that need to be accounted for. The process will go back into the Report state. --- * **NoMore**: There are no more DCS events that need to be accounted for. The process will go into the Success state. --- --- ### ACT_ACCOUNT **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ACCOUNT **States**: --- --- * **Assigned**: The player is assigned to the task. This is the initialization state for the process. --- * **Waiting**: the process is waiting for a DCS event to occur within the simulator. This state is set automatically. --- * **Report**: The process is Reporting to the players in the group of the unit. This state is set automatically every 30 seconds. --- * **Account**: The relevant DCS event has occurred, and is accounted for. --- * **Success (*)**: All DCS events were accounted for. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ACCOUNT state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- # 1) @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Fsm.Account#ACT_ACCOUNT} --- --- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units. --- The process is given a @{Set} of units that will be tracked upon successful destruction. --- The process will end after each target has been successfully destroyed. --- Each successful dead will trigger an Account state transition that can be scored, modified or administered. --- --- --- ## ACT_ACCOUNT_DEADS constructor: --- --- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object. --- --- === --- --- @module Account - - -do -- ACT_ACCOUNT - - --- ACT_ACCOUNT class - -- @type ACT_ACCOUNT - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Core.Fsm#FSM_PROCESS - ACT_ACCOUNT = { - ClassName = "ACT_ACCOUNT", - TargetSetUnit = nil, - } - - --- Creates a new DESTROY process. - -- @param #ACT_ACCOUNT self - -- @return #ACT_ACCOUNT - function ACT_ACCOUNT:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "Assigned", "Start", "Waiting") - self:AddTransition( "*", "Wait", "Waiting") - self:AddTransition( "*", "Report", "Report") - self:AddTransition( "*", "Event", "Account") - self:AddTransition( "Account", "More", "Wait") - self:AddTransition( "Account", "NoMore", "Accounted") - self:AddTransition( "*", "Fail", "Failed") - - self:AddEndState( "Accounted" ) - self:AddEndState( "Failed" ) - - self:SetStartState( "Assigned" ) - - return self - end - - --- Process Events - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onafterStart( ProcessUnit, From, Event, To ) - - self:HandleEvent( EVENTS.Dead, self.onfuncEventDead ) - - self:__Wait( 1 ) - end - - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onenterWaiting( ProcessUnit, From, Event, To ) - - if self.DisplayCount >= self.DisplayInterval then - self:Report() - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event ) - - self:__NoMore( 1 ) - end - -end -- ACT_ACCOUNT - -do -- ACT_ACCOUNT_DEADS - - --- ACT_ACCOUNT_DEADS class - -- @type ACT_ACCOUNT_DEADS - -- @field Set#SET_UNIT TargetSetUnit - -- @extends #ACT_ACCOUNT - ACT_ACCOUNT_DEADS = { - ClassName = "ACT_ACCOUNT_DEADS", - TargetSetUnit = nil, - } - - - --- Creates a new DESTROY process. - -- @param #ACT_ACCOUNT_DEADS self - -- @param Set#SET_UNIT TargetSetUnit - -- @param #string TaskName - function ACT_ACCOUNT_DEADS:New( TargetSetUnit, TaskName ) - -- Inherits from BASE - local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS - - self.TargetSetUnit = TargetSetUnit - self.TaskName = TaskName - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - return self - end - - function ACT_ACCOUNT_DEADS:Init( FsmAccount ) - - self.TargetSetUnit = FsmAccount.TargetSetUnit - self.TaskName = FsmAccount.TaskName - end - - --- Process Events - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit, From, Event, To } ) - - self:Message( "Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." ) - end - - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT_DEADS:onenterAccount( ProcessUnit, From, Event, To, EventData ) - self:T( { ProcessUnit, EventData, From, Event, To } ) - - self:T({self.Controllable}) - - self.TargetSetUnit:Flush() - - if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then - local TaskGroup = ProcessUnit:GetGroup() - self:Message( "You hit a target. Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." ) - end - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, From, Event, To, EventData ) - - if self.TargetSetUnit:Count() > 1 then - self:__More( 1 ) - else - self:__NoMore( 1 ) - end - end - - --- DCS Events - - --- @param #ACT_ACCOUNT_DEADS self - -- @param Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) - self:T( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - self:Event( EventData ) - end - end - -end -- ACT_ACCOUNT DEADS ---- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. --- --- === --- --- # @{#ACT_ASSIST} FSM class, extends @{Fsm#FSM_PROCESS} --- --- ## ACT_ASSIST state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ASSIST **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. --- * **Next**: The process is smoking the targets in the given zone. --- --- ### ACT_ASSIST **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ASSIST **States**: --- --- * **None**: The controllable did not receive route commands. --- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone. --- * **Smoking (*)**: The process is smoking the targets in the zone. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ASSIST state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Fsm.Route#ACT_ASSIST} --- --- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}. --- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour. --- At random intervals, a new target is smoked. --- --- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor: --- --- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object. --- --- === --- --- @module Smoke - -do -- ACT_ASSIST - - --- ACT_ASSIST class - -- @type ACT_ASSIST - -- @extends Core.Fsm#FSM_PROCESS - ACT_ASSIST = { - ClassName = "ACT_ASSIST", - } - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST self - -- @return #ACT_ASSIST - function ACT_ASSIST:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "None", "Start", "AwaitSmoke" ) - self:AddTransition( "AwaitSmoke", "Next", "Smoking" ) - self:AddTransition( "Smoking", "Next", "AwaitSmoke" ) - self:AddTransition( "*", "Stop", "Success" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Failed" ) - self:AddEndState( "Success" ) - - self:SetStartState( "None" ) - - return self - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ASSIST self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST:onafterStart( ProcessUnit, From, Event, To ) - - local ProcessGroup = ProcessUnit:GetGroup() - local MissionMenu = self:GetMission():GetMenu( ProcessGroup ) - - local function MenuSmoke( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local SmokeColor = MenuParam.SmokeColor - self.SmokeColor = SmokeColor - self:__Next( 1 ) - end - - self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) - self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) - self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) - self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) - self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) - self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIST self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST:onafterStop( ProcessUnit, From, Event, To ) - - self.Menu:Remove() -- When stopped, remove the menus - end - -end - -do -- ACT_ASSIST_SMOKE_TARGETS_ZONE - - --- ACT_ASSIST_SMOKE_TARGETS_ZONE class - -- @type ACT_ASSIST_SMOKE_TARGETS_ZONE - -- @field Set#SET_UNIT TargetSetUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIST - ACT_ASSIST_SMOKE_TARGETS_ZONE = { - ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE", - } - --- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor() --- self:E("_Destructor") --- --- self.Menu:Remove() --- self:EventRemoveAll() --- end - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Set#SET_UNIT TargetSetUnit - -- @param Core.Zone#ZONE_BASE TargetZone - function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone ) - local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - return self - end - - function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke ) - - self.TargetSetUnit = FsmSmoke.TargetSetUnit - self.TargetZone = FsmSmoke.TargetZone - end - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Set#SET_UNIT TargetSetUnit - -- @param Core.Zone#ZONE_BASE TargetZone - -- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self - function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone ) - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - return self - end - - --- StateMachine callback function - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To ) - - self.TargetSetUnit:ForEachUnit( - --- @param Wrapper.Unit#UNIT SmokeUnit - function( SmokeUnit ) - if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then - SCHEDULER:New( self, - function() - if SmokeUnit:IsAlive() then - SmokeUnit:Smoke( self.SmokeColor, 150 ) - end - end, {}, math.random( 10, 60 ) - ) - end - end - ) - - end - -end--- A COMMANDCENTER is the owner of multiple missions within MOOSE. --- A COMMANDCENTER governs multiple missions, the tasking and the reporting. --- @module CommandCenter - - - ---- The REPORT class --- @type REPORT --- @extends Core.Base#BASE -REPORT = { - ClassName = "REPORT", -} - ---- Create a new REPORT. --- @param #REPORT self --- @param #string Title --- @return #REPORT -function REPORT:New( Title ) - - local self = BASE:Inherit( self, BASE:New() ) - - self.Report = {} - if Title then - self.Report[#self.Report+1] = Title - end - - return self -end - ---- Add a new line to a REPORT. --- @param #REPORT self --- @param #string Text --- @return #REPORT -function REPORT:Add( Text ) - self.Report[#self.Report+1] = Text - return self.Report[#self.Report] -end - -function REPORT:Text() - return table.concat( self.Report, "\n" ) -end - ---- The COMMANDCENTER class --- @type COMMANDCENTER --- @field Wrapper.Group#GROUP HQ --- @field Dcs.DCSCoalitionWrapper.Object#coalition CommandCenterCoalition --- @list Missions --- @extends Core.Base#BASE -COMMANDCENTER = { - ClassName = "COMMANDCENTER", - CommandCenterName = "", - CommandCenterCoalition = nil, - CommandCenterPositionable = nil, - Name = "", -} ---- The constructor takes an IDENTIFIABLE as the HQ command center. --- @param #COMMANDCENTER self --- @param Wrapper.Positionable#POSITIONABLE CommandCenterPositionable --- @param #string CommandCenterName --- @return #COMMANDCENTER -function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) - - local self = BASE:Inherit( self, BASE:New() ) - - self.CommandCenterPositionable = CommandCenterPositionable - self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName() - self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition() - - self.Missions = {} - - self:HandleEvent( EVENTS.Birth, - --- @param #COMMANDCENTER self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - if EventData.IniObjectCategory == 1 then - local EventGroup = GROUP:Find( EventData.IniDCSGroup ) - if EventGroup and self:HasGroup( EventGroup ) then - local MenuReporting = MENU_GROUP:New( EventGroup, "Reporting", self.CommandCenterMenu ) - local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Summary Report", MenuReporting, self.ReportSummary, self, EventGroup ) - local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Details Report", MenuReporting, self.ReportDetails, self, EventGroup ) - self:ReportSummary( EventGroup ) - end - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled! - Mission:JoinUnit( PlayerUnit, PlayerGroup ) - Mission:ReportDetails() - end - end - - end - ) - - -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do. - -- For these elements, it will= - -- - Set the correct menu. - -- - Assign the PlayerUnit to the Task if required. - -- - Send a message to the other players in the group that this player has joined. - self:HandleEvent( EVENTS.PlayerEnterUnit, - --- @param #COMMANDCENTER self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled! - Mission:JoinUnit( PlayerUnit, PlayerGroup ) - Mission:ReportDetails() - end - end - ) - - -- Handle when a player leaves a slot and goes back to spectators ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.PlayerLeaveUnit, - --- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:AbortUnit( PlayerUnit ) - end - end - ) - - -- Handle when a player leaves a slot and goes back to spectators ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.Crash, - --- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - Mission:CrashUnit( PlayerUnit ) - end - end - ) - - return self -end - ---- Gets the name of the HQ command center. --- @param #COMMANDCENTER self --- @return #string -function COMMANDCENTER:GetName() - - return self.CommandCenterName -end - ---- Gets the POSITIONABLE of the HQ command center. --- @param #COMMANDCENTER self --- @return Wrapper.Positionable#POSITIONABLE -function COMMANDCENTER:GetPositionable() - return self.CommandCenterPositionable -end - ---- Get the Missions governed by the HQ command center. --- @param #COMMANDCENTER self --- @return #list -function COMMANDCENTER:GetMissions() - - return self.Missions -end - ---- Add a MISSION to be governed by the HQ command center. --- @param #COMMANDCENTER self --- @param Tasking.Mission#MISSION Mission --- @return Tasking.Mission#MISSION -function COMMANDCENTER:AddMission( Mission ) - - self.Missions[Mission] = Mission - - return Mission -end - ---- Removes a MISSION to be governed by the HQ command center. --- The given Mission is not nilified. --- @param #COMMANDCENTER self --- @param Tasking.Mission#MISSION Mission --- @return Tasking.Mission#MISSION -function COMMANDCENTER:RemoveMission( Mission ) - - self.Missions[Mission] = nil - - return Mission -end - ---- Sets the menu structure of the Missions governed by the HQ command center. --- @param #COMMANDCENTER self -function COMMANDCENTER:SetMenu() - self:F() - - self.CommandCenterMenu = self.CommandCenterMenu or MENU_COALITION:New( self.CommandCenterCoalition, "Command Center (" .. self:GetName() .. ")" ) - - local MenuTime = timer.getTime() - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:SetMenu( MenuTime ) - end - - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:RemoveMenu( MenuTime ) - end - -end - ---- Gets the commandcenter menu structure governed by the HQ command center. --- @param #COMMANDCENTER self --- @return Core.Menu#MENU_COALITION -function COMMANDCENTER:GetMenu() - self:F() - return self.CommandCenterMenu -end - ---- Checks of the COMMANDCENTER has a GROUP. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP --- @return #boolean -function COMMANDCENTER:HasGroup( MissionGroup ) - - local Has = false - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:HasGroup( MissionGroup ) then - Has = true - break - end - end - - return Has -end - ---- Send a CC message to the coalition of the CC. --- @param #COMMANDCENTER self -function COMMANDCENTER:MessageToAll( Message ) - - self:GetPositionable():MessageToAll( Message, 20, self:GetName() ) - -end - ---- Send a CC message to a GROUP. --- @param #COMMANDCENTER self --- @param #string Message --- @param Wrapper.Group#GROUP TaskGroup --- @param #sring Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown. -function COMMANDCENTER:MessageToGroup( Message, TaskGroup, Name ) - - local Prefix = "@ Group" - Prefix = Prefix .. ( Name and " (" .. Name .. "): " or '' ) - Message = Prefix .. Message - self:GetPositionable():MessageToGroup( Message , 20, TaskGroup, self:GetName() ) - -end - ---- Send a CC message to the coalition of the CC. --- @param #COMMANDCENTER self -function COMMANDCENTER:MessageToCoalition( Message ) - - local CCCoalition = self:GetPositionable():GetCoalition() - --TODO: Fix coalition bug! - self:GetPositionable():MessageToCoalition( Message, 20, CCCoalition, self:GetName() ) - -end - - ---- Report the status of all MISSIONs to a GROUP. --- Each Mission is listed, with an indication how many Tasks are still to be completed. --- @param #COMMANDCENTER self -function COMMANDCENTER:ReportSummary( ReportGroup ) - self:E( ReportGroup ) - - local Report = REPORT:New() - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportOverview() ) - end - - self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) - -end - ---- Report the status of a Task to a Group. --- Report the details of a Mission, listing the Mission, and all the Task details. --- @param #COMMANDCENTER self -function COMMANDCENTER:ReportDetails( ReportGroup, Task ) - self:E( ReportGroup ) - - local Report = REPORT:New() - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportDetails() ) - end - - self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) -end - ---- A MISSION is the main owner of a Mission orchestration within MOOSE . The Mission framework orchestrates @{CLIENT}s, @{TASK}s, @{STAGE}s etc. --- A @{CLIENT} needs to be registered within the @{MISSION} through the function @{AddClient}. A @{TASK} needs to be registered within the @{MISSION} through the function @{AddTask}. --- @module Mission - ---- The MISSION class --- @type MISSION --- @field #MISSION.Clients _Clients --- @field Core.Menu#MENU_COALITION MissionMenu --- @field #string MissionBriefing --- @extends Core.Fsm#FSM -MISSION = { - ClassName = "MISSION", - Name = "", - MissionStatus = "PENDING", -} - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param #MISSION self --- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter --- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. --- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param Dcs.DCSCoalitionWrapper.Object#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... --- @return #MISSION self -function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) - - local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM - - self:SetStartState( "Idle" ) - - self:AddTransition( "Idle", "Start", "Ongoing" ) - - --- OnLeave Transition Handler for State Idle. - -- @function [parent=#MISSION] OnLeaveIdle - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Idle. - -- @function [parent=#MISSION] OnEnterIdle - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnLeave Transition Handler for State Ongoing. - -- @function [parent=#MISSION] OnLeaveOngoing - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Ongoing. - -- @function [parent=#MISSION] OnEnterOngoing - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Start. - -- @function [parent=#MISSION] OnBeforeStart - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Start. - -- @function [parent=#MISSION] OnAfterStart - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Start. - -- @function [parent=#MISSION] Start - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Start. - -- @function [parent=#MISSION] __Start - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Ongoing", "Stop", "Idle" ) - - --- OnLeave Transition Handler for State Idle. - -- @function [parent=#MISSION] OnLeaveIdle - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Idle. - -- @function [parent=#MISSION] OnEnterIdle - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Stop. - -- @function [parent=#MISSION] OnBeforeStop - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Stop. - -- @function [parent=#MISSION] OnAfterStop - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Stop. - -- @function [parent=#MISSION] Stop - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Stop. - -- @function [parent=#MISSION] __Stop - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Ongoing", "Complete", "Completed" ) - - --- OnLeave Transition Handler for State Completed. - -- @function [parent=#MISSION] OnLeaveCompleted - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Completed. - -- @function [parent=#MISSION] OnEnterCompleted - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Complete. - -- @function [parent=#MISSION] OnBeforeComplete - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Complete. - -- @function [parent=#MISSION] OnAfterComplete - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Complete. - -- @function [parent=#MISSION] Complete - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Complete. - -- @function [parent=#MISSION] __Complete - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Fail", "Failed" ) - - --- OnLeave Transition Handler for State Failed. - -- @function [parent=#MISSION] OnLeaveFailed - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Failed. - -- @function [parent=#MISSION] OnEnterFailed - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Fail. - -- @function [parent=#MISSION] OnBeforeFail - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fail. - -- @function [parent=#MISSION] OnAfterFail - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fail. - -- @function [parent=#MISSION] Fail - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Fail. - -- @function [parent=#MISSION] __Fail - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - - self.CommandCenter = CommandCenter - CommandCenter:AddMission( self ) - - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - - self.Tasks = {} - - -- Private implementations - - - - return self -end - --- FSM function for a MISSION --- @param #MISSION self --- @param #string From --- @param #string Event --- @param #string To -function MISSION:onbeforeComplete( From, Event, To ) - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if not Task:IsStateSuccess() and not Task:IsStateFailed() and not Task:IsStateAborted() and not Task:IsStateCancelled() then - return false -- Mission cannot be completed. Other Tasks are still active. - end - end - return true -- Allow Mission completion. -end - --- FSM function for a MISSION --- @param #MISSION self --- @param #string From --- @param #string Event --- @param #string To -function MISSION:onenterCompleted( From, Event, To ) - - self:GetCommandCenter():MessageToCoalition( "Mission " .. self:GetName() .. " has been completed! Good job guys!" ) -end - ---- Gets the mission name. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetName() - return self.Name -end - ---- Add a Unit to join the Mission. --- For each Task within the Mission, the Unit is joined with the Task. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. --- @return #boolean true if Unit is part of a Task in the Mission. -function MISSION:JoinUnit( PlayerUnit, PlayerGroup ) - self:F( { PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) - - local PlayerUnitAdded = false - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:JoinUnit( PlayerUnit, PlayerGroup ) then - PlayerUnitAdded = true - end - end - - return PlayerUnitAdded -end - ---- Aborts a PlayerUnit from the Mission. --- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @return #boolean true if Unit is part of a Task in the Mission. -function MISSION:AbortUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - local PlayerUnitRemoved = false - - for TaskID, Task in pairs( self:GetTasks() ) do - if Task:AbortUnit( PlayerUnit ) then - PlayerUnitRemoved = true - end - end - - return PlayerUnitRemoved -end - ---- Handles a crash of a PlayerUnit from the Mission. --- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player crashing. --- @return #boolean true if Unit is part of a Task in the Mission. -function MISSION:CrashUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - local PlayerUnitRemoved = false - - for TaskID, Task in pairs( self:GetTasks() ) do - if Task:CrashUnit( PlayerUnit ) then - PlayerUnitRemoved = true - end - end - - return PlayerUnitRemoved -end - ---- Add a scoring to the mission. --- @param #MISSION self --- @return #MISSION self -function MISSION:AddScoring( Scoring ) - self.Scoring = Scoring - return self -end - ---- Get the scoring object of a mission. --- @param #MISSION self --- @return #SCORING Scoring -function MISSION:GetScoring() - return self.Scoring -end - ---- Get the groups for which TASKS are given in the mission --- @param #MISSION self --- @return Core.Set#SET_GROUP -function MISSION:GetGroups() - - local SetGroup = SET_GROUP:New() - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local GroupSet = Task:GetGroups() - GroupSet:ForEachGroup( - function( TaskGroup ) - SetGroup:Add( TaskGroup, TaskGroup ) - end - ) - end - - return SetGroup - -end - - ---- Sets the Planned Task menu. --- @param #MISSION self --- @param #number MenuTime -function MISSION:SetMenu( MenuTime ) - self:F() - - for _, TaskData in pairs( self:GetTasks() ) do - local Task = TaskData -- Tasking.Task#TASK - Task:SetMenu( MenuTime ) - end -end - ---- Removes the Planned Task menu. --- @param #MISSION self --- @param #number MenuTime -function MISSION:RemoveMenu( MenuTime ) - self:F() - - for _, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Task:RemoveMenu( MenuTime ) - end -end - - ---- Gets the COMMANDCENTER. --- @param #MISSION self --- @return Tasking.CommandCenter#COMMANDCENTER -function MISSION:GetCommandCenter() - return self.CommandCenter -end - - ---- Removes a Task menu. --- @param #MISSION self --- @param Tasking.Task#TASK Task --- @return #MISSION self -function MISSION:RemoveTaskMenu( Task ) - - Task:RemoveMenu() -end - - ---- Gets the mission menu for the coalition. --- @param #MISSION self --- @param Wrapper.Group#GROUP TaskGroup --- @return Core.Menu#MENU_COALITION self -function MISSION:GetMenu( TaskGroup ) - - local CommandCenter = self:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu() - - local MissionName = self:GetName() - local MissionMenu = CommandCenterMenu:GetMenu( MissionName ) - - return MissionMenu -end - - ---- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param #string TaskName The Name of the @{Task} within the @{Mission}. --- @return Tasking.Task#TASK The Task --- @return #nil Returns nil if no task was found. -function MISSION:GetTask( TaskName ) - self:F( { TaskName } ) - - return self.Tasks[TaskName] -end - - ---- Register a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Task} object. --- @return Tasking.Task#TASK The task added. -function MISSION:AddTask( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName] = Task - - self:GetCommandCenter():SetMenu() - - return Task -end - ---- Removes a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Task} object. --- @return #nil The cleaned Task reference. -function MISSION:RemoveTask( Task ) - - local TaskName = Task:GetTaskName() - - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - -- Ensure everything gets garbarge collected. - self.Tasks[TaskName] = nil - Task = nil - - collectgarbage() - - self:GetCommandCenter():SetMenu() - - return nil -end - ---- Return the next @{Task} ID to be completed within the @{Mission}. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Task} object. --- @return Tasking.Task#TASK The task added. -function MISSION:GetNextTaskID( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 - - return self.Tasks[TaskName].n -end - ---- Is the @{Mission} **Completed**. --- @param #MISSION self --- @return #boolean -function MISSION:IsCompleted() - return self:Is( "Completed" ) -end - ---- Is the @{Mission} **Idle**. --- @param #MISSION self --- @return #boolean -function MISSION:IsIdle() - return self:Is( "Idle" ) -end - ---- Is the @{Mission} **Ongoing**. --- @param #MISSION self --- @return #boolean -function MISSION:IsOngoing() - return self:Is( "Ongoing" ) -end - ---- Is the @{Mission} **Failed**. --- @param #MISSION self --- @return #boolean -function MISSION:IsFailed() - return self:Is( "Failed" ) -end - ---- Is the @{Mission} **Hold**. --- @param #MISSION self --- @return #boolean -function MISSION:IsHold() - return self:Is( "Hold" ) -end - ---- Validates if the Mission has a Group --- @param #MISSION --- @return #boolean true if the Mission has a Group. -function MISSION:HasGroup( TaskGroup ) - local Has = false - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:HasGroup( TaskGroup ) then - Has = true - break - end - end - - return Has -end - ---- Create a summary report of the Mission (one line). --- @param #MISSION self --- @return #string -function MISSION:ReportSummary() - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetName() - - -- Determine the status of the mission. - local Status = self:GetState() - - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:IsStateSuccess() or Task:IsStateFailed() then - else - TasksRemaining = TasksRemaining + 1 - end - end - - Report:Add( "Mission " .. Name .. " - " .. Status .. " - " .. TasksRemaining .. " tasks remaining." ) - - return Report:Text() -end - ---- Create a overview report of the Mission (multiple lines). --- @param #MISSION self --- @return #string -function MISSION:ReportOverview() - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetName() - - -- Determine the status of the mission. - local Status = self:GetState() - - Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) - - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Report:Add( "- " .. Task:ReportSummary() ) - end - - return Report:Text() -end - ---- Create a detailed report of the Mission, listing all the details of the Task. --- @param #MISSION self --- @return #string -function MISSION:ReportDetails() - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetName() - - -- Determine the status of the mission. - local Status = self:GetState() - - Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) - - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Report:Add( Task:ReportDetails() ) - end - - return Report:Text() -end - ---- Get all the TASKs from the Mission. This function is useful in GoalFunctions. --- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. --- @usage --- -- Get Tasks from the Mission. --- Tasks = Mission:GetTasks() --- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) -function MISSION:GetTasks() - self:F() - - return self.Tasks -end - - ---- This module contains the TASK class. --- --- 1) @{#TASK} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK.AddProcess}():Add a @{Process} to a task. --- * @{#TASK.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK.SetStateMachine}():Set a @{Fsm} to a task. --- * @{#TASK.RemoveStateMachine}():Remove @{Fsm} from a task. --- * @{#TASK.HasStateMachine}():Enquire if the task has a @{Fsm} --- * @{#TASK.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK}. --- * @{#TASK.UnAssignFromUnit}(): Unassign the task from a unit. --- * @{#TASK.SetTimeOut}(): Set timer in seconds before task gets cancelled if not assigned. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK class --- @type TASK --- @field Core.Scheduler#SCHEDULER TaskScheduler --- @field Tasking.Mission#MISSION Mission --- @field Core.Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @field Core.Fsm#FSM_PROCESS FsmTemplate --- @field Tasking.Mission#MISSION Mission --- @field Tasking.CommandCenter#COMMANDCENTER CommandCenter --- @extends Core.Fsm#FSM_TASK -TASK = { - ClassName = "TASK", - TaskScheduler = nil, - ProcessClasses = {}, -- The container of the Process classes that will be used to create and assign new processes for the task to ProcessUnits. - Processes = {}, -- The container of actual process objects instantiated and assigned to ProcessUnits. - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, - FsmTemplate = nil, - Mission = nil, - CommandCenter = nil, - TimeOut = 0, -} - ---- FSM PlayerAborted event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerAborted --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he went back to spectators or left the mission. --- @param #string PlayerName The name of the Player. - ---- FSM PlayerCrashed event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerCrashed --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he crashed in the mission. --- @param #string PlayerName The name of the Player. - ---- FSM PlayerDead event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerDead --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he died in the mission. --- @param #string PlayerName The name of the Player. - ---- FSM Fail synchronous event function for TASK. --- Use this event to Fail the Task. --- @function [parent=#TASK] Fail --- @param #TASK self - ---- FSM Fail asynchronous event function for TASK. --- Use this event to Fail the Task. --- @function [parent=#TASK] __Fail --- @param #TASK self - ---- FSM Abort synchronous event function for TASK. --- Use this event to Abort the Task. --- @function [parent=#TASK] Abort --- @param #TASK self - ---- FSM Abort asynchronous event function for TASK. --- Use this event to Abort the Task. --- @function [parent=#TASK] __Abort --- @param #TASK self - ---- FSM Success synchronous event function for TASK. --- Use this event to make the Task a Success. --- @function [parent=#TASK] Success --- @param #TASK self - ---- FSM Success asynchronous event function for TASK. --- Use this event to make the Task a Success. --- @function [parent=#TASK] __Success --- @param #TASK self - ---- FSM Cancel synchronous event function for TASK. --- Use this event to Cancel the Task. --- @function [parent=#TASK] Cancel --- @param #TASK self - ---- FSM Cancel asynchronous event function for TASK. --- Use this event to Cancel the Task. --- @function [parent=#TASK] __Cancel --- @param #TASK self - ---- FSM Replan synchronous event function for TASK. --- Use this event to Replan the Task. --- @function [parent=#TASK] Replan --- @param #TASK self - ---- FSM Replan asynchronous event function for TASK. --- Use this event to Replan the Task. --- @function [parent=#TASK] __Replan --- @param #TASK self - - ---- Instantiates a new TASK. Should never be used. Interface Class. --- @param #TASK self --- @param Tasking.Mission#MISSION Mission The mission wherein the Task is registered. --- @param Core.Set#SET_GROUP SetGroupAssign The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @return #TASK self -function TASK:New( Mission, SetGroupAssign, TaskName, TaskType ) - - local self = BASE:Inherit( self, FSM_TASK:New() ) -- Core.Fsm#FSM_TASK - - self:SetStartState( "Planned" ) - self:AddTransition( "Planned", "Assign", "Assigned" ) - self:AddTransition( "Assigned", "AssignUnit", "Assigned" ) - self:AddTransition( "Assigned", "Success", "Success" ) - self:AddTransition( "Assigned", "Fail", "Failed" ) - self:AddTransition( "Assigned", "Abort", "Aborted" ) - self:AddTransition( "Assigned", "Cancel", "Cancelled" ) - self:AddTransition( "*", "PlayerCrashed", "*" ) - self:AddTransition( "*", "PlayerAborted", "*" ) - self:AddTransition( "*", "PlayerDead", "*" ) - self:AddTransition( { "Failed", "Aborted", "Cancelled" }, "Replan", "Planned" ) - self:AddTransition( "*", "TimeOut", "Cancelled" ) - - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.CommandCenter = Mission:GetCommandCenter() - - self.SetGroup = SetGroupAssign - - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are invited for the task: " .. self.TaskName .. "." - - self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() - - - return self -end - ---- Get the Task FSM Process Template --- @param #TASK self --- @return Core.Fsm#FSM_PROCESS -function TASK:GetUnitProcess( TaskUnit ) - - if TaskUnit then - return self:GetStateMachine( TaskUnit ) - else - return self.FsmTemplate - end -end - ---- Sets the Task FSM Process Template --- @param #TASK self --- @param Core.Fsm#FSM_PROCESS -function TASK:SetUnitProcess( FsmTemplate ) - - self.FsmTemplate = FsmTemplate -end - ---- Add a PlayerUnit to join the Task. --- For each Group within the Task, the Unit is check if it can join the Task. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. --- @return #boolean true if Unit is part of the Task. -function TASK:JoinUnit( PlayerUnit, PlayerGroup ) - self:F( { PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) - - local PlayerUnitAdded = false - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is added to the Task. - -- If the PlayerGroup is not assigned to the Task, the menu needs to be set. In that case, the PlayerUnit will become the GroupPlayer leader. - if self:IsStatePlanned() or self:IsStateReplanned() then - self:SetMenuForGroup( PlayerGroup ) - --self:MessageToGroups( PlayerUnit:GetPlayerName() .. " is planning to join Task " .. self:GetName() ) - end - if self:IsStateAssigned() then - local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) - self:E( { IsAssignedToGroup = IsAssignedToGroup } ) - if IsAssignedToGroup then - self:AssignToUnit( PlayerUnit ) - self:MessageToGroups( PlayerUnit:GetPlayerName() .. " joined Task " .. self:GetName() ) - end - end - end - - return PlayerUnitAdded -end - ---- Abort a PlayerUnit from a Task. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. --- @return #boolean true if Unit is part of the Task. -function TASK:AbortUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - local PlayerUnitAborted = false - - local PlayerGroups = self:GetGroups() - local PlayerGroup = PlayerUnit:GetGroup() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStateAssigned() then - local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) - self:E( { IsAssignedToGroup = IsAssignedToGroup } ) - if IsAssignedToGroup then - self:UnAssignFromUnit( PlayerUnit ) - self:MessageToGroups( PlayerUnit:GetPlayerName() .. " aborted Task " .. self:GetName() ) - self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) - if #PlayerGroup:GetUnits() == 1 then - self:UnAssignFromGroup( PlayerGroup ) - PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) - self:RemoveMenuForGroup( PlayerGroup ) - end - self:Abort() - end - end - end - - return PlayerUnitAborted -end - ---- A PlayerUnit crashed in a Task. Abort the Player. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. --- @return #boolean true if Unit is part of the Task. -function TASK:CrashUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - local PlayerUnitCrashed = false - - local PlayerGroups = self:GetGroups() - local PlayerGroup = PlayerUnit:GetGroup() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStateAssigned() then - local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) - self:E( { IsAssignedToGroup = IsAssignedToGroup } ) - if IsAssignedToGroup then - self:UnAssignFromUnit( PlayerUnit ) - self:MessageToGroups( PlayerUnit:GetPlayerName() .. " crashed in Task " .. self:GetName() ) - self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) - if #PlayerGroup:GetUnits() == 1 then - PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) - self:RemoveMenuForGroup( PlayerGroup ) - end - self:PlayerCrashed( PlayerUnit ) - end - end - end - - return PlayerUnitCrashed -end - - - ---- Gets the Mission to where the TASK belongs. --- @param #TASK self --- @return Tasking.Mission#MISSION -function TASK:GetMission() - - return self.Mission -end - - ---- Gets the SET_GROUP assigned to the TASK. --- @param #TASK self --- @return Core.Set#SET_GROUP -function TASK:GetGroups() - return self.SetGroup -end - - - ---- Assign the @{Task} to a @{Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @return #TASK -function TASK:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - local Mission = self:GetMission() - local MissionMenu = Mission:GetMenu( TaskGroup ) - MissionMenu:RemoveSubMenus() - - --self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - self:E(PlayerName) - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end - - return self -end - ---- --- @param #TASK self --- @param Wrapper.Group#GROUP FindGroup --- @return #boolean -function TASK:HasGroup( FindGroup ) - - return self:GetGroups():IsIncludeObject( FindGroup ) - -end - ---- Assign the @{Task} to an alive @{Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local FsmTemplate = self:GetUnitProcess() - - -- Assign a new FsmUnit to TaskUnit. - local FsmUnit = self:SetStateMachine( TaskUnit, FsmTemplate:Copy( TaskUnit, self ) ) -- Core.Fsm#FSM_PROCESS - self:E({"Address FsmUnit", tostring( FsmUnit ) } ) - - FsmUnit:SetStartState( "Planned" ) - - FsmUnit:Accept() -- Each Task needs to start with an Accept event to start the flow. - - return self -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:UnAssignFromUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - self:RemoveStateMachine( TaskUnit ) - - return self -end - ---- Sets the TimeOut for the @{Task}. If @{Task} stayed planned for longer than TimeOut, it gets into Cancelled status. --- @param #TASK self --- @param #integer Timer in seconds --- @return #TASK self -function TASK:SetTimeOut ( Timer ) - self:F( Timer ) - self.TimeOut = Timer - self:__TimeOut( self.TimeOut ) - return self -end - ---- Send a message of the @{Task} to the assigned @{Group}s. --- @param #TASK self -function TASK:MessageToGroups( Message ) - self:F( { Message = Message } ) - - local Mission = self:GetMission() - local CC = Mission:GetCommandCenter() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - CC:MessageToGroup( Message, TaskGroup, TaskGroup:GetName() ) - end -end - - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK self -function TASK:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- UnAssign the @{Task} from the @{Group}s. --- @param #TASK self -function TASK:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:UnAssignFromGroup( TaskGroup ) - end -end - ---- UnAssign the @{Task} from a @{Group}. --- @param #TASK self -function TASK:UnAssignFromGroup( TaskGroup ) - self:F2( { TaskGroup } ) - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - - self:RemoveAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end -end - - - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @return #boolean -function TASK:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - self:T( { "Task is assigned to:", TaskGroup:GetName() } ) - return true - end - end - - self:T( { "Task is not assigned to:", TaskGroup:GetName() } ) - return false -end - ---- Returns if the @{Task} has still alive and assigned Units. --- @param #TASK self --- @return #boolean -function TASK:HasAliveUnits() - self:F() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsStateAssigned() then - if self:IsAssignedToGroup( TaskGroup ) then - for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do - if TaskUnit:IsAlive() then - self:T( { HasAliveUnits = true } ) - return true - end - end - end - end - end - - self:T( { HasAliveUnits = false } ) - return false -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:SetMenu( MenuTime ) - self:F() - - self.SetGroup:Flush() - for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetSet() ) do - local TaskGroup = TaskGroupData -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() and TaskGroup:GetPlayerNames() then - if self:IsStatePlanned() or self:IsStateReplanned() then - self:SetMenuForGroup( TaskGroup, MenuTime ) - end - end - end -end - - - ---- Set the Menu for a Group --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:SetMenuForGroup( TaskGroup, MenuTime ) - - if not TaskGroup:GetState( TaskGroup, "Assigned" ) then - self:SetPlannedMenuForGroup( TaskGroup, self:GetTaskName(), MenuTime ) - else - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup, MenuTime ) - end - end -end - - ---- Set the planned menu option of the @{Task}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @param #number MenuTime --- @return #TASK self -function TASK:SetPlannedMenuForGroup( TaskGroup, MenuText, MenuTime ) - self:E( TaskGroup:GetName() ) - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local CommandCenter = Mission:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu() - - local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ):SetTime( MenuTime ) - - - local MissionMenu = Mission:GetMenu( TaskGroup ) - - local TaskType = self:GetType() - local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskType, MissionMenu ):SetTime( MenuTime ) - local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, TaskTypeMenu, self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ):SetTime( MenuTime ):SetRemoveParent( true ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime ) - self:E( TaskGroup:GetName() ) - - local Mission = self:GetMission() - local MissionMenu = Mission:GetMenu( TaskGroup ) - - self:E( { MissionMenu = MissionMenu } ) - - local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MissionMenu, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ) - local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MissionMenu, self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ) - - return self -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:RemoveMenu( MenuTime ) - self:F() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() and TaskGroup:GetPlayerNames() then - if not self:IsAssignedToGroup( TaskGroup ) then - self:RemovePlannedMenuForGroup( TaskGroup, MenuTime ) - end - end - end -end - - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:RemovePlannedMenuForGroup( TaskGroup, MenuTime ) - self:F() - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - - local MissionMenu = Mission:GetMenu( TaskGroup ) - - if MissionMenu then - local TaskType = self:GetType() - local TypeMenu = MissionMenu:GetMenu( TaskType ) - - if TypeMenu then - local TaskMenu = TypeMenu:GetMenu( self:GetTaskName() ) - if TaskMenu then - TaskMenu:Remove( MenuTime ) - end - end - end - -end - ---- Remove the assigned menu option of the @{Task} for a @{Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:RemoveAssignedMenuForGroup( TaskGroup ) - self:F() - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - - local MissionMenu = Mission:GetMenu( TaskGroup ) - - if MissionMenu then - MissionMenu:RemoveSubMenus() - end - -end - -function TASK.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:E( "Assigned menu selected") - - self:AssignToGroup( TaskGroup ) -end - ---- Report the task status. --- @param #TASK self -function TASK:MenuTaskStatus( TaskGroup ) - - local ReportText = self:ReportDetails() - - self:T( ReportText ) - self:GetMission():GetCommandCenter():MessageToGroup( ReportText, TaskGroup ) - -end - ---- Report the task status. --- @param #TASK self -function TASK:MenuTaskAbort( TaskGroup ) - - self:Abort() -end - - - ---- Returns the @{Task} name. --- @param #TASK self --- @return #string TaskName -function TASK:GetTaskName() - return self.TaskName -end - - - - ---- Get the default or currently assigned @{Process} template with key ProcessName. --- @param #TASK self --- @param #string ProcessName --- @return Core.Fsm#FSM_PROCESS -function TASK:GetProcessTemplate( ProcessName ) - - local ProcessTemplate = self.ProcessClasses[ProcessName] - - return ProcessTemplate -end - - - --- TODO: Obscolete? ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK self --- @param #string TaskUnitName --- @return #TASK self -function TASK:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key Task@{Unit} --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @param Core.Fsm#FSM_PROCESS Fsm --- @return #TASK self -function TASK:SetStateMachine( TaskUnit, Fsm ) - self:F2( { TaskUnit, self.Fsm[TaskUnit] ~= nil, Fsm:GetClassNameAndID() } ) - - self.Fsm[TaskUnit] = Fsm - - return Fsm -end - ---- Gets the FiniteStateMachine of @{Task} with key Task@{Unit} --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return Core.Fsm#FSM_PROCESS -function TASK:GetStateMachine( TaskUnit ) - self:F2( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - return self.Fsm[TaskUnit] -end - ---- Remove FiniteStateMachines from @{Task} with key Task@{Unit} --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:RemoveStateMachine( TaskUnit ) - self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - self:E( self.Fsm ) - for TaskUnitT, Fsm in pairs( self.Fsm ) do - self:E( TaskUnitT ) - end - - self.Fsm[TaskUnit] = nil - - collectgarbage() - self:E( "Garbage Collected, Processes should be finalized now ...") -end - ---- Checks if there is a FiniteStateMachine assigned to Task@{Unit} for @{Task} --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:HasStateMachine( TaskUnit ) - self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - return ( self.Fsm[TaskUnit] ~= nil ) -end - - ---- Gets the Scoring of the task --- @param #TASK self --- @return Functional.Scoring#SCORING Scoring -function TASK:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task type, the Task name. --- @param #TASK self --- @return #string The Task ID -function TASK:GetTaskIndex() - - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK self --- @param #string TaskName -function TASK:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK self --- @return #string The Task Name -function TASK:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK self --- @param #string TaskType -function TASK:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK self --- @return #string TaskType -function TASK:GetType() - return self.TaskType -end - ---- Sets the ID of the Task --- @param #TASK self --- @param #string TaskID -function TASK:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK self --- @return #string TaskID -function TASK:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK self -function TASK:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK self -function TASK:IsStateSuccess() - return self:Is( "Success" ) -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK self -function TASK:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK self -function TASK:IsStateFailed() - return self:Is( "Failed" ) -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK self -function TASK:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK self -function TASK:IsStatePlanned() - return self:Is( "Planned" ) -end - ---- Sets a @{Task} to status **Aborted**. --- @param #TASK self -function TASK:StateAborted() - self:SetState( self, "State", "Aborted" ) - return self -end - ---- Is the @{Task} status **Aborted**. --- @param #TASK self -function TASK:IsStateAborted() - return self:Is( "Aborted" ) -end - ---- Sets a @{Task} to status **Cancelled**. --- @param #TASK self -function TASK:StateCancelled() - self:SetState( self, "State", "Cancelled" ) - return self -end - ---- Is the @{Task} status **Cancelled**. --- @param #TASK self -function TASK:IsStateCancelled() - return self:Is( "Cancelled" ) -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK self -function TASK:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK self -function TASK:IsStateAssigned() - return self:Is( "Assigned" ) -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK self -function TASK:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK self -function TASK:IsStateHold() - return self:Is( "Hold" ) -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK self -function TASK:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK self -function TASK:IsStateReplanned() - return self:Is( "Replanned" ) -end - ---- Gets the @{Task} status. --- @param #TASK self -function TASK:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK self --- @param #string TaskBriefing --- @return #TASK self -function TASK:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterAssigned( From, Event, To ) - - self:E("Task Assigned") - - self:MessageToGroups( "Task " .. self:GetName() .. " has been assigned to your group." ) - self:GetMission():__Start( 1 ) -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterSuccess( From, Event, To ) - - self:E( "Task Success" ) - - self:MessageToGroups( "Task " .. self:GetName() .. " is successful! Good job!" ) - self:UnAssignFromGroups() - - self:GetMission():__Complete( 1 ) - -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterAborted( From, Event, To ) - - self:E( "Task Aborted" ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been aborted! Task may be replanned." ) - - self:UnAssignFromGroups() - - self:__Replan( 5 ) -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onafterReplan( From, Event, To ) - - self:E( "Task Replanned" ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Replanning Task " .. self:GetName() .. "." ) - - self:SetMenu() - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterFailed( From, Event, To ) - - self:E( "Task Failed" ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has failed!" ) - - self:UnAssignFromGroups() -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onstatechange( From, Event, To ) - - if self:IsTrace() then - MESSAGE:New( "@ Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() - end - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - self:E( { self.Scores[To].ScoreText, self.Scores[To].Score } ) - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterPlanned( From, Event, To) - if not self.TimeOut == 0 then - self.__TimeOut( self.TimeOut ) - end -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onbeforeTimeOut( From, Event, To ) - if From == "Planned" then - self:RemoveMenu() - return true - end - return false -end - -do -- Reporting - ---- Create a summary report of the Task. --- List the Task Name and Status --- @param #TASK self --- @return #string -function TASK:ReportSummary() - - local Report = REPORT:New() - - -- List the name of the Task. - local Name = self:GetName() - - -- Determine the status of the Task. - local State = self:GetState() - - Report:Add( "Task " .. Name .. " - State '" .. State ) - - return Report:Text() -end - - ---- Create a detailed report of the Task. --- List the Task Status, and the Players assigned to the Task. --- @param #TASK self --- @return #string -function TASK:ReportDetails() - - local Report = REPORT:New() - - -- List the name of the Task. - local Name = self:GetName() - - -- Determine the status of the Task. - local State = self:GetState() - - -- Loop each Unit active in the Task, and find Player Names. - local PlayerNames = {} - local PlayerReport = REPORT:New( " - Players:" ) - for PlayerGroupID, PlayerGroupData in pairs( self:GetGroups():GetSet() ) do - local PlayerGroup = PlayerGroupData -- Wrapper.Group#GROUP - PlayerNames = PlayerGroup:GetPlayerNames() - if PlayerNames then - PlayerReport:Add( " -- Group " .. PlayerGroup:GetCallsign() .. ": " .. table.concat( PlayerNames, ", " ) ) - end - end - - -- Loop each Process in the Task, and find Reporting Details. - Report:Add( string.format( " - Task %s\n -- State '%s'\n%s", Name, State, PlayerReport:Text() ) ) - return Report:Text() -end - - -end -- Reporting ---- This module contains the DETECTION_MANAGER class and derived classes. --- --- === --- --- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} --- ==================================================================== --- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. --- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. --- --- 1.1) DETECTION_MANAGER constructor: --- ----------------------------------- --- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. --- --- 1.2) DETECTION_MANAGER reporting: --- --------------------------------- --- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. --- --- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. --- --- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). --- --- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. --- --- === --- --- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} --- ========================================================================================= --- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. --- --- 2.1) DETECTION_REPORTING constructor: --- ------------------------------- --- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. --- --- --- === --- --- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module DetectionManager - -do -- DETECTION MANAGER - - --- DETECTION_MANAGER class. - -- @type DETECTION_MANAGER - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @extends Base#BASE - DETECTION_MANAGER = { - ClassName = "DETECTION_MANAGER", - SetGroup = nil, - Detection = nil, - } - - --- FAC constructor. - -- @param #DETECTION_MANAGER self - -- @param Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:New( SetGroup, Detection ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Functional.Detection#DETECTION_MANAGER - - self.SetGroup = SetGroup - self.Detection = Detection - - self:SetReportInterval( 30 ) - self:SetReportDisplayTime( 25 ) - - Detection:__Start( 5 ) - - return self - end - - --- Set the reporting time interval. - -- @param #DETECTION_MANAGER self - -- @param #number ReportInterval The interval in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetReportInterval( ReportInterval ) - self:F2() - - self._ReportInterval = ReportInterval - end - - - --- Set the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime ) - self:F2() - - self._ReportDisplayTime = ReportDisplayTime - end - - --- Get the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. - function DETECTION_MANAGER:GetReportDisplayTime() - self:F2() - - return self._ReportDisplayTime - end - - - - --- Reports the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_MANAGER self - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:ReportDetected( Detection ) - self:F2() - - end - - --- Schedule the FAC reporting. - -- @param #DETECTION_MANAGER self - -- @param #number DelayTime The delay in seconds to wait the reporting. - -- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:Schedule( DelayTime, ReportInterval ) - self:F2() - - self._ScheduleDelayTime = DelayTime - - self:SetReportInterval( ReportInterval ) - - self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "DetectionManager" }, self._ScheduleDelayTime, self._ReportInterval ) - return self - end - - --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. - -- @param #DETECTION_MANAGER self - function DETECTION_MANAGER:_FacScheduler( SchedulerName ) - self:F2( { SchedulerName } ) - - return self:ProcessDetected( self.Detection ) - --- self.SetGroup:ForEachGroup( --- --- @param Wrapper.Group#GROUP Group --- function( Group ) --- if Group:IsAlive() then --- return self:ProcessDetected( self.Detection ) --- end --- end --- ) - --- return true - end - -end - - -do -- DETECTION_REPORTING - - --- DETECTION_REPORTING class. - -- @type DETECTION_REPORTING - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @extends #DETECTION_MANAGER - DETECTION_REPORTING = { - ClassName = "DETECTION_REPORTING", - } - - - --- DETECTION_REPORTING constructor. - -- @param #DETECTION_REPORTING self - -- @param Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_AREAS Detection - -- @return #DETECTION_REPORTING self - function DETECTION_REPORTING:New( SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING - - self:Schedule( 1, 30 ) - return self - end - - --- Creates a string of the detected items in a @{Detection}. - -- @param #DETECTION_MANAGER self - -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. - -- @return #DETECTION_MANAGER self - function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - local UnitType = DetectedUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) - end - - - - --- Reports the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_REPORTING self - -- @param Wrapper.Group#GROUP Group The @{Group} object to where the report needs to go. - -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. - -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. - function DETECTION_REPORTING:ProcessDetected( Group, Detection ) - self:F2( Group ) - - self:E( Group ) - local DetectedMsg = {} - for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea - DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) - end - local FACGroup = Detection:GetDetectionGroups() - FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group ) - - return true - end - -end - ---- **Tasking** - The TASK_A2G_DISPATCHER creates and manages player TASK_A2G tasks based on detected targets. --- --- === --- --- # 1) @{#TASK_A2G_DISPATCHER} class, extends @{#DETECTION_MANAGER} --- --- The @{#TASK_A2G_DISPATCHER} class implements the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of FAC (groups). --- The FAC will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. --- Find a summary below describing for which situation a task type is created: --- --- * **CAS Task**: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter. --- * **BAI Task**: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter. --- * **SEAD Task**: Is created when there are enemy ground units wihtin range of the FAC, with air search radars. --- --- Other task types will follow... --- --- 3.1) TASK_A2G_DISPATCHER constructor: --- -------------------------------------- --- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK_A2G_DISPATCHER instance. --- --- === --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-09: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module Task_A2G_Dispatcher - -do -- TASK_A2G_DISPATCHER - - --- TASK_A2G_DISPATCHER class. - -- @type TASK_A2G_DISPATCHER - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Tasking.Mission#MISSION Mission - -- @field Wrapper.Group#GROUP CommandCenter - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - TASK_A2G_DISPATCHER = { - ClassName = "TASK_A2G_DISPATCHER", - Mission = nil, - CommandCenter = nil, - Detection = nil, - } - - - --- TASK_A2G_DISPATCHER constructor. - -- @param #TASK_A2G_DISPATCHER self - -- @param Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #TASK_A2G_DISPATCHER self - function TASK_A2G_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER - - self.Detection = Detection - self.CommandCenter = CommandCenter - self.Mission = Mission - - self:Schedule( 30 ) - return self - end - - - --- Creates a SEAD task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount > 0 then - - -- Here we're doing something advanced... We're copying the DetectedSet, but making a new Set only with SEADable Radar units in it. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterHasSEAD() - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a CAS task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Tasking.Task#TASK - function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem ) - - if GroundUnitCount > 0 and FriendliesNearBy == true then - - -- Copy the Set - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a BAI task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Tasking.Task#TASK - function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem ) - - if GroundUnitCount > 0 and FriendliesNearBy == false then - - -- Copy the Set - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Evaluates the removal of the Task from the Mission. - -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". - -- @param #TASK_A2G_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission - -- @param Tasking.Task#TASK Task - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Tasking.Task#TASK - function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedItem ) - - if Task then - if Task:IsStatePlanned() and DetectedItem.Changed == true then - self:E( "Removing Tasking: " .. Task:GetTaskName() ) - Task = Mission:RemoveTask( Task ) - end - end - - return Task - end - - - --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Detection#DETECTION_BASE} derived object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function TASK_A2G_DISPATCHER:ProcessDetected( Detection ) - self:F2() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - local ReportSEAD = REPORT:New( " - SEAD Tasks:") - local ReportCAS = REPORT:New( " - CAS Tasks:") - local ReportBAI = REPORT:New( " - BAI Tasks:") - local ReportChanges = REPORT:New( " - Changes:" ) - - --- First we need to the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Functional.Detection#DETECTION_BASE.DetectedSet - local DetectedZone = DetectedItem.Zone - self:E( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - DetectedSet:Flush() - - local ItemID = DetectedItem.ItemID - - -- Evaluate SEAD Tasking - local SEADTask = Mission:GetTask( string.format( "SEAD.%03d", ItemID ) ) - SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedItem ) - if not SEADTask then - local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - local Task = TASK_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", ItemID ), TargetSetUnit ) - Task:SetTargetZone( DetectedZone ) - SEADTask = Mission:AddTask( Task ) - end - end - if SEADTask and SEADTask:IsStatePlanned() then - ReportSEAD:Add( string.format( " - %s.%02d - %s", "SEAD", ItemID, Detection:DetectedItemReportSummary(DetectedItemID) ) ) - end - - -- Evaluate CAS Tasking - local CASTask = Mission:GetTask( string.format( "CAS.%03d", ItemID ) ) - CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedItem ) - if not CASTask then - local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - local Task = TASK_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", ItemID ), TargetSetUnit ) - --Task:SetTargetZone( DetectedZone ) - CASTask = Mission:AddTask( Task ) - end - end - if CASTask and CASTask:IsStatePlanned() then - ReportCAS:Add( string.format( " - %s.%02d - %s", "CAS", ItemID, Detection:DetectedItemReportSummary(DetectedItemID) ) ) - end - - -- Evaluate BAI Tasking - local BAITask = Mission:GetTask( string.format( "BAI.%03d", ItemID ) ) - BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedItem ) - if not BAITask then - local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - local Task = TASK_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", ItemID ), TargetSetUnit ) - Task:SetTargetZone( DetectedZone ) - BAITask = Mission:AddTask( Task ) - end - end - if BAITask and BAITask:IsStatePlanned() then - ReportBAI:Add( string.format( " - %s.%02d - %s", "BAI", ItemID, Detection:DetectedItemReportSummary(DetectedItemID) ) ) - end - - - -- Loop through the changes ... - local ChangeText = Detection:GetChangeText( DetectedItem ) - ReportChanges:Add( ChangeText ) - - - -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedItem ) - - end - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not TaskGroup:GetState( TaskGroup, "Assigned" ) then - self.CommandCenter:MessageToGroup( - string.format( "HQ Reporting - Planned tasks for mission '%s':\n%s\n", - self.Mission:GetName(), - string.format( "%s\n%s\n%s\n%s", ReportSEAD:Text(), ReportCAS:Text(), ReportBAI:Text(), ReportChanges:Text() - ) - ), self:GetReportDisplayTime(), TaskGroup - ) - end - end - - return true - end - -end--- This module contains the TASK_A2G classes. --- --- # 1) @{Task_A2G#TASK_A2G} class, extends @{Task#TASK} --- --- The @{#TASK_A2G} class defines Air To Ground tasks for a @{Set} of Target Units, --- based on the tasking capabilities defined in @{Task#TASK}. --- The TASK_A2G is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: --- --- * **None**: Start of the process --- * **Planned**: The A2G task is planned. --- * **Assigned**: The A2G task is assigned to a @{Group#GROUP}. --- * **Success**: The A2G task is successfully completed. --- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- # 1) @{Task_A2G#TASK_SEAD} class, extends @{Task_A2G#TASK_A2G} --- --- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-09: Revised version. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[WingThor]**: Concept, Advice & Testing. --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module Task_A2G - -do -- TASK_A2G - - --- The TASK_A2G class - -- @type TASK_A2G - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - TASK_A2G = { - ClassName = "TASK_A2G", - } - - --- Instantiates a new TASK_A2G. - -- @param #TASK_A2G self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_A2G self - function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) -- Tasking.Task#TASK_A2G - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TaskType = TaskType - - Mission:AddTask( self ) - - local Fsm = self:GetUnitProcess() - - - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "RouteToRendezVous", Rejected = "Reject" } ) - - Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" ) - Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } ) - Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } ) - - Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" ) - - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" ) - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" ) - - Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, self.TaskType ), { Accounted = "Success" } ) - Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" ) - Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} ) - Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" ) - - Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) - Fsm:AddTransition( "Accounted", "Success", "Success" ) - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.RendezVousSetUnit - - if Task:GetRendezVousZone( TaskUnit ) then - self:__RouteToRendezVousZone( 0.1 ) - else - if Task:GetRendezVousPointVec2( TaskUnit ) then - self:__RouteToRendezVousPoint( 0.1 ) - else - self:__ArriveAtRendezVous( 0.1 ) - end - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2G Task - function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2G Task - function Fsm:onafterEngage( TaskUnit, Task ) - self:E( { self } ) - self:__Account( 0.1 ) - self:__RouteToTarget(0.1 ) - self:__RouteToTargets( -10 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToTarget( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - if Task:GetTargetZone( TaskUnit ) then - self:__RouteToTargetZone( 0.1 ) - else - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - local PointVec2 = TargetUnit:GetPointVec2() - self:T( { TargetPointVec2 = PointVec2, PointVec2:GetX(), PointVec2:GetAlt(), PointVec2:GetZ() } ) - Task:SetTargetPointVec2( TargetUnit:GetPointVec2(), TaskUnit ) - end - self:__RouteToTargetPoint( 0.1 ) - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToTargets( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - Task:SetTargetPointVec2( TargetUnit:GetPointVec2(), TaskUnit ) - end - self:__RouteToTargets( -10 ) - end - - return self - - end - - --- @param #TASK_A2G self - function TASK_A2G:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - --- @param #TASK_A2G self - -- @param Core.Point#POINT_VEC2 RendezVousPointVec2 The PointVec2 object referencing to the 2D point where the RendezVous point is located on the map. - -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousPointVec2( RendezVousPointVec2, RendezVousRange, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteRendezVous:SetPointVec2( RendezVousPointVec2 ) - ActRouteRendezVous:SetRange( RendezVousRange ) - end - - --- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#POINT_VEC2 The PointVec2 object referencing to the 2D point where the RendezVous point is located on the map. - -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - function TASK_A2G:GetRendezVousPointVec2( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteRendezVous:GetPointVec2(), ActRouteRendezVous:GetRange() - end - - - - --- @param #TASK_A2G self - -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteRendezVous:SetZone( RendezVousZone ) - end - - --- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. - function TASK_A2G:GetRendezVousZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteRendezVous:GetZone() - end - - --- @param #TASK_A2G self - -- @param Core.Point#POINT_VEC2 TargetPointVec2 The PointVec2 object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetTargetPointVec2( TargetPointVec2, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteTarget:SetPointVec2( TargetPointVec2 ) - end - - - --- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#POINT_VEC2 The PointVec2 object where the Target is located on the map. - function TASK_A2G:GetTargetPointVec2( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteTarget:GetPointVec2() - end - - - --- @param #TASK_A2G self - -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetTargetZone( TargetZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteTarget:SetZone( TargetZone ) - end - - - --- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_A2G:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - -end - - -do -- TASK_SEAD - - --- The TASK_SEAD class - -- @type TASK_SEAD - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - TASK_SEAD = { - ClassName = "TASK_SEAD", - } - - --- Instantiates a new TASK_SEAD. - -- @param #TASK_SEAD self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_SEAD self - function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD" ) ) -- #TASK_SEAD - self:F() - - return self - end - -end - -do -- TASK_BAI - - --- The TASK_BAI class - -- @type TASK_BAI - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - TASK_BAI = { - ClassName = "TASK_BAI", - } - - --- Instantiates a new TASK_BAI. - -- @param #TASK_BAI self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_BAI self - function TASK_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "BAI" ) ) -- #TASK_BAI - self:F() - - return self - end - -end - -do -- TASK_CAS - - --- The TASK_CAS class - -- @type TASK_CAS - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - TASK_CAS = { - ClassName = "TASK_CAS", - } - - --- Instantiates a new TASK_CAS. - -- @param #TASK_CAS self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_CAS self - function TASK_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "CAS" ) ) -- #TASK_CAS - self:F() - - return self - end - -end ---- The main include file for the MOOSE system. --- Test of permissions - ---- Core Routines -Include.File( "Utilities/Routines" ) -Include.File( "Utilities/Utils" ) - ---- Core Classes -Include.File( "Core/Base" ) -Include.File( "Core/Scheduler" ) -Include.File( "Core/ScheduleDispatcher") -Include.File( "Core/Event" ) -Include.File( "Core/Menu" ) -Include.File( "Core/Zone" ) -Include.File( "Core/Database" ) -Include.File( "Core/Set" ) -Include.File( "Core/Point" ) -Include.File( "Core/Message" ) -Include.File( "Core/Fsm" ) - ---- Wrapper Classes -Include.File( "Wrapper/Object" ) -Include.File( "Wrapper/Identifiable" ) -Include.File( "Wrapper/Positionable" ) -Include.File( "Wrapper/Controllable" ) -Include.File( "Wrapper/Group" ) -Include.File( "Wrapper/Unit" ) -Include.File( "Wrapper/Client" ) -Include.File( "Wrapper/Static" ) -Include.File( "Wrapper/Airbase" ) -Include.File( "Wrapper/Scenery" ) - ---- Functional Classes -Include.File( "Functional/Scoring" ) -Include.File( "Functional/CleanUp" ) -Include.File( "Functional/Spawn" ) -Include.File( "Functional/Movement" ) -Include.File( "Functional/Sead" ) -Include.File( "Functional/Escort" ) -Include.File( "Functional/MissileTrainer" ) -Include.File( "Functional/AirbasePolice" ) -Include.File( "Functional/Detection" ) - ---- AI Classes -Include.File( "AI/AI_Balancer" ) -Include.File( "AI/AI_Patrol" ) -Include.File( "AI/AI_Cap" ) -Include.File( "AI/AI_Cas" ) -Include.File( "AI/AI_Cargo" ) - ---- Actions -Include.File( "Actions/Act_Assign" ) -Include.File( "Actions/Act_Route" ) -Include.File( "Actions/Act_Account" ) -Include.File( "Actions/Act_Assist" ) - ---- Task Handling Classes -Include.File( "Tasking/CommandCenter" ) -Include.File( "Tasking/Mission" ) -Include.File( "Tasking/Task" ) -Include.File( "Tasking/DetectionManager" ) -Include.File( "Tasking/Task_A2G_Dispatcher") -Include.File( "Tasking/Task_A2G" ) - - --- The order of the declarations is important here. Don't touch it. - ---- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT - ---- Declare the timer dispatcher based on the SCHEDULEDISPATCHER class -_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER - ---- Declare the main database object, which is used internally by the MOOSE classes. -_DATABASE = DATABASE:New() -- Database#DATABASE +Include.ProgramPath = "Scripts/Moose/" +env.info( "Include.ProgramPath = " .. Include.ProgramPath) +Include.Files = {} +Include.File( "Moose" ) -BASE:TraceOnOff( false ) +BASE:TraceOnOff( true ) env.info( '*** MOOSE INCLUDE END *** ' ) diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index 11c91cda5..0768235fb 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,35176 +1,31 @@ -env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20170317_1149' ) +env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) +env.info( 'Moose Generation Timestamp: 20170317_1239' ) + local base = _G Include = {} -Include.Files = {} + Include.File = function( IncludeFile ) -end - ---- Various routines --- @module routines --- @author Flightcontrol - -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 = {} - ---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) + if not Include.Files[ IncludeFile ] then + Include.Files[IncludeFile] = IncludeFile + env.info( "Include:" .. IncludeFile .. " from " .. Include.ProgramPath ) + local f = assert( base.loadfile( Include.ProgramPath .. IncludeFile .. ".lua" ) ) + if f == nil then + error ("Could not load MOOSE file " .. IncludeFile .. ".lua" ) else - return tostring(tbl) + env.info( "Include:" .. IncludeFile .. " loaded from " .. Include.ProgramPath ) + return f() 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('%q', s) - 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 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) - - return -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) - - return -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.RED ) -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' )) - ---- This module contains derived utilities taken from the MIST framework, --- which are excellent tools to be reused in an OO environment!. --- --- ### Authors: --- --- * Grimes : Design & Programming of the MIST framework. --- --- ### Contributions: --- --- * FlightControl : Rework to OO framework --- --- @module Utils - - ---- @type SMOKECOLOR --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - -SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR - ---- @type FLARECOLOR --- @field Green --- @field Red --- @field White --- @field Yellow - -FLARECOLOR = trigger.flareColor -- #FLARECOLOR - ---- Utilities static class. --- @type UTILS -UTILS = {} - - ---from http://lua-users.org/wiki/CopyTable -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 -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] = "f() " .. 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 - return tostring(tbl) - end - end - - local objectreturn = _Serialize(tbl) - return objectreturn -end - ---porting in Slmod's "safestring" basic serialize -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 - - -UTILS.ToDegree = function(angle) - return angle*180/math.pi -end - -UTILS.ToRadian = function(angle) - return angle*math.pi/180 -end - -UTILS.MetersToNM = function(meters) - return meters/1852 -end - -UTILS.MetersToFeet = function(meters) - return meters/0.3048 -end - -UTILS.NMToMeters = function(NM) - return NM*1852 -end - -UTILS.FeetToMeters = function(feet) - return feet*0.3048 -end - -UTILS.MpsToKnots = function(mps) - return mps*3600/1852 -end - -UTILS.MpsToKmph = function(mps) - return mps*3.6 -end - -UTILS.KnotsToMps = function(knots) - return knots*1852/3600 -end - -UTILS.KmphToMps = function(kmph) - return kmph/3.6 -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. -]] -UTILS.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 = UTILS.Round((oldLatMin - latMin)*60, acc) - - local oldLonMin = lonMin - lonMin = math.floor(lonMin) - local lonSec = 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 = UTILS.Round(latMin, acc) - lonMin = 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 - - ---- From http://lua-users.org/wiki/SimpleRound --- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place -function UTILS.Round( num, idp ) - local mult = 10 ^ ( idp or 0 ) - return math.floor( num * mult + 0.5 ) / mult -end - --- porting in Slmod's dostring -function UTILS.DoString( s ) - local f, err = loadstring( s ) - if f then - return true, f() - else - return false, err - end -end ---- **Core** - BASE forms **the basis of the MOOSE framework**. Each class within the MOOSE framework derives from BASE. --- --- ![Banner Image](..\Presentations\BASE\Dia1.JPG) --- --- === --- --- # 1) @{#BASE} class --- --- All classes within the MOOSE framework are derived from the @{#BASE} class. --- --- BASE provides facilities for : --- --- * The construction and inheritance of MOOSE classes. --- * The class naming and numbering system. --- * The class hierarchy search system. --- * The tracing of information or objects during mission execution for debuggin purposes. --- * The subscription to DCS events for event handling in MOOSE objects. --- --- Note: The BASE class is an abstract class and is not meant to be used directly. --- --- ## 1.1) BASE constructor --- --- Any class derived from BASE, will use the @{Base#BASE.New} constructor embedded in the @{Base#BASE.Inherit} method. --- See an example at the @{Base#BASE.New} method how this is done. --- --- ## 1.2) Trace information for debugging --- --- The BASE class contains trace methods to trace progress within a mission execution of a certain object. --- These trace methods are inherited by each MOOSE class interiting BASE, soeach object created from derived class from BASE can use the tracing methods to trace its execution. --- --- Any type of information can be passed to these tracing methods. See the following examples: --- --- self:E( "Hello" ) --- --- Result in the word "Hello" in the dcs.log. --- --- local Array = { 1, nil, "h", { "a","b" }, "x" } --- self:E( Array ) --- --- Results with the text [1]=1,[3]="h",[4]={[1]="a",[2]="b"},[5]="x"} in the dcs.log. --- --- local Object1 = "Object1" --- local Object2 = 3 --- local Object3 = { Object 1, Object 2 } --- self:E( { Object1, Object2, Object3 } ) --- --- Results with the text [1]={[1]="Object",[2]=3,[3]={[1]="Object",[2]=3}} in the dcs.log. --- --- local SpawnObject = SPAWN:New( "Plane" ) --- local GroupObject = GROUP:FindByName( "Group" ) --- self:E( { Spawn = SpawnObject, Group = GroupObject } ) --- --- Results with the text [1]={Spawn={....),Group={...}} in the dcs.log. --- --- Below a more detailed explanation of the different method types for tracing. --- --- ### 1.2.1) Tracing methods categories --- --- There are basically 3 types of tracing methods available: --- --- * @{#BASE.F}: Used to trace the entrance of a function and its given parameters. An F is indicated at column 44 in the DCS.log file. --- * @{#BASE.T}: Used to trace further logic within a function giving optional variables or parameters. A T is indicated at column 44 in the DCS.log file. --- * @{#BASE.E}: Used to always trace information giving optional variables or parameters. An E is indicated at column 44 in the DCS.log file. --- --- ### 1.2.2) Tracing levels --- --- There are 3 tracing levels within MOOSE. --- These tracing levels were defined to avoid bulks of tracing to be generated by lots of objects. --- --- As such, the F and T methods have additional variants to trace level 2 and 3 respectively: --- --- * @{#BASE.F2}: Trace the beginning of a function and its given parameters with tracing level 2. --- * @{#BASE.F3}: Trace the beginning of a function and its given parameters with tracing level 3. --- * @{#BASE.T2}: Trace further logic within a function giving optional variables or parameters with tracing level 2. --- * @{#BASE.T3}: Trace further logic within a function giving optional variables or parameters with tracing level 3. --- --- ### 1.2.3) Trace activation. --- --- Tracing can be activated in several ways: --- --- * Switch tracing on or off through the @{#BASE.TraceOnOff}() method. --- * Activate all tracing through the @{#BASE.TraceAll}() method. --- * Activate only the tracing of a certain class (name) through the @{#BASE.TraceClass}() method. --- * Activate only the tracing of a certain method of a certain class through the @{#BASE.TraceClassMethod}() method. --- * Activate only the tracing of a certain level through the @{#BASE.TraceLevel}() method. --- --- ### 1.2.4) Check if tracing is on. --- --- The method @{#BASE.IsTrace}() will validate if tracing is activated or not. --- --- ## 1.3 DCS simulator Event Handling --- --- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator, --- and handled through lua scripting. MOOSE provides an encapsulation to handle these events more efficiently. --- --- ### 1.3.1 Subscribe / Unsubscribe to DCS Events --- --- At first, the mission designer will need to **Subscribe** to a specific DCS event for the class. --- So, when the DCS event occurs, the class will be notified of that event. --- There are two methods which you use to subscribe to or unsubscribe from an event. --- --- * @{#BASE.HandleEvent}(): Subscribe to a DCS Event. --- * @{#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event. --- --- ### 1.3.2 Event Handling of DCS Events --- --- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called --- when the DCS event occurs. The Event Handling method receives an @{Event#EVENTDATA} structure, which contains a lot of information --- about the event that occurred. --- --- Find below an example of the prototype how to write an event handling function for two units: --- --- local Tank1 = UNIT:FindByName( "Tank A" ) --- local Tank2 = UNIT:FindByName( "Tank B" ) --- --- -- Here we subscribe to the Dead events. So, if one of these tanks dies, the Tank1 or Tank2 objects will be notified. --- Tank1:HandleEvent( EVENTS.Dead ) --- Tank2:HandleEvent( EVENTS.Dead ) --- --- --- This function is an Event Handling function that will be called when Tank1 is Dead. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank1:OnEventDead( EventData ) --- --- self:SmokeGreen() --- end --- --- --- This function is an Event Handling function that will be called when Tank2 is Dead. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank2:OnEventDead( EventData ) --- --- self:SmokeBlue() --- end --- --- --- --- See the @{Event} module for more information about event handling. --- --- ## 1.4) Class identification methods --- --- BASE provides methods to get more information of each object: --- --- * @{#BASE.GetClassID}(): Gets the ID (number) of the object. Each object created is assigned a number, that is incremented by one. --- * @{#BASE.GetClassName}(): Gets the name of the object, which is the name of the class the object was instantiated from. --- * @{#BASE.GetClassNameAndID}(): Gets the name and ID of the object. --- --- ## 1.5) All objects derived from BASE can have "States" --- --- A mechanism is in place in MOOSE, that allows to let the objects administer **states**. --- States are essentially properties of objects, which are identified by a **Key** and a **Value**. --- --- The method @{#BASE.SetState}() can be used to set a Value with a reference Key to the object. --- To **read or retrieve** a state Value based on a Key, use the @{#BASE.GetState} method. --- --- These two methods provide a very handy way to keep state at long lasting processes. --- Values can be stored within the objects, and later retrieved or changed when needed. --- There is one other important thing to note, the @{#BASE.SetState}() and @{#BASE.GetState} methods --- receive as the **first parameter the object for which the state needs to be set**. --- Thus, if the state is to be set for the same object as the object for which the method is used, then provide the same --- object name to the method. --- --- ## 1.10) Inheritance --- --- The following methods are available to implement inheritance --- --- * @{#BASE.Inherit}: Inherits from a class. --- * @{#BASE.GetParent}: Returns the parent object from the object it is handling, or nil if there is no parent object. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- YYYY-MM-DD: CLASS:**NewFunction**( Params ) replaces CLASS:_OldFunction_( Params ) --- YYYY-MM-DD: CLASS:**NewFunction( Params )** added --- --- Hereby the change log: --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * None. --- --- ### Authors: --- --- * **FlightControl**: Design & Programming --- --- @module Base - - - -local _TraceOnOff = true -local _TraceLevel = 1 -local _TraceAll = false -local _TraceClass = {} -local _TraceClassMethod = {} - -local _ClassID = 0 - ---- The BASE Class --- @type BASE --- @field ClassName The name of the class. --- @field ClassID The ID number of the class. --- @field ClassNameAndID The name of the class concatenated with the ID number of the class. -BASE = { - ClassName = "BASE", - ClassID = 0, - _Private = {}, - Events = {}, - States = {} -} - ---- The Formation Class --- @type FORMATION --- @field Cone A cone formation. -FORMATION = { - Cone = "Cone" -} - - - ---- BASE constructor. --- --- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE. --- --- function EVENT:New() --- local self = BASE:Inherit( self, BASE:New() ) -- #EVENT --- return self --- end --- --- @param #BASE self --- @return #BASE -function BASE:New() - local self = routines.utils.deepCopy( self ) -- Create a new self instance - local MetaTable = {} - setmetatable( self, MetaTable ) - self.__index = self - _ClassID = _ClassID + 1 - self.ClassID = _ClassID - - - return self -end - -function BASE:_Destructor() - --self:E("_Destructor") - - --self:EventRemoveAll() -end - - --- THIS IS WHY WE NEED LUA 5.2 ... -function BASE:_SetDestructor() - - -- TODO: Okay, this is really technical... - -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak... - -- Therefore, I am parking this logic until I've properly discussed all this with the community. - - local proxy = newproxy(true) - local proxyMeta = getmetatable(proxy) - - proxyMeta.__gc = function () - env.info("In __gc for " .. self:GetClassNameAndID() ) - if self._Destructor then - self:_Destructor() - end - end - - -- keep the userdata from newproxy reachable until the object - -- table is about to be garbage-collected - then the __gc hook - -- will be invoked and the destructor called - rawset( self, '__proxy', proxy ) - -end - ---- This is the worker method to inherit from a parent class. --- @param #BASE self --- @param Child is the Child class that inherits. --- @param #BASE Parent is the Parent class that the Child inherits from. --- @return #BASE Child -function BASE:Inherit( Child, Parent ) - local Child = routines.utils.deepCopy( Child ) - --local Parent = routines.utils.deepCopy( Parent ) - --local Parent = Parent - if Child ~= nil then - setmetatable( Child, Parent ) - Child.__index = Child - - --Child:_SetDestructor() - end - --self:T( 'Inherited from ' .. Parent.ClassName ) - return Child -end - ---- This is the worker method to retrieve the Parent class. --- Note that the Parent class must be passed to call the parent class method. --- --- self:GetParent(self):ParentMethod() --- --- --- @param #BASE self --- @param #BASE Child is the Child class from which the Parent class needs to be retrieved. --- @return #BASE -function BASE:GetParent( Child ) - local Parent = getmetatable( Child ) --- env.info('Inherited class of ' .. Child.ClassName .. ' is ' .. Parent.ClassName ) - return Parent -end - ---- Get the ClassName + ClassID of the class instance. --- The ClassName + ClassID is formatted as '%s#%09d'. --- @param #BASE self --- @return #string The ClassName + ClassID of the class instance. -function BASE:GetClassNameAndID() - return string.format( '%s#%09d', self.ClassName, self.ClassID ) -end - ---- Get the ClassName of the class instance. --- @param #BASE self --- @return #string The ClassName of the class instance. -function BASE:GetClassName() - return self.ClassName -end - ---- Get the ClassID of the class instance. --- @param #BASE self --- @return #string The ClassID of the class instance. -function BASE:GetClassID() - return self.ClassID -end - -do -- Event Handling - - --- Returns the event dispatcher - -- @param #BASE self - -- @return Core.Event#EVENT - function BASE:EventDispatcher() - - return _EVENTDISPATCHER - end - - - --- Get the Class @{Event} processing Priority. - -- The Event processing Priority is a number from 1 to 10, - -- reflecting the order of the classes subscribed to the Event to be processed. - -- @param #BASE self - -- @return #number The @{Event} processing Priority. - function BASE:GetEventPriority() - return self._Private.EventPriority or 5 - end - - --- Set the Class @{Event} processing Priority. - -- The Event processing Priority is a number from 1 to 10, - -- reflecting the order of the classes subscribed to the Event to be processed. - -- @param #BASE self - -- @param #number EventPriority The @{Event} processing Priority. - -- @return self - function BASE:SetEventPriority( EventPriority ) - self._Private.EventPriority = EventPriority - end - - --- Remove all subscribed events - -- @param #BASE self - -- @return #BASE - function BASE:EventRemoveAll() - - self:EventDispatcher():RemoveAll( self ) - - return self - end - - --- Subscribe to a DCS Event. - -- @param #BASE self - -- @param Core.Event#EVENTS Event - -- @param #function EventFunction (optional) The function to be called when the event occurs for the unit. - -- @return #BASE - function BASE:HandleEvent( Event, EventFunction ) - - self:EventDispatcher():OnEventGeneric( EventFunction, self, Event ) - - return self - end - - --- UnSubscribe to a DCS event. - -- @param #BASE self - -- @param Core.Event#EVENTS Event - -- @return #BASE - function BASE:UnHandleEvent( Event ) - - self:EventDispatcher():Remove( self, Event ) - - return self - end - - -- Event handling function prototypes - - --- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or autocannon based weapon, those are handled by EVENT.ShootingStart. - -- @function [parent=#BASE] OnEventShot - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs whenever an object is hit by a weapon. - -- initiator : The unit object the fired the weapon - -- weapon: Weapon object that hit the target - -- target: The Object that was hit. - -- @function [parent=#BASE] OnEventHit - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an aircraft takes off from an airbase, farp, or ship. - -- initiator : The unit that tookoff - -- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships - -- @function [parent=#BASE] OnEventTakeoff - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an aircraft lands at an airbase, farp or ship - -- initiator : The unit that has landed - -- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships - -- @function [parent=#BASE] OnEventLand - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any aircraft crashes into the ground and is completely destroyed. - -- initiator : The unit that has crashed - -- @function [parent=#BASE] OnEventCrash - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when a pilot ejects from an aircraft - -- initiator : The unit that has ejected - -- @function [parent=#BASE] OnEventEjection - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an aircraft connects with a tanker and begins taking on fuel. - -- initiator : The unit that is receiving fuel. - -- @function [parent=#BASE] OnEventRefueling - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an object is completely destroyed. - -- initiator : The unit that is was destroyed. - -- @function [parent=#BASE] OnEvent - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when the pilot of an aircraft is killed. Can occur either if the player is alive and crashes or if a weapon kills the pilot without completely destroying the plane. - -- initiator : The unit that the pilot has died in. - -- @function [parent=#BASE] OnEventPilotDead - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when a ground unit captures either an airbase or a farp. - -- initiator : The unit that captured the base - -- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction. - -- @function [parent=#BASE] OnEventBaseCaptured - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when a mission starts - -- @function [parent=#BASE] OnEventMissionStart - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when a mission ends - -- @function [parent=#BASE] OnEventMissionEnd - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when an aircraft is finished taking fuel. - -- initiator : The unit that was receiving fuel. - -- @function [parent=#BASE] OnEventRefuelingStop - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any object is spawned into the mission. - -- initiator : The unit that was spawned - -- @function [parent=#BASE] OnEventBirth - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any system fails on a human controlled aircraft. - -- initiator : The unit that had the failure - -- @function [parent=#BASE] OnEventHumanFailure - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any aircraft starts its engines. - -- initiator : The unit that is starting its engines. - -- @function [parent=#BASE] OnEventEngineStartup - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any aircraft shuts down its engines. - -- initiator : The unit that is stopping its engines. - -- @function [parent=#BASE] OnEventEngineShutdown - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any player assumes direct control of a unit. - -- initiator : The unit that is being taken control of. - -- @function [parent=#BASE] OnEventPlayerEnterUnit - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any player relieves control of a unit to the AI. - -- initiator : The unit that the player left. - -- @function [parent=#BASE] OnEventPlayerLeaveUnit - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns. - -- initiator : The unit that is doing the shooing. - -- target: The unit that is being targeted. - -- @function [parent=#BASE] OnEventShootingStart - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - - --- Occurs when any unit stops firing its weapon. Event will always correspond with a shooting start event. - -- initiator : The unit that was doing the shooing. - -- @function [parent=#BASE] OnEventShootingEnd - -- @param #BASE self - -- @param Core.Event#EVENTDATA EventData The EventData structure. - -end - - ---- Creation of a Birth Event. --- @param #BASE self --- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. --- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. --- @param #string IniUnitName The initiating unit name. --- @param place --- @param subplace -function BASE:CreateEventBirth( EventTime, Initiator, IniUnitName, place, subplace ) - self:F( { EventTime, Initiator, IniUnitName, place, subplace } ) - - local Event = { - id = world.event.S_EVENT_BIRTH, - time = EventTime, - initiator = Initiator, - IniUnitName = IniUnitName, - place = place, - subplace = subplace - } - - world.onEvent( Event ) -end - ---- Creation of a Crash Event. --- @param #BASE self --- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. --- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. -function BASE:CreateEventCrash( EventTime, Initiator ) - self:F( { EventTime, Initiator } ) - - local Event = { - id = world.event.S_EVENT_CRASH, - time = EventTime, - initiator = Initiator, - } - - world.onEvent( Event ) -end - --- TODO: Complete Dcs.DCSTypes#Event structure. ---- The main event handling function... This function captures all events generated for the class. --- @param #BASE self --- @param Dcs.DCSTypes#Event event -function BASE:onEvent(event) - --self:F( { BaseEventCodes[event.id], event } ) - - if self then - for EventID, EventObject in pairs( self.Events ) do - if EventObject.EventEnabled then - --env.info( 'onEvent Table EventObject.Self = ' .. tostring(EventObject.Self) ) - --env.info( 'onEvent event.id = ' .. tostring(event.id) ) - --env.info( 'onEvent EventObject.Event = ' .. tostring(EventObject.Event) ) - if event.id == EventObject.Event then - if self == EventObject.Self then - if event.initiator and event.initiator:isExist() then - event.IniUnitName = event.initiator:getName() - end - if event.target and event.target:isExist() then - event.TgtUnitName = event.target:getName() - end - --self:T( { BaseEventCodes[event.id], event } ) - --EventObject.EventFunction( self, event ) - end - end - end - end - end -end - ---- Set a state or property of the Object given a Key and a Value. --- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone. --- @param #BASE self --- @param Object The object that will hold the Value set by the Key. --- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! --- @param Value The value to is stored in the object. --- @return The Value set. --- @return #nil The Key was not found and thus the Value could not be retrieved. -function BASE:SetState( Object, Key, Value ) - - local ClassNameAndID = Object:GetClassNameAndID() - - self.States[ClassNameAndID] = self.States[ClassNameAndID] or {} - self.States[ClassNameAndID][Key] = Value - self:T2( { ClassNameAndID, Key, Value } ) - - return self.States[ClassNameAndID][Key] -end - - ---- Get a Value given a Key from the Object. --- Note that if the Object is destroyed, nillified or garbage collected, then the Values and Keys will also be gone. --- @param #BASE self --- @param Object The object that holds the Value set by the Key. --- @param Key The key that is used to retrieve the value. Note that the key can be a #string, but it can also be any other type! --- @param Value The value to is stored in the Object. --- @return The Value retrieved. -function BASE:GetState( Object, Key ) - - local ClassNameAndID = Object:GetClassNameAndID() - - if self.States[ClassNameAndID] then - local Value = self.States[ClassNameAndID][Key] or false - self:T2( { ClassNameAndID, Key, Value } ) - return Value - end - - return nil -end - -function BASE:ClearState( Object, StateName ) - - local ClassNameAndID = Object:GetClassNameAndID() - if self.States[ClassNameAndID] then - self.States[ClassNameAndID][StateName] = nil - end -end - --- Trace section - --- Log a trace (only shown when trace is on) --- TODO: Make trace function using variable parameters. - ---- Set trace on or off --- Note that when trace is off, no debug statement is performed, increasing performance! --- When Moose is loaded statically, (as one file), tracing is switched off by default. --- So tracing must be switched on manually in your mission if you are using Moose statically. --- When moose is loading dynamically (for moose class development), tracing is switched on by default. --- @param #BASE self --- @param #boolean TraceOnOff Switch the tracing on or off. --- @usage --- -- Switch the tracing On --- BASE:TraceOnOff( true ) --- --- -- Switch the tracing Off --- BASE:TraceOnOff( false ) -function BASE:TraceOnOff( TraceOnOff ) - _TraceOnOff = TraceOnOff -end - - ---- Enquires if tracing is on (for the class). --- @param #BASE self --- @return #boolean -function BASE:IsTrace() - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - return true - else - return false - end -end - ---- Set trace level --- @param #BASE self --- @param #number Level -function BASE:TraceLevel( Level ) - _TraceLevel = Level - self:E( "Tracing level " .. Level ) -end - ---- Trace all methods in MOOSE --- @param #BASE self --- @param #boolean TraceAll true = trace all methods in MOOSE. -function BASE:TraceAll( TraceAll ) - - _TraceAll = TraceAll - - if _TraceAll then - self:E( "Tracing all methods in MOOSE " ) - else - self:E( "Switched off tracing all methods in MOOSE" ) - end -end - ---- Set tracing for a class --- @param #BASE self --- @param #string Class -function BASE:TraceClass( Class ) - _TraceClass[Class] = true - _TraceClassMethod[Class] = {} - self:E( "Tracing class " .. Class ) -end - ---- Set tracing for a specific method of class --- @param #BASE self --- @param #string Class --- @param #string Method -function BASE:TraceClassMethod( Class, Method ) - if not _TraceClassMethod[Class] then - _TraceClassMethod[Class] = {} - _TraceClassMethod[Class].Method = {} - end - _TraceClassMethod[Class].Method[Method] = true - self:E( "Tracing method " .. Method .. " of class " .. Class ) -end - ---- Trace a function call. This function is private. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - - local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo( 2, "nl" ) - local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - if _TraceAll == true or _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName].Method[Function] then - local LineCurrent = 0 - if DebugInfoCurrent.currentline then - LineCurrent = DebugInfoCurrent.currentline - end - local LineFrom = 0 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) - end - end -end - ---- Trace a function call. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 1 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - - ---- Trace a function call level 2. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F2( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 2 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function call level 3. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F3( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 3 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - - local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo( 2, "nl" ) - local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - if _TraceAll == true or _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName].Method[Function] then - local LineCurrent = 0 - if DebugInfoCurrent.currentline then - LineCurrent = DebugInfoCurrent.currentline - end - local LineFrom = 0 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) - end - end -end - ---- Trace a function logic level 1. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 1 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - - ---- Trace a function logic level 2. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T2( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 2 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function logic level 3. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T3( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 3 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Log an exception which will be traced always. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:E( Arguments ) - - if debug then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - local LineCurrent = DebugInfoCurrent.currentline - local LineFrom = -1 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) - end - -end - - - ---- **Core** - SCHEDULER prepares and handles the **execution of functions over scheduled time (intervals)**. --- --- ![Banner Image](..\Presentations\SCHEDULER\Dia1.JPG) --- --- === --- --- # 1) @{Scheduler#SCHEDULER} class, extends @{Base#BASE} --- --- The @{Scheduler#SCHEDULER} class creates schedule. --- --- ## 1.1) SCHEDULER constructor --- --- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: --- --- * @{Scheduler#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. --- * @{Scheduler#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. --- * @{Scheduler#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. --- * @{Scheduler#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. --- --- ## 1.2) SCHEDULER timer stopping and (re-)starting. --- --- The SCHEDULER can be stopped and restarted with the following methods: --- --- * @{Scheduler#SCHEDULER.Start}(): (Re-)Start the schedules within the SCHEDULER object. If a CallID is provided to :Start(), only the schedule referenced by CallID will be (re-)started. --- * @{Scheduler#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped. --- --- ## 1.3) Create a new schedule --- --- With @{Scheduler#SCHEDULER.Schedule}() a new time event can be scheduled. This function is used by the :New() constructor when a new schedule is planned. --- --- === --- --- ### Contributions: --- --- * FlightControl : Concept & Testing --- --- ### Authors: --- --- * FlightControl : Design & Programming --- --- ### Test Missions: --- --- * SCH - Scheduler --- --- === --- --- @module Scheduler - - ---- The SCHEDULER class --- @type SCHEDULER --- @field #number ScheduleID the ID of the scheduler. --- @extends Core.Base#BASE -SCHEDULER = { - ClassName = "SCHEDULER", - Schedules = {}, -} - ---- SCHEDULER constructor. --- @param #SCHEDULER self --- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. --- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. --- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. --- @return #SCHEDULER self. --- @return #number The ScheduleID of the planned schedule. -function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( { Start, Repeat, RandomizeFactor, Stop } ) - - local ScheduleID = nil - - self.MasterObject = SchedulerObject - - if SchedulerFunction then - ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - end - - return self, ScheduleID -end - ---function SCHEDULER:_Destructor() --- --self:E("_Destructor") --- --- _SCHEDULEDISPATCHER:RemoveSchedule( self.CallID ) ---end - ---- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also. --- @param #SCHEDULER self --- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. --- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. --- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. --- @return #number The ScheduleID of the planned schedule. -function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - self:F2( { Start, Repeat, RandomizeFactor, Stop } ) - self:T3( { SchedulerArguments } ) - - local ObjectName = "-" - if SchedulerObject and SchedulerObject.ClassName and SchedulerObject.ClassID then - ObjectName = SchedulerObject.ClassName .. SchedulerObject.ClassID - end - self:F3( { "Schedule :", ObjectName, tostring( SchedulerObject ), Start, Repeat, RandomizeFactor, Stop } ) - self.SchedulerObject = SchedulerObject - - local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( - self, - SchedulerFunction, - SchedulerArguments, - Start, - Repeat, - RandomizeFactor, - Stop - ) - - self.Schedules[#self.Schedules+1] = ScheduleID - - return ScheduleID -end - ---- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided. --- @param #SCHEDULER self --- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. -function SCHEDULER:Start( ScheduleID ) - self:F3( { ScheduleID } ) - - _SCHEDULEDISPATCHER:Start( self, ScheduleID ) -end - ---- Stops the schedules or a specific schedule if a valid ScheduleID is provided. --- @param #SCHEDULER self --- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. -function SCHEDULER:Stop( ScheduleID ) - self:F3( { ScheduleID } ) - - _SCHEDULEDISPATCHER:Stop( self, ScheduleID ) -end - ---- Removes a specific schedule if a valid ScheduleID is provided. --- @param #SCHEDULER self --- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. -function SCHEDULER:Remove( ScheduleID ) - self:F3( { ScheduleID } ) - - _SCHEDULEDISPATCHER:Remove( self, ScheduleID ) -end - ---- Clears all pending schedules. --- @param #SCHEDULER self -function SCHEDULER:Clear() - self:F3( ) - - _SCHEDULEDISPATCHER:Clear( self ) -end - - - - - - - - - - - - - - ---- This module defines the SCHEDULEDISPATCHER class, which is used by a central object called _SCHEDULEDISPATCHER. --- --- === --- --- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. --- --- This class is tricky and needs some thorought explanation. --- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. --- The SCHEDULEDISPATCHER class ensures that: --- --- - Scheduled functions are planned according the SCHEDULER object parameters. --- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. --- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. --- --- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection: --- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER --- object is _persistent_ within memory. --- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection! --- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected. --- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, --- these will not be executed anymore when the SCHEDULER object has been destroyed. --- --- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. --- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. --- The SCHEDULER object plans new scheduled functions through the @{Scheduler#SCHEDULER.Schedule}() method. --- The Schedule() method returns the CallID that is the reference ID for each planned schedule. --- --- === --- --- === --- --- ### Contributions: - --- ### Authors: FlightControl : Design & Programming --- --- @module ScheduleDispatcher - ---- The SCHEDULEDISPATCHER structure --- @type SCHEDULEDISPATCHER -SCHEDULEDISPATCHER = { - ClassName = "SCHEDULEDISPATCHER", - CallID = 0, -} - -function SCHEDULEDISPATCHER:New() - local self = BASE:Inherit( self, BASE:New() ) - self:F3() - return self -end - ---- Add a Schedule to the ScheduleDispatcher. --- The development of this method was really tidy. --- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. --- Nothing of this code should be modified without testing it thoroughly. --- @param #SCHEDULEDISPATCHER self --- @param Core.Scheduler#SCHEDULER Scheduler -function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop ) - self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } ) - - self.CallID = self.CallID + 1 - - -- Initialize the ObjectSchedulers array, which is a weakly coupled table. - -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. - self.PersistentSchedulers = self.PersistentSchedulers or {} - - -- Initialize the ObjectSchedulers array, which is a weakly coupled table. - -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. - self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) -- or {} - - if Scheduler.MasterObject then - self.ObjectSchedulers[self.CallID] = Scheduler - self:F3( { CallID = self.CallID, ObjectScheduler = tostring(self.ObjectSchedulers[self.CallID]), MasterObject = tostring(Scheduler.MasterObject) } ) - else - self.PersistentSchedulers[self.CallID] = Scheduler - self:F3( { CallID = self.CallID, PersistentScheduler = self.PersistentSchedulers[self.CallID] } ) - end - - self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } ) - self.Schedule[Scheduler] = self.Schedule[Scheduler] or {} - self.Schedule[Scheduler][self.CallID] = {} - self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction - self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments - self.Schedule[Scheduler][self.CallID].StartTime = timer.getTime() + ( Start or 0 ) - self.Schedule[Scheduler][self.CallID].Start = Start + .1 - self.Schedule[Scheduler][self.CallID].Repeat = Repeat - self.Schedule[Scheduler][self.CallID].Randomize = Randomize - self.Schedule[Scheduler][self.CallID].Stop = Stop - - self:T3( self.Schedule[Scheduler][self.CallID] ) - - self.Schedule[Scheduler][self.CallID].CallHandler = function( CallID ) - self:F2( CallID ) - - local ErrorHandler = function( errmsg ) - env.info( "Error in timer function: " .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - return errmsg - end - - local Scheduler = self.ObjectSchedulers[CallID] - if not Scheduler then - Scheduler = self.PersistentSchedulers[CallID] - end - - self:T3( { Scheduler = Scheduler } ) - - if Scheduler then - - local Schedule = self.Schedule[Scheduler][CallID] - - self:T3( { Schedule = Schedule } ) - - local ScheduleObject = Scheduler.SchedulerObject - --local ScheduleObjectName = Scheduler.SchedulerObject:GetNameAndClassID() - local ScheduleFunction = Schedule.Function - local ScheduleArguments = Schedule.Arguments - local Start = Schedule.Start - local Repeat = Schedule.Repeat or 0 - local Randomize = Schedule.Randomize or 0 - local Stop = Schedule.Stop or 0 - local ScheduleID = Schedule.ScheduleID - - local Status, Result - if ScheduleObject then - local function Timer() - return ScheduleFunction( ScheduleObject, unpack( ScheduleArguments ) ) - end - Status, Result = xpcall( Timer, ErrorHandler ) - else - local function Timer() - return ScheduleFunction( unpack( ScheduleArguments ) ) - end - Status, Result = xpcall( Timer, ErrorHandler ) - end - - local CurrentTime = timer.getTime() - local StartTime = CurrentTime + Start - - if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then - if Repeat ~= 0 and ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) then - local ScheduleTime = - CurrentTime + - Repeat + - math.random( - - ( Randomize * Repeat / 2 ), - ( Randomize * Repeat / 2 ) - ) + - 0.01 - self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) - return ScheduleTime -- returns the next time the function needs to be called. - else - self:Stop( Scheduler, CallID ) - end - else - self:Stop( Scheduler, CallID ) - end - else - self:E( "Scheduled obscolete call for CallID: " .. CallID ) - end - - return nil - end - - self:Start( Scheduler, self.CallID ) - - return self.CallID -end - -function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID ) - self:F2( { Remove = CallID, Scheduler = Scheduler } ) - - if CallID then - self:Stop( Scheduler, CallID ) - self.Schedule[Scheduler][CallID] = nil - end -end - -function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) - self:F2( { Start = CallID, Scheduler = Scheduler } ) - - if CallID then - local Schedule = self.Schedule[Scheduler] - -- Only start when there is no ScheduleID defined! - -- This prevents to "Start" the scheduler twice with the same CallID... - if not Schedule[CallID].ScheduleID then - Schedule[CallID].ScheduleID = timer.scheduleFunction( - Schedule[CallID].CallHandler, - CallID, - timer.getTime() + Schedule[CallID].Start - ) - end - else - for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do - self:Start( Scheduler, CallID ) -- Recursive - end - end -end - -function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) - self:F2( { Stop = CallID, Scheduler = Scheduler } ) - - if CallID then - local Schedule = self.Schedule[Scheduler] - -- Only stop when there is a ScheduleID defined for the CallID. - -- So, when the scheduler was stopped before, do nothing. - if Schedule[CallID].ScheduleID then - timer.removeFunction( Schedule[CallID].ScheduleID ) - Schedule[CallID].ScheduleID = nil - end - else - for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do - self:Stop( Scheduler, CallID ) -- Recursive - end - end -end - -function SCHEDULEDISPATCHER:Clear( Scheduler ) - self:F2( { Scheduler = Scheduler } ) - - for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do - self:Stop( Scheduler, CallID ) -- Recursive - end -end - - - ---- **Core** - EVENT models DCS **event dispatching** using a **publish-subscribe** model. --- --- ![Banner Image](..\Presentations\EVENT\Dia1.JPG) --- --- === --- --- # 1) Event Handling Overview --- --- ![Objects](..\Presentations\EVENT\Dia2.JPG) --- --- Within a running mission, various DCS events occur. Units are dynamically created, crash, die, shoot stuff, get hit etc. --- This module provides a mechanism to dispatch those events occuring within your running mission, to the different objects orchestrating your mission. --- --- ![Objects](..\Presentations\EVENT\Dia3.JPG) --- --- Objects can subscribe to different events. The Event dispatcher will publish the received DCS events to the subscribed MOOSE objects, in a specified order. --- In this way, the subscribed MOOSE objects are kept in sync with your evolving running mission. --- --- ## 1.1) Event Dispatching --- --- ![Objects](..\Presentations\EVENT\Dia4.JPG) --- --- The _EVENTDISPATCHER object is automatically created within MOOSE, --- and handles the dispatching of DCS Events occurring --- in the simulator to the subscribed objects --- in the correct processing order. --- --- ![Objects](..\Presentations\EVENT\Dia5.JPG) --- --- There are 5 levels of kind of objects that the _EVENTDISPATCHER services: --- --- * _DATABASE object: The core of the MOOSE objects. Any object that is created, deleted or updated, is done in this database. --- * SET_ derived classes: Subsets of the _DATABASE object. These subsets are updated by the _EVENTDISPATCHER as the second priority. --- * UNIT objects: UNIT objects can subscribe to DCS events. Each DCS event will be directly published to teh subscribed UNIT object. --- * GROUP objects: GROUP objects can subscribe to DCS events. Each DCS event will be directly published to the subscribed GROUP object. --- * Any other object: Various other objects can subscribe to DCS events. Each DCS event triggered will be published to each subscribed object. --- --- ![Objects](..\Presentations\EVENT\Dia6.JPG) --- --- For most DCS events, the above order of updating will be followed. --- --- ![Objects](..\Presentations\EVENT\Dia7.JPG) --- --- But for some DCS events, the publishing order is reversed. This is due to the fact that objects need to be **erased** instead of added. --- --- ## 1.2) Event Handling --- --- ![Objects](..\Presentations\EVENT\Dia8.JPG) --- --- The actual event subscribing and handling is not facilitated through the _EVENTDISPATCHER, but it is done through the @{BASE} class, @{UNIT} class and @{GROUP} class. --- The _EVENTDISPATCHER is a component that is quietly working in the background of MOOSE. --- --- ![Objects](..\Presentations\EVENT\Dia9.JPG) --- --- The BASE class provides methods to catch DCS Events. These are events that are triggered from within the DCS simulator, --- and handled through lua scripting. MOOSE provides an encapsulation to handle these events more efficiently. --- --- ### 1.2.1 Subscribe / Unsubscribe to DCS Events --- --- At first, the mission designer will need to **Subscribe** to a specific DCS event for the class. --- So, when the DCS event occurs, the class will be notified of that event. --- There are two functions which you use to subscribe to or unsubscribe from an event. --- --- * @{Base#BASE.HandleEvent}(): Subscribe to a DCS Event. --- * @{Base#BASE.UnHandleEvent}(): Unsubscribe from a DCS Event. --- --- Note that for a UNIT, the event will be handled **for that UNIT only**! --- Note that for a GROUP, the event will be handled **for all the UNITs in that GROUP only**! --- --- For all objects of other classes, the subscribed events will be handled for **all UNITs within the Mission**! --- So if a UNIT within the mission has the subscribed event for that object, --- then the object event handler will receive the event for that UNIT! --- --- ### 1.3.2 Event Handling of DCS Events --- --- Once the class is subscribed to the event, an **Event Handling** method on the object or class needs to be written that will be called --- when the DCS event occurs. The Event Handling method receives an @{Event#EVENTDATA} structure, which contains a lot of information --- about the event that occurred. --- --- Find below an example of the prototype how to write an event handling function for two units: --- --- local Tank1 = UNIT:FindByName( "Tank A" ) --- local Tank2 = UNIT:FindByName( "Tank B" ) --- --- -- Here we subscribe to the Dead events. So, if one of these tanks dies, the Tank1 or Tank2 objects will be notified. --- Tank1:HandleEvent( EVENTS.Dead ) --- Tank2:HandleEvent( EVENTS.Dead ) --- --- --- This function is an Event Handling function that will be called when Tank1 is Dead. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank1:OnEventDead( EventData ) --- --- self:SmokeGreen() --- end --- --- --- This function is an Event Handling function that will be called when Tank2 is Dead. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank2:OnEventDead( EventData ) --- --- self:SmokeBlue() --- end --- --- ### 1.3.3 Event Handling methods that are automatically called upon subscribed DCS events --- --- ![Objects](..\Presentations\EVENT\Dia10.JPG) --- --- The following list outlines which EVENTS item in the structure corresponds to which Event Handling method. --- Always ensure that your event handling methods align with the events being subscribed to, or nothing will be executed. --- --- # 2) EVENTS type --- --- The EVENTS structure contains names for all the different DCS events that objects can subscribe to using the --- @{Base#BASE.HandleEvent}() method. --- --- # 3) EVENTDATA type --- --- The @{Event#EVENTDATA} structure contains all the fields that are populated with event information before --- an Event Handler method is being called by the event dispatcher. --- The Event Handler received the EVENTDATA object as a parameter, and can be used to investigate further the different events. --- There are basically 4 main categories of information stored in the EVENTDATA structure: --- --- * Initiator Unit data: Several fields documenting the initiator unit related to the event. --- * Target Unit data: Several fields documenting the target unit related to the event. --- * Weapon data: Certain events populate weapon information. --- * Place data: Certain events populate place information. --- --- --- This function is an Event Handling function that will be called when Tank1 is Dead. --- -- EventData is an EVENTDATA structure. --- -- We use the EventData.IniUnit to smoke the tank Green. --- -- @param Wrapper.Unit#UNIT self --- -- @param Core.Event#EVENTDATA EventData --- function Tank1:OnEventDead( EventData ) --- --- EventData.IniUnit:SmokeGreen() --- end --- --- --- Find below an overview which events populate which information categories: --- --- ![Objects](..\Presentations\EVENT\Dia14.JPG) --- --- **IMPORTANT NOTE:** Some events can involve not just UNIT objects, but also STATIC objects!!! --- In that case the initiator or target unit fields will refer to a STATIC object! --- In case a STATIC object is involved, the documentation indicates which fields will and won't not be populated. --- The fields **IniObjectCategory** and **TgtObjectCategory** contain the indicator which **kind of object is involved** in the event. --- You can use the enumerator **Object.Category.UNIT** and **Object.Category.STATIC** to check on IniObjectCategory and TgtObjectCategory. --- Example code snippet: --- --- if Event.IniObjectCategory == Object.Category.UNIT then --- ... --- end --- if Event.IniObjectCategory == Object.Category.STATIC then --- ... --- end --- --- When a static object is involved in the event, the Group and Player fields won't be populated. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- YYYY-MM-DD: CLASS:**NewFunction**( Params ) replaces CLASS:_OldFunction_( Params ) --- YYYY-MM-DD: CLASS:**NewFunction( Params )** added --- --- Hereby the change log: --- --- * 2017-03-07: Added the correct event dispatching in case the event is subscribed by a GROUP. --- --- * 2017-02-07: Did a complete revision of the Event Handing API and underlying mechanisms. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- ### Authors: --- --- * [**FlightControl**](https://forums.eagle.ru/member.php?u=89536): Design & Programming & documentation. --- --- @module Event - - ---- The EVENT structure --- @type EVENT --- @field #EVENT.Events Events --- @extends Core.Base#BASE -EVENT = { - ClassName = "EVENT", - ClassID = 0, -} - ---- The different types of events supported by MOOSE. --- Use this structure to subscribe to events using the @{Base#BASE.HandleEvent}() method. --- @type EVENTS -EVENTS = { - Shot = world.event.S_EVENT_SHOT, - Hit = world.event.S_EVENT_HIT, - Takeoff = world.event.S_EVENT_TAKEOFF, - Land = world.event.S_EVENT_LAND, - Crash = world.event.S_EVENT_CRASH, - Ejection = world.event.S_EVENT_EJECTION, - Refueling = world.event.S_EVENT_REFUELING, - Dead = world.event.S_EVENT_DEAD, - PilotDead = world.event.S_EVENT_PILOT_DEAD, - BaseCaptured = world.event.S_EVENT_BASE_CAPTURED, - MissionStart = world.event.S_EVENT_MISSION_START, - MissionEnd = world.event.S_EVENT_MISSION_END, - TookControl = world.event.S_EVENT_TOOK_CONTROL, - RefuelingStop = world.event.S_EVENT_REFUELING_STOP, - Birth = world.event.S_EVENT_BIRTH, - HumanFailure = world.event.S_EVENT_HUMAN_FAILURE, - EngineStartup = world.event.S_EVENT_ENGINE_STARTUP, - EngineShutdown = world.event.S_EVENT_ENGINE_SHUTDOWN, - PlayerEnterUnit = world.event.S_EVENT_PLAYER_ENTER_UNIT, - PlayerLeaveUnit = world.event.S_EVENT_PLAYER_LEAVE_UNIT, - PlayerComment = world.event.S_EVENT_PLAYER_COMMENT, - ShootingStart = world.event.S_EVENT_SHOOTING_START, - ShootingEnd = world.event.S_EVENT_SHOOTING_END, -} - ---- The Event structure --- Note that at the beginning of each field description, there is an indication which field will be populated depending on the object type involved in the Event: --- --- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event. --- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ --- --- @type EVENTDATA --- @field #number id The identifier of the event. --- --- @field Dcs.DCSUnit#Unit initiator (UNIT/STATIC/SCENERY) The initiating @{Dcs.DCSUnit#Unit} or @{Dcs.DCSStaticObject#StaticObject}. --- @field Dcs.DCSObject#Object.Category IniObjectCategory (UNIT/STATIC/SCENERY) The initiator object category ( Object.Category.UNIT or Object.Category.STATIC ). --- @field Dcs.DCSUnit#Unit IniDCSUnit (UNIT/STATIC) The initiating @{DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. --- @field #string IniDCSUnitName (UNIT/STATIC) The initiating Unit name. --- @field Wrapper.Unit#UNIT IniUnit (UNIT/STATIC) The initiating MOOSE wrapper @{Unit#UNIT} of the initiator Unit object. --- @field #string IniUnitName (UNIT/STATIC) The initiating UNIT name (same as IniDCSUnitName). --- @field Dcs.DCSGroup#Group IniDCSGroup (UNIT) The initiating {DCSGroup#Group}. --- @field #string IniDCSGroupName (UNIT) The initiating Group name. --- @field Wrapper.Group#GROUP IniGroup (UNIT) The initiating MOOSE wrapper @{Group#GROUP} of the initiator Group object. --- @field #string IniGroupName UNIT) The initiating GROUP name (same as IniDCSGroupName). --- @field #string IniPlayerName (UNIT) The name of the initiating player in case the Unit is a client or player slot. --- @field Dcs.DCScoalition#coalition.side IniCoalition (UNIT) The coalition of the initiator. --- @field Dcs.DCSUnit#Unit.Category IniCategory (UNIT) The category of the initiator. --- @field #string IniTypeName (UNIT) The type name of the initiator. --- --- @field Dcs.DCSUnit#Unit target (UNIT/STATIC) The target @{Dcs.DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. --- @field Dcs.DCSObject#Object.Category TgtObjectCategory (UNIT/STATIC) The target object category ( Object.Category.UNIT or Object.Category.STATIC ). --- @field Dcs.DCSUnit#Unit TgtDCSUnit (UNIT/STATIC) The target @{DCSUnit#Unit} or @{DCSStaticObject#StaticObject}. --- @field #string TgtDCSUnitName (UNIT/STATIC) The target Unit name. --- @field Wrapper.Unit#UNIT TgtUnit (UNIT/STATIC) The target MOOSE wrapper @{Unit#UNIT} of the target Unit object. --- @field #string TgtUnitName (UNIT/STATIC) The target UNIT name (same as TgtDCSUnitName). --- @field Dcs.DCSGroup#Group TgtDCSGroup (UNIT) The target {DCSGroup#Group}. --- @field #string TgtDCSGroupName (UNIT) The target Group name. --- @field Wrapper.Group#GROUP TgtGroup (UNIT) The target MOOSE wrapper @{Group#GROUP} of the target Group object. --- @field #string TgtGroupName (UNIT) The target GROUP name (same as TgtDCSGroupName). --- @field #string TgtPlayerName (UNIT) The name of the target player in case the Unit is a client or player slot. --- @field Dcs.DCScoalition#coalition.side TgtCoalition (UNIT) The coalition of the target. --- @field Dcs.DCSUnit#Unit.Category TgtCategory (UNIT) The category of the target. --- @field #string TgtTypeName (UNIT) The type name of the target. --- --- @field weapon The weapon used during the event. --- @field Weapon --- @field WeaponName --- @field WeaponTgtDCSUnit - - -local _EVENTMETA = { - [world.event.S_EVENT_SHOT] = { - Order = 1, - Event = "OnEventShot", - Text = "S_EVENT_SHOT" - }, - [world.event.S_EVENT_HIT] = { - Order = 1, - Event = "OnEventHit", - Text = "S_EVENT_HIT" - }, - [world.event.S_EVENT_TAKEOFF] = { - Order = 1, - Event = "OnEventTakeoff", - Text = "S_EVENT_TAKEOFF" - }, - [world.event.S_EVENT_LAND] = { - Order = 1, - Event = "OnEventLand", - Text = "S_EVENT_LAND" - }, - [world.event.S_EVENT_CRASH] = { - Order = -1, - Event = "OnEventCrash", - Text = "S_EVENT_CRASH" - }, - [world.event.S_EVENT_EJECTION] = { - Order = 1, - Event = "OnEventEjection", - Text = "S_EVENT_EJECTION" - }, - [world.event.S_EVENT_REFUELING] = { - Order = 1, - Event = "OnEventRefueling", - Text = "S_EVENT_REFUELING" - }, - [world.event.S_EVENT_DEAD] = { - Order = -1, - Event = "OnEventDead", - Text = "S_EVENT_DEAD" - }, - [world.event.S_EVENT_PILOT_DEAD] = { - Order = 1, - Event = "OnEventPilotDead", - Text = "S_EVENT_PILOT_DEAD" - }, - [world.event.S_EVENT_BASE_CAPTURED] = { - Order = 1, - Event = "OnEventBaseCaptured", - Text = "S_EVENT_BASE_CAPTURED" - }, - [world.event.S_EVENT_MISSION_START] = { - Order = 1, - Event = "OnEventMissionStart", - Text = "S_EVENT_MISSION_START" - }, - [world.event.S_EVENT_MISSION_END] = { - Order = 1, - Event = "OnEventMissionEnd", - Text = "S_EVENT_MISSION_END" - }, - [world.event.S_EVENT_TOOK_CONTROL] = { - Order = 1, - Event = "OnEventTookControl", - Text = "S_EVENT_TOOK_CONTROL" - }, - [world.event.S_EVENT_REFUELING_STOP] = { - Order = 1, - Event = "OnEventRefuelingStop", - Text = "S_EVENT_REFUELING_STOP" - }, - [world.event.S_EVENT_BIRTH] = { - Order = 1, - Event = "OnEventBirth", - Text = "S_EVENT_BIRTH" - }, - [world.event.S_EVENT_HUMAN_FAILURE] = { - Order = 1, - Event = "OnEventHumanFailure", - Text = "S_EVENT_HUMAN_FAILURE" - }, - [world.event.S_EVENT_ENGINE_STARTUP] = { - Order = 1, - Event = "OnEventEngineStartup", - Text = "S_EVENT_ENGINE_STARTUP" - }, - [world.event.S_EVENT_ENGINE_SHUTDOWN] = { - Order = 1, - Event = "OnEventEngineShutdown", - Text = "S_EVENT_ENGINE_SHUTDOWN" - }, - [world.event.S_EVENT_PLAYER_ENTER_UNIT] = { - Order = 1, - Event = "OnEventPlayerEnterUnit", - Text = "S_EVENT_PLAYER_ENTER_UNIT" - }, - [world.event.S_EVENT_PLAYER_LEAVE_UNIT] = { - Order = -1, - Event = "OnEventPlayerLeaveUnit", - Text = "S_EVENT_PLAYER_LEAVE_UNIT" - }, - [world.event.S_EVENT_PLAYER_COMMENT] = { - Order = 1, - Event = "OnEventPlayerComment", - Text = "S_EVENT_PLAYER_COMMENT" - }, - [world.event.S_EVENT_SHOOTING_START] = { - Order = 1, - Event = "OnEventShootingStart", - Text = "S_EVENT_SHOOTING_START" - }, - [world.event.S_EVENT_SHOOTING_END] = { - Order = 1, - Event = "OnEventShootingEnd", - Text = "S_EVENT_SHOOTING_END" - }, -} - - ---- The Events structure --- @type EVENT.Events --- @field #number IniUnit - -function EVENT:New() - local self = BASE:Inherit( self, BASE:New() ) - self:F2() - self.EventHandler = world.addEventHandler( self ) - return self -end - -function EVENT:EventText( EventID ) - - local EventText = _EVENTMETA[EventID].Text - - return EventText -end - - ---- Initializes the Events structure for the event --- @param #EVENT self --- @param Dcs.DCSWorld#world.event EventID --- @param Core.Base#BASE EventClass --- @return #EVENT.Events -function EVENT:Init( EventID, EventClass ) - self:F3( { _EVENTMETA[EventID].Text, EventClass } ) - - if not self.Events[EventID] then - -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. - self.Events[EventID] = setmetatable( {}, { __mode = "k" } ) - end - - -- Each event has a subtable of EventClasses, ordered by EventPriority. - local EventPriority = EventClass:GetEventPriority() - if not self.Events[EventID][EventPriority] then - self.Events[EventID][EventPriority] = setmetatable( {}, { __mode = "kv" } ) - end - - if not self.Events[EventID][EventPriority][EventClass] then - self.Events[EventID][EventPriority][EventClass] = {} - end - return self.Events[EventID][EventPriority][EventClass] -end - ---- Removes an Events entry --- @param #EVENT self --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param Dcs.DCSWorld#world.event EventID --- @return #EVENT.Events -function EVENT:Remove( EventClass, EventID ) - self:F3( { EventClass, _EVENTMETA[EventID].Text } ) - - local EventClass = EventClass - local EventPriority = EventClass:GetEventPriority() - self.Events[EventID][EventPriority][EventClass] = nil -end - ---- Removes an Events entry for a UNIT. --- @param #EVENT self --- @param #string UnitName The name of the UNIT. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param Dcs.DCSWorld#world.event EventID --- @return #EVENT.Events -function EVENT:RemoveForUnit( UnitName, EventClass, EventID ) - self:F3( { EventClass, _EVENTMETA[EventID].Text } ) - - local EventClass = EventClass - local EventPriority = EventClass:GetEventPriority() - local Event = self.Events[EventID][EventPriority][EventClass] - Event.EventUnit[UnitName] = nil -end - ---- Removes an Events entry for a GROUP. --- @param #EVENT self --- @param #string GroupName The name of the GROUP. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param Dcs.DCSWorld#world.event EventID --- @return #EVENT.Events -function EVENT:RemoveForGroup( GroupName, EventClass, EventID ) - self:F3( { EventClass, _EVENTMETA[EventID].Text } ) - - local EventClass = EventClass - local EventPriority = EventClass:GetEventPriority() - local Event = self.Events[EventID][EventPriority][EventClass] - Event.EventGroup[GroupName] = nil -end - ---- Clears all event subscriptions for a @{Base#BASE} derived object. --- @param #EVENT self --- @param Core.Base#BASE EventObject -function EVENT:RemoveAll( EventObject ) - self:F3( { EventObject:GetClassNameAndID() } ) - - local EventClass = EventObject:GetClassNameAndID() - local EventPriority = EventClass:GetEventPriority() - for EventID, EventData in pairs( self.Events ) do - self.Events[EventID][EventPriority][EventClass] = nil - end -end - - - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventClass The instance of the class for which the event is. --- @param #function OnEventFunction --- @return #EVENT -function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EventID ) - self:F2( EventTemplate.name ) - - for EventUnitID, EventUnit in pairs( EventTemplate.units ) do - self:OnEventForUnit( EventUnit.name, EventFunction, EventClass, EventID ) - end - return self -end - ---- Set a new listener for an S_EVENT_X event independent from a unit or a weapon. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is captured. When the event happens, the event process will be called in this class provided. --- @param EventID --- @return #EVENT -function EVENT:OnEventGeneric( EventFunction, EventClass, EventID ) - self:F2( { EventID } ) - - local EventData = self:Init( EventID, EventClass ) - EventData.EventFunction = EventFunction - EventData.EventClass = EventClass - - return self -end - - ---- Set a new listener for an S_EVENT_X event for a UNIT. --- @param #EVENT self --- @param #string UnitName The name of the UNIT. --- @param #function EventFunction The function to be called when the event occurs for the GROUP. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param EventID --- @return #EVENT -function EVENT:OnEventForUnit( UnitName, EventFunction, EventClass, EventID ) - self:F2( UnitName ) - - local EventData = self:Init( EventID, EventClass ) - if not EventData.EventUnit then - EventData.EventUnit = {} - end - EventData.EventUnit[UnitName] = {} - EventData.EventUnit[UnitName].EventFunction = EventFunction - EventData.EventUnit[UnitName].EventClass = EventClass - return self -end - ---- Set a new listener for an S_EVENT_X event for a GROUP. --- @param #EVENT self --- @param #string GroupName The name of the GROUP. --- @param #function EventFunction The function to be called when the event occurs for the GROUP. --- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param EventID --- @return #EVENT -function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID ) - self:F2( GroupName ) - - local Event = self:Init( EventID, EventClass ) - if not Event.EventGroup then - Event.EventGroup = {} - end - Event.EventGroup[GroupName] = {} - Event.EventGroup[GroupName].EventFunction = EventFunction - Event.EventGroup[GroupName].EventClass = EventClass - return self -end - -do -- OnBirth - - --- Create an OnBirth event handler for a group - -- @param #EVENT self - -- @param Wrapper.Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Birth ) - - return self - end - -end - -do -- OnCrash - - --- Create an OnCrash event handler for a group - -- @param #EVENT self - -- @param Wrapper.Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Crash ) - - return self - end - -end - -do -- OnDead - - --- Create an OnDead event handler for a group - -- @param #EVENT self - -- @param Wrapper.Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Dead ) - - return self - end - -end - - -do -- OnLand - --- Create an OnLand event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Land ) - - return self - end - -end - -do -- OnTakeOff - --- Create an OnTakeOff event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Takeoff ) - - return self - end - -end - -do -- OnEngineShutDown - - --- Create an OnDead event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventClass ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.EngineShutdown ) - - return self - end - -end - - ---- @param #EVENT self --- @param #EVENTDATA Event -function EVENT:onEvent( Event ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - - if self and self.Events and self.Events[Event.id] then - - - if Event.initiator then - - Event.IniObjectCategory = Event.initiator:getCategory() - - if Event.IniObjectCategory == Object.Category.UNIT then - Event.IniDCSUnit = Event.initiator - Event.IniDCSUnitName = Event.IniDCSUnit:getName() - Event.IniUnitName = Event.IniDCSUnitName - Event.IniDCSGroup = Event.IniDCSUnit:getGroup() - Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName ) - if not Event.IniUnit then - -- Unit can be a CLIENT. Most likely this will be the case ... - Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true ) - end - Event.IniDCSGroupName = "" - if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then - Event.IniDCSGroupName = Event.IniDCSGroup:getName() - Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) - if Event.IniGroup then - Event.IniGroupName = Event.IniDCSGroupName - end - end - Event.IniPlayerName = Event.IniDCSUnit:getPlayerName() - Event.IniCoalition = Event.IniDCSUnit:getCoalition() - Event.IniTypeName = Event.IniDCSUnit:getTypeName() - Event.IniCategory = Event.IniDCSUnit:getDesc().category - end - - if Event.IniObjectCategory == Object.Category.STATIC then - Event.IniDCSUnit = Event.initiator - Event.IniDCSUnitName = Event.IniDCSUnit:getName() - Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = STATIC:FindByName( Event.IniDCSUnitName, false ) - Event.IniCoalition = Event.IniDCSUnit:getCoalition() - Event.IniCategory = Event.IniDCSUnit:getDesc().category - Event.IniTypeName = Event.IniDCSUnit:getTypeName() - end - - if Event.IniObjectCategory == Object.Category.SCENERY then - Event.IniDCSUnit = Event.initiator - Event.IniDCSUnitName = Event.IniDCSUnit:getName() - Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator ) - Event.IniCategory = Event.IniDCSUnit:getDesc().category - Event.IniTypeName = Event.IniDCSUnit:getTypeName() - end - end - - if Event.target then - - Event.TgtObjectCategory = Event.target:getCategory() - - if Event.TgtObjectCategory == Object.Category.UNIT then - Event.TgtDCSUnit = Event.target - Event.TgtDCSGroup = Event.TgtDCSUnit:getGroup() - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = UNIT:FindByName( Event.TgtDCSUnitName ) - Event.TgtDCSGroupName = "" - if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then - Event.TgtDCSGroupName = Event.TgtDCSGroup:getName() - Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) - if Event.TgtGroup then - Event.TgtGroupName = Event.TgtDCSGroupName - end - end - Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName() - Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - end - - if Event.TgtObjectCategory == Object.Category.STATIC then - Event.TgtDCSUnit = Event.target - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName ) - Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - end - - if Event.TgtObjectCategory == Object.Category.SCENERY then - Event.TgtDCSUnit = Event.target - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - end - end - - if Event.weapon then - Event.Weapon = Event.weapon - Event.WeaponName = Event.Weapon:getTypeName() - --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() - end - - local PriorityOrder = _EVENTMETA[Event.id].Order - local PriorityBegin = PriorityOrder == -1 and 5 or 1 - local PriorityEnd = PriorityOrder == -1 and 1 or 5 - - if Event.IniObjectCategory ~= 3 then - self:E( { _EVENTMETA[Event.id].Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) - end - - for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do - - if self.Events[Event.id][EventPriority] then - - -- Okay, we got the event from DCS. Now loop the SORTED self.EventSorted[] table for the received Event.id, and for each EventData registered, check if a function needs to be called. - for EventClass, EventData in pairs( self.Events[Event.id][EventPriority] ) do - - Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) - Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) - - -- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT. - if ( Event.IniDCSUnitName and EventData.EventUnit and EventData.EventUnit[Event.IniDCSUnitName] ) or - ( Event.TgtDCSUnitName and EventData.EventUnit and EventData.EventUnit[Event.TgtDCSUnitName] ) then - - if EventData.EventUnit[Event.IniDCSUnitName] then - - -- First test if a EventFunction is Set, otherwise search for the default function - if EventData.EventUnit[Event.IniDCSUnitName].EventFunction then - - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventData.EventUnit[Event.IniDCSUnitName].EventFunction( EventClass, Event ) - end, ErrorHandler ) - - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - - if EventData.EventUnit[Event.TgtDCSUnitName] then - - -- First test if a EventFunction is Set, otherwise search for the default function - if EventData.EventUnit[Event.TgtDCSUnitName].EventFunction then - - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventData.EventUnit[Event.TgtDCSUnitName].EventFunction( EventClass, Event ) - end, ErrorHandler ) - - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - - else - - -- If the EventData is for a GROUP, the call directly the EventClass EventFunction for the UNIT in that GROUP. - if ( Event.IniDCSUnitName and Event.IniDCSGroupName and Event.IniGroupName and EventData.EventGroup and EventData.EventGroup[Event.IniGroupName] ) or - ( Event.TgtDCSUnitName and Event.TgtDCSGroupName and Event.TgtGroupName and EventData.EventGroup and EventData.EventGroup[Event.TgtGroupName] ) then - - if EventData.EventGroup[Event.IniGroupName] then - -- First test if a EventFunction is Set, otherwise search for the default function - if EventData.EventGroup[Event.IniGroupName].EventFunction then - - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventData.EventGroup[Event.IniGroupName].EventFunction( EventClass, Event ) - end, ErrorHandler ) - - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - - if EventData.EventGroup[Event.TgtGroupName] then - if EventData.EventGroup[Event.TgtGroupName].EventFunction then - - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventData.EventGroup[Event.TgtGroupName].EventFunction( EventClass, Event ) - end, ErrorHandler ) - - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - - else - - -- If the EventData is not bound to a specific unit, then call the EventClass EventFunction. - -- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon. - if Event.IniDCSUnit and not EventData.EventUnit then - - if EventClass == EventData.EventClass then - - -- First test if a EventFunction is Set, otherwise search for the default function - if EventData.EventFunction then - - -- There is an EventFunction defined, so call the EventFunction. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - end - local Result, Value = xpcall( - function() - return EventData.EventFunction( EventClass, Event ) - end, ErrorHandler ) - else - - -- There is no EventFunction defined, so try to find if a default OnEvent function is defined on the object. - local EventFunction = EventClass[ _EVENTMETA[Event.id].Event ] - if EventFunction and type( EventFunction ) == "function" then - - -- Now call the default event function. - if Event.IniObjectCategory ~= 3 then - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - end - - local Result, Value = xpcall( - function() - return EventFunction( EventClass, Event ) - end, ErrorHandler ) - end - end - end - end - end - end - end - end - end - else - self:E( { _EVENTMETA[Event.id].Text, Event } ) - end -end - ---- The EVENTHANDLER structure --- @type EVENTHANDLER --- @extends Core.Base#BASE -EVENTHANDLER = { - ClassName = "EVENTHANDLER", - ClassID = 0, -} - ---- The EVENTHANDLER constructor --- @param #EVENTHANDLER self --- @return #EVENTHANDLER -function EVENTHANDLER:New() - self = BASE:Inherit( self, BASE:New() ) -- #EVENTHANDLER - return self -end ---- **Core** -- MENU_ classes model the definition of **hierarchical menu structures** and **commands for players** within a mission. --- --- === --- --- DCS Menus can be managed using the MENU classes. --- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to --- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing --- menus is not a easy feat if you have complex menu hierarchies defined. --- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. --- On top, MOOSE implements **variable parameter** passing for command menus. --- --- There are basically two different MENU class types that you need to use: --- --- ### To manage **main menus**, the classes begin with **MENU_**: --- --- * @{Menu#MENU_MISSION}: Manages main menus for whole mission file. --- * @{Menu#MENU_COALITION}: Manages main menus for whole coalition. --- * @{Menu#MENU_GROUP}: Manages main menus for GROUPs. --- * @{Menu#MENU_CLIENT}: Manages main menus for CLIENTs. This manages menus for units with the skill level "Client". --- --- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: --- --- * @{Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. --- * @{Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. --- * @{Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- * @{Menu#MENU_CLIENT_COMMAND}: Manages command menus for CLIENTs. This manages menus for units with the skill level "Client". --- --- === --- --- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these): --- --- 1) MENU_ BASE abstract base classes (don't use them) --- ==================================================== --- The underlying base menu classes are **NOT** to be used within your missions. --- These are simply abstract base classes defining a couple of fields that are used by the --- derived MENU_ classes to manage menus. --- --- 1.1) @{#MENU_BASE} class, extends @{Base#BASE} --- -------------------------------------------------- --- The @{#MENU_BASE} class defines the main MENU class where other MENU classes are derived from. --- --- 1.2) @{#MENU_COMMAND_BASE} class, extends @{Base#BASE} --- ---------------------------------------------------------- --- The @{#MENU_COMMAND_BASE} class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands. --- --- === --- --- **The next menus define the MENU classes that you can use within your missions.** --- --- 2) MENU MISSION classes --- ====================== --- The underlying classes manage the menus for a complete mission file. --- --- 2.1) @{#MENU_MISSION} class, extends @{Menu#MENU_BASE} --- --------------------------------------------------------- --- The @{Menu#MENU_MISSION} class manages the main menus for a complete mission. --- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. --- --- 2.2) @{#MENU_MISSION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} --- ------------------------------------------------------------------------- --- The @{Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution. --- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. --- --- === --- --- 3) MENU COALITION classes --- ========================= --- The underlying classes manage the menus for whole coalitions. --- --- 3.1) @{#MENU_COALITION} class, extends @{Menu#MENU_BASE} --- ------------------------------------------------------------ --- The @{Menu#MENU_COALITION} class manages the main menus for coalitions. --- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. --- --- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} --- ---------------------------------------------------------------------------- --- The @{Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. --- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. --- --- === --- --- 4) MENU GROUP classes --- ===================== --- The underlying classes manage the menus for groups. Note that groups can be inactive, alive or can be destroyed. --- --- 4.1) @{Menu#MENU_GROUP} class, extends @{Menu#MENU_BASE} --- -------------------------------------------------------- --- The @{Menu#MENU_GROUP} class manages the main menus for coalitions. --- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. --- --- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} --- ------------------------------------------------------------------------ --- The @{Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. --- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. --- --- === --- --- 5) MENU CLIENT classes --- ====================== --- The underlying classes manage the menus for units with skill level client or player. --- --- 5.1) @{Menu#MENU_CLIENT} class, extends @{Menu#MENU_BASE} --- --------------------------------------------------------- --- The @{Menu#MENU_CLIENT} class manages the main menus for coalitions. --- You can add menus with the @{#MENU_CLIENT.New} method, which constructs a MENU_CLIENT object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT.Remove}. --- --- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} --- ------------------------------------------------------------------------- --- The @{Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. --- You can add menus with the @{#MENU_CLIENT_COMMAND.New} method, which constructs a MENU_CLIENT_COMMAND object and returns you the object reference. --- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT_COMMAND.Remove}. --- --- === --- --- ### Contributions: - --- ### Authors: FlightControl : Design & Programming --- --- @module Menu - - -do -- MENU_BASE - - --- The MENU_BASE class - -- @type MENU_BASE - -- @extends Base#BASE - MENU_BASE = { - ClassName = "MENU_BASE", - MenuPath = nil, - MenuText = "", - MenuParentPath = nil - } - - --- Consructor - -- @param #MENU_BASE - -- @return #MENU_BASE - function MENU_BASE:New( MenuText, ParentMenu ) - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, BASE:New() ) - - self.MenuPath = nil - self.MenuText = MenuText - self.MenuParentPath = MenuParentPath - self.Menus = {} - self.MenuCount = 0 - self.MenuRemoveParent = false - self.MenuTime = timer.getTime() - - return self - end - - --- Gets a @{Menu} from a parent @{Menu} - -- @param #MENU_BASE self - -- @param #string MenuText The text of the child menu. - -- @return #MENU_BASE - function MENU_BASE:GetMenu( MenuText ) - self:F( { self.Menus, MenuText } ) - return self.Menus[MenuText] - end - - --- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}. - -- @param #MENU_BASE self - -- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}. - -- @return #MENU_BASE - function MENU_BASE:SetRemoveParent( RemoveParent ) - self:F( { RemoveParent } ) - self.MenuRemoveParent = RemoveParent - return self - end - - - --- Sets a time stamp for later prevention of menu removal. - -- @param #MENU_BASE self - -- @param MenuTime - -- @return #MENU_BASE - function MENU_BASE:SetTime( MenuTime ) - self.MenuTime = MenuTime - return self - end - -end - -do -- MENU_COMMAND_BASE - - --- The MENU_COMMAND_BASE class - -- @type MENU_COMMAND_BASE - -- @field #function MenuCallHandler - -- @extends Core.Menu#MENU_BASE - MENU_COMMAND_BASE = { - ClassName = "MENU_COMMAND_BASE", - CommandMenuFunction = nil, - CommandMenuArgument = nil, - MenuCallHandler = nil, - } - - --- Constructor - -- @param #MENU_COMMAND_BASE - -- @return #MENU_COMMAND_BASE - function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self.CommandMenuFunction = CommandMenuFunction - self.MenuCallHandler = function( CommandMenuArguments ) - self.CommandMenuFunction( unpack( CommandMenuArguments ) ) - end - - return self - end - -end - - -do -- MENU_MISSION - - --- The MENU_MISSION class - -- @type MENU_MISSION - -- @extends Core.Menu#MENU_BASE - MENU_MISSION = { - ClassName = "MENU_MISSION" - } - - --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. - -- @param #MENU_MISSION self - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). - -- @return #MENU_MISSION - function MENU_MISSION:New( MenuText, ParentMenu ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self:F( { MenuText, ParentMenu } ) - - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - self:T( { MenuText } ) - - self.MenuPath = missionCommands.addSubMenu( MenuText, self.MenuParentPath ) - - self:T( { self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - - return self - end - - --- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept! - -- @param #MENU_MISSION self - -- @return #MENU_MISSION - function MENU_MISSION:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the main menu and the sub menus recursively of this MENU_MISSION. - -- @param #MENU_MISSION self - -- @return #nil - function MENU_MISSION:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - missionCommands.removeItem( self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - - return nil - end - -end - -do -- MENU_MISSION_COMMAND - - --- The MENU_MISSION_COMMAND class - -- @type MENU_MISSION_COMMAND - -- @extends Core.Menu#MENU_COMMAND_BASE - MENU_MISSION_COMMAND = { - ClassName = "MENU_MISSION_COMMAND" - } - - --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. - -- @param #MENU_MISSION_COMMAND self - -- @param #string MenuText The text for the menu. - -- @param Menu#MENU_MISSION ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. - -- @return #MENU_MISSION_COMMAND self - function MENU_MISSION_COMMAND:New( MenuText, ParentMenu, CommandMenuFunction, ... ) - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { MenuText, CommandMenuFunction, arg } ) - - - self.MenuPath = missionCommands.addCommand( MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - --- Removes a radio command item for a coalition - -- @param #MENU_MISSION_COMMAND self - -- @return #nil - function MENU_MISSION_COMMAND:Remove() - self:F( self.MenuPath ) - - missionCommands.removeItem( self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - return nil - end - -end - - - -do -- MENU_COALITION - - --- The MENU_COALITION class - -- @type MENU_COALITION - -- @extends Core.Menu#MENU_BASE - -- @usage - -- -- This demo creates a menu structure for the planes within the red coalition. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- - -- local Plane1 = CLIENT:FindByName( "Plane 1" ) - -- local Plane2 = CLIENT:FindByName( "Plane 2" ) - -- - -- - -- -- This would create a menu for the red coalition under the main DCS "Others" menu. - -- local MenuCoalitionRed = MENU_COALITION:New( coalition.side.RED, "Manage Menus" ) - -- - -- - -- local function ShowStatus( StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- Plane1:Message( StatusText, 15 ) - -- Plane2:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus -- Menu#MENU_COALITION - -- local MenuStatusShow -- Menu#MENU_COALITION_COMMAND - -- - -- local function RemoveStatusMenu() - -- MenuStatus:Remove() - -- end - -- - -- local function AddStatusMenu() - -- - -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. - -- MenuStatus = MENU_COALITION:New( coalition.side.RED, "Status for Planes" ) - -- MenuStatusShow = MENU_COALITION_COMMAND:New( coalition.side.RED, "Show Status", MenuStatus, ShowStatus, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- local MenuAdd = MENU_COALITION_COMMAND:New( coalition.side.RED, "Add Status Menu", MenuCoalitionRed, AddStatusMenu ) - -- local MenuRemove = MENU_COALITION_COMMAND:New( coalition.side.RED, "Remove Status Menu", MenuCoalitionRed, RemoveStatusMenu ) - MENU_COALITION = { - ClassName = "MENU_COALITION" - } - - --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. - -- @param #MENU_COALITION self - -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). - -- @return #MENU_COALITION self - function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self:F( { Coalition, MenuText, ParentMenu } ) - - self.Coalition = Coalition - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - self:T( { MenuText } ) - - self.MenuPath = missionCommands.addSubMenuForCoalition( Coalition, MenuText, self.MenuParentPath ) - - self:T( { self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - - return self - end - - --- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept! - -- @param #MENU_COALITION self - -- @return #MENU_COALITION - function MENU_COALITION:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the main menu and the sub menus recursively of this MENU_COALITION. - -- @param #MENU_COALITION self - -- @return #nil - function MENU_COALITION:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - - return nil - end - -end - -do -- MENU_COALITION_COMMAND - - --- The MENU_COALITION_COMMAND class - -- @type MENU_COALITION_COMMAND - -- @extends Core.Menu#MENU_COMMAND_BASE - MENU_COALITION_COMMAND = { - ClassName = "MENU_COALITION_COMMAND" - } - - --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. - -- @param #MENU_COALITION_COMMAND self - -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. - -- @param #string MenuText The text for the menu. - -- @param Menu#MENU_COALITION ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. - -- @return #MENU_COALITION_COMMAND - function MENU_COALITION_COMMAND:New( Coalition, MenuText, ParentMenu, CommandMenuFunction, ... ) - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuCoalition = Coalition - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { MenuText, CommandMenuFunction, arg } ) - - - self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - --- Removes a radio command item for a coalition - -- @param #MENU_COALITION_COMMAND self - -- @return #nil - function MENU_COALITION_COMMAND:Remove() - self:F( self.MenuPath ) - - missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - return nil - end - -end - -do -- MENU_CLIENT - - -- This local variable is used to cache the menus registered under clients. - -- Menus don't dissapear when clients are destroyed and restarted. - -- So every menu for a client created must be tracked so that program logic accidentally does not create - -- the same menus twice during initialization logic. - -- These menu classes are handling this logic with this variable. - local _MENUCLIENTS = {} - - --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. - -- @type MENU_CLIENT - -- @extends Core.Menu#MENU_BASE - -- @usage - -- -- This demo creates a menu structure for the two clients of planes. - -- -- Each client will receive a different menu structure. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- -- And play with the Add and Remove menu options. - -- - -- -- Note that in multi player, this will only work after the DCS clients bug is solved. - -- - -- local function ShowStatus( PlaneClient, StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- PlaneClient:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus = {} - -- - -- local function RemoveStatusMenu( MenuClient ) - -- local MenuClientName = MenuClient:GetName() - -- MenuStatus[MenuClientName]:Remove() - -- end - -- - -- --- @param Wrapper.Client#CLIENT MenuClient - -- local function AddStatusMenu( MenuClient ) - -- local MenuClientName = MenuClient:GetName() - -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. - -- MenuStatus[MenuClientName] = MENU_CLIENT:New( MenuClient, "Status for Planes" ) - -- MENU_CLIENT_COMMAND:New( MenuClient, "Show Status", MenuStatus[MenuClientName], ShowStatus, MenuClient, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneClient = CLIENT:FindByName( "Plane 1" ) - -- if PlaneClient and PlaneClient:IsAlive() then - -- local MenuManage = MENU_CLIENT:New( PlaneClient, "Manage Menus" ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Add Status Menu Plane 1", MenuManage, AddStatusMenu, PlaneClient ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 1", MenuManage, RemoveStatusMenu, PlaneClient ) - -- end - -- end, {}, 10, 10 ) - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneClient = CLIENT:FindByName( "Plane 2" ) - -- if PlaneClient and PlaneClient:IsAlive() then - -- local MenuManage = MENU_CLIENT:New( PlaneClient, "Manage Menus" ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Add Status Menu Plane 2", MenuManage, AddStatusMenu, PlaneClient ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneClient ) - -- end - -- end, {}, 10, 10 ) - MENU_CLIENT = { - ClassName = "MENU_CLIENT" - } - - --- MENU_CLIENT constructor. Creates a new radio menu item for a client. - -- @param #MENU_CLIENT self - -- @param Wrapper.Client#CLIENT Client The Client owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. - -- @return #MENU_CLIENT self - function MENU_CLIENT:New( Client, MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, MenuParentPath ) ) - self:F( { Client, MenuText, ParentMenu } ) - - self.MenuClient = Client - self.MenuClientGroupID = Client:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { Client:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath - - self:T( { Client:GetClientGroupName(), self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - return self - end - - --- Removes the sub menus recursively of this @{#MENU_CLIENT}. - -- @param #MENU_CLIENT self - -- @return #MENU_CLIENT self - function MENU_CLIENT:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the sub menus recursively of this MENU_CLIENT. - -- @param #MENU_CLIENT self - -- @return #nil - function MENU_CLIENT:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil - end - - - --- The MENU_CLIENT_COMMAND class - -- @type MENU_CLIENT_COMMAND - -- @extends Core.Menu#MENU_COMMAND - MENU_CLIENT_COMMAND = { - ClassName = "MENU_CLIENT_COMMAND" - } - - --- MENU_CLIENT_COMMAND constructor. Creates a new radio command item for a client, which can invoke a function with parameters. - -- @param #MENU_CLIENT_COMMAND self - -- @param Wrapper.Client#CLIENT Client The Client owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #MENU_BASE ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @return Menu#MENU_CLIENT_COMMAND self - function MENU_CLIENT_COMMAND:New( Client, MenuText, ParentMenu, CommandMenuFunction, ... ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, MenuParentPath, CommandMenuFunction, arg ) ) -- Menu#MENU_CLIENT_COMMAND - - self.MenuClient = Client - self.MenuClientGroupID = Client:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { Client:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, arg } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, self.MenuCallHandler, arg ) - MenuPath[MenuPathID] = self.MenuPath - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - - return self - end - - --- Removes a menu structure for a client. - -- @param #MENU_CLIENT_COMMAND self - -- @return #nil - function MENU_CLIENT_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil - end -end - ---- MENU_GROUP - -do - -- This local variable is used to cache the menus registered under groups. - -- Menus don't dissapear when groups for players are destroyed and restarted. - -- So every menu for a client created must be tracked so that program logic accidentally does not create. - -- the same menus twice during initialization logic. - -- These menu classes are handling this logic with this variable. - local _MENUGROUPS = {} - - --- The MENU_GROUP class - -- @type MENU_GROUP - -- @extends Core.Menu#MENU_BASE - -- @usage - -- -- This demo creates a menu structure for the two groups of planes. - -- -- Each group will receive a different menu structure. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- -- And play with the Add and Remove menu options. - -- - -- -- Note that in multi player, this will only work after the DCS groups bug is solved. - -- - -- local function ShowStatus( PlaneGroup, StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- PlaneGroup:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus = {} - -- - -- local function RemoveStatusMenu( MenuGroup ) - -- local MenuGroupName = MenuGroup:GetName() - -- MenuStatus[MenuGroupName]:Remove() - -- end - -- - -- --- @param Wrapper.Group#GROUP MenuGroup - -- local function AddStatusMenu( MenuGroup ) - -- local MenuGroupName = MenuGroup:GetName() - -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. - -- MenuStatus[MenuGroupName] = MENU_GROUP:New( MenuGroup, "Status for Planes" ) - -- MENU_GROUP_COMMAND:New( MenuGroup, "Show Status", MenuStatus[MenuGroupName], ShowStatus, MenuGroup, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneGroup = GROUP:FindByName( "Plane 1" ) - -- if PlaneGroup and PlaneGroup:IsAlive() then - -- local MenuManage = MENU_GROUP:New( PlaneGroup, "Manage Menus" ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Add Status Menu Plane 1", MenuManage, AddStatusMenu, PlaneGroup ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Remove Status Menu Plane 1", MenuManage, RemoveStatusMenu, PlaneGroup ) - -- end - -- end, {}, 10, 10 ) - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneGroup = GROUP:FindByName( "Plane 2" ) - -- if PlaneGroup and PlaneGroup:IsAlive() then - -- local MenuManage = MENU_GROUP:New( PlaneGroup, "Manage Menus" ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Add Status Menu Plane 2", MenuManage, AddStatusMenu, PlaneGroup ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneGroup ) - -- end - -- end, {}, 10, 10 ) - -- - MENU_GROUP = { - ClassName = "MENU_GROUP" - } - - --- MENU_GROUP constructor. Creates a new radio menu item for a group. - -- @param #MENU_GROUP self - -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. - -- @return #MENU_GROUP self - function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) - - -- Determine if the menu was not already created and already visible at the group. - -- If it is visible, then return the cached self, otherwise, create self and cache it. - - MenuGroup._Menus = MenuGroup._Menus or {} - local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText - if MenuGroup._Menus[Path] then - self = MenuGroup._Menus[Path] - else - self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - MenuGroup._Menus[Path] = self - - self.MenuGroup = MenuGroup - self.Path = Path - self.MenuGroupID = MenuGroup:GetID() - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { "Adding Menu ", MenuText, self.MenuParentPath } ) - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, self.MenuParentPath ) - - if self.ParentMenu and self.ParentMenu.Menus then - self.ParentMenu.Menus[MenuText] = self - self:F( { self.ParentMenu.Menus, MenuText } ) - self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1 - end - end - - --self:F( { MenuGroup:GetName(), MenuText, ParentMenu.MenuPath } ) - - return self - end - - --- Removes the sub menus recursively of this MENU_GROUP. - -- @param #MENU_GROUP self - -- @param MenuTime - -- @return #MENU_GROUP self - function MENU_GROUP:RemoveSubMenus( MenuTime ) - self:F2( { self.MenuPath, MenuTime, self.MenuTime } ) - - self:T( { "Removing Group SubMenus:", self.MenuGroup:GetName(), self.MenuPath } ) - for MenuText, Menu in pairs( self.Menus ) do - Menu:Remove( MenuTime ) - end - - end - - - --- Removes the main menu and sub menus recursively of this MENU_GROUP. - -- @param #MENU_GROUP self - -- @param MenuTime - -- @return #nil - function MENU_GROUP:Remove( MenuTime ) - self:F( { self.MenuGroupID, self.MenuPath, MenuTime, self.MenuTime } ) - - self:RemoveSubMenus( MenuTime ) - - if not MenuTime or self.MenuTime ~= MenuTime then - if self.MenuGroup._Menus[self.Path] then - self = self.MenuGroup._Menus[self.Path] - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuText] = nil - self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1 - if self.ParentMenu.MenuCount == 0 then - if self.MenuRemoveParent == true then - self:T( "Removing Parent Menu " ) - self.ParentMenu:Remove() - end - end - end - self:T( { "Removing Group Menu:", self.MenuGroup:GetName(), self.MenuGroup._Menus[self.Path].Path } ) - self.MenuGroup._Menus[self.Path] = nil - self = nil - end - end - - return nil - end - - - --- The MENU_GROUP_COMMAND class - -- @type MENU_GROUP_COMMAND - -- @extends Core.Menu#MENU_BASE - MENU_GROUP_COMMAND = { - ClassName = "MENU_GROUP_COMMAND" - } - - --- Creates a new radio command item for a group - -- @param #MENU_GROUP_COMMAND self - -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. - -- @param MenuText The text for the menu. - -- @param ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. - -- @return #MENU_GROUP_COMMAND - function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, ... ) - - MenuGroup._Menus = MenuGroup._Menus or {} - local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText - if MenuGroup._Menus[Path] then - self = MenuGroup._Menus[Path] - self:T( { "Re-using Group Command Menu:", MenuGroup:GetName(), MenuText } ) - else - self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - MenuGroup._Menus[Path] = self - - self.Path = Path - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { "Adding Group Command Menu:", MenuGroup:GetName(), MenuText, self.MenuParentPath } ) - self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - - if self.ParentMenu and self.ParentMenu.Menus then - self.ParentMenu.Menus[MenuText] = self - self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1 - self:F( { ParentMenu.Menus, MenuText } ) - end - end - - return self - end - - --- Removes a menu structure for a group. - -- @param #MENU_GROUP_COMMAND self - -- @param MenuTime - -- @return #nil - function MENU_GROUP_COMMAND:Remove( MenuTime ) - self:F( { self.MenuGroupID, self.MenuPath, MenuTime, self.MenuTime } ) - - if not MenuTime or self.MenuTime ~= MenuTime then - if self.MenuGroup._Menus[self.Path] then - self = self.MenuGroup._Menus[self.Path] - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - self:T( { "Removing Group Command Menu:", self.MenuGroup:GetName(), self.MenuText, self.Path, self.MenuGroup._Menus[self.Path].Path } ) - - self.ParentMenu.Menus[self.MenuText] = nil - self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1 - if self.ParentMenu.MenuCount == 0 then - if self.MenuRemoveParent == true then - self:T( "Removing Parent Menu " ) - self.ParentMenu:Remove() - end - end - - self.MenuGroup._Menus[self.Path] = nil - self = nil - end - end - - return nil - end - -end - ---- **Core** - ZONE classes define **zones** within your mission of **various forms**, with **various capabilities**. --- --- ![Banner Image](..\Presentations\ZONE\Dia1.JPG) --- --- === --- --- There are essentially two core functions that zones accomodate: --- --- * Test if an object is within the zone boundaries. --- * Provide the zone behaviour. Some zones are static, while others are moveable. --- --- The object classes are using the zone classes to test the zone boundaries, which can take various forms: --- --- * Test if completely within the zone. --- * Test if partly within the zone (for @{Group#GROUP} objects). --- * Test if not in the zone. --- * Distance to the nearest intersecting point of the zone. --- * Distance to the center of the zone. --- * ... --- --- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: --- --- * @{Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. --- * @{Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. --- * @{Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. --- * @{Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Unit#UNIT} with a radius. --- * @{Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. --- * @{Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- --- === --- --- # 1) @{Zone#ZONE_BASE} class, extends @{Base#BASE} --- --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- --- ## 1.1) Each zone has a name: --- --- * @{#ZONE_BASE.GetName}(): Returns the name of the zone. --- --- ## 1.2) Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}: --- --- * @{#ZONE_BASE.IsVec2InZone}(): Returns if a Vec2 is within the zone. --- * @{#ZONE_BASE.IsVec3InZone}(): Returns if a Vec3 is within the zone. --- --- ## 1.3) A zone has a probability factor that can be set to randomize a selection between zones: --- --- * @{#ZONE_BASE.SetRandomizeProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% ) --- * @{#ZONE_BASE.GetRandomizeProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% ) --- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate. --- --- ## 1.4) A zone manages Vectors: --- --- * @{#ZONE_BASE.GetVec2}(): Returns the @{DCSTypes#Vec2} coordinate of the zone. --- * @{#ZONE_BASE.GetRandomVec2}(): Define a random @{DCSTypes#Vec2} within the zone. --- --- ## 1.5) A zone has a bounding square: --- --- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone. --- --- ## 1.6) A zone can be marked: --- --- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. --- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. --- --- === --- --- # 2) @{Zone#ZONE_RADIUS} class, extends @{Zone#ZONE_BASE} --- --- The ZONE_RADIUS class defined by a zone name, a location and a radius. --- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. --- --- ## 2.1) @{Zone#ZONE_RADIUS} constructor --- --- * @{#ZONE_RADIUS.New}(): Constructor. --- --- ## 2.2) Manage the radius of the zone --- --- * @{#ZONE_RADIUS.SetRadius}(): Sets the radius of the zone. --- * @{#ZONE_RADIUS.GetRadius}(): Returns the radius of the zone. --- --- ## 2.3) Manage the location of the zone --- --- * @{#ZONE_RADIUS.SetVec2}(): Sets the @{DCSTypes#Vec2} of the zone. --- * @{#ZONE_RADIUS.GetVec2}(): Returns the @{DCSTypes#Vec2} of the zone. --- * @{#ZONE_RADIUS.GetVec3}(): Returns the @{DCSTypes#Vec3} of the zone, taking an additional height parameter. --- --- ## 2.4) Zone point randomization --- --- Various functions exist to find random points within the zone. --- --- * @{#ZONE_RADIUS.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Point#POINT_VEC2} object representing a random 2D point in the zone. --- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. --- --- === --- --- # 3) @{Zone#ZONE} class, extends @{Zone#ZONE_RADIUS} --- --- The ZONE class, defined by the zone name as defined within the Mission Editor. --- This class implements the inherited functions from {Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- --- === --- --- # 4) @{Zone#ZONE_UNIT} class, extends @{Zone#ZONE_RADIUS} --- --- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- --- === --- --- # 5) @{Zone#ZONE_GROUP} class, extends @{Zone#ZONE_RADIUS} --- --- The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. The current leader of the group defines the center of the zone. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- --- === --- --- # 6) @{Zone#ZONE_POLYGON_BASE} class, extends @{Zone#ZONE_BASE} --- --- The ZONE_POLYGON_BASE class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- --- ## 6.1) Zone point randomization --- --- Various functions exist to find random points within the zone. --- --- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Point#POINT_VEC2} object representing a random 2D point within the zone. --- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. --- --- --- === --- --- # 7) @{Zone#ZONE_POLYGON} class, extends @{Zone#ZONE_POLYGON_BASE} --- --- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- This class implements the inherited functions from @{Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- --- ==== --- --- **API CHANGE HISTORY** --- ====================== --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-02-28: ZONE\_BASE:**IsVec2InZone()** replaces ZONE\_BASE:_IsPointVec2InZone()_. --- 2017-02-28: ZONE\_BASE:**IsVec3InZone()** replaces ZONE\_BASE:_IsPointVec3InZone()_. --- 2017-02-28: ZONE\_RADIUS:**IsVec2InZone()** replaces ZONE\_RADIUS:_IsPointVec2InZone()_. --- 2017-02-28: ZONE\_RADIUS:**IsVec3InZone()** replaces ZONE\_RADIUS:_IsPointVec3InZone()_. --- 2017-02-28: ZONE\_POLYGON:**IsVec2InZone()** replaces ZONE\_POLYGON:_IsPointVec2InZone()_. --- 2017-02-28: ZONE\_POLYGON:**IsVec3InZone()** replaces ZONE\_POLYGON:_IsPointVec3InZone()_. --- --- 2017-02-18: ZONE\_POLYGON_BASE:**GetRandomPointVec2()** added. --- --- 2017-02-18: ZONE\_POLYGON_BASE:**GetRandomPointVec3()** added. --- --- 2017-02-18: ZONE\_RADIUS:**GetRandomPointVec3( inner, outer )** added. --- --- 2017-02-18: ZONE\_RADIUS:**GetRandomPointVec2( inner, outer )** added. --- --- 2016-08-15: ZONE\_BASE:**GetName()** added. --- --- 2016-08-15: ZONE\_BASE:**SetZoneProbability( ZoneProbability )** added. --- --- 2016-08-15: ZONE\_BASE:**GetZoneProbability()** added. --- --- 2016-08-15: ZONE\_BASE:**GetZoneMaybe()** added. --- --- === --- --- @module Zone - - ---- The ZONE_BASE class --- @type ZONE_BASE --- @field #string ZoneName Name of the zone. --- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. --- @extends Core.Base#BASE -ZONE_BASE = { - ClassName = "ZONE_BASE", - ZoneName = "", - ZoneProbability = 1, - } - - ---- The ZONE_BASE.BoundingSquare --- @type ZONE_BASE.BoundingSquare --- @field Dcs.DCSTypes#Distance x1 The lower x coordinate (left down) --- @field Dcs.DCSTypes#Distance y1 The lower y coordinate (left down) --- @field Dcs.DCSTypes#Distance x2 The higher x coordinate (right up) --- @field Dcs.DCSTypes#Distance y2 The higher y coordinate (right up) - - ---- ZONE_BASE constructor --- @param #ZONE_BASE self --- @param #string ZoneName Name of the zone. --- @return #ZONE_BASE self -function ZONE_BASE:New( ZoneName ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( ZoneName ) - - self.ZoneName = ZoneName - - return self -end - ---- Returns the name of the zone. --- @param #ZONE_BASE self --- @return #string The name of the zone. -function ZONE_BASE:GetName() - self:F2() - - return self.ZoneName -end ---- Returns if a location is within the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_BASE:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) - - return false -end - ---- Returns if a point is within the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. --- @return #boolean true if the point is within the zone. -function ZONE_BASE:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) - - local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) - - return InZone -end - ---- Returns the @{DCSTypes#Vec2} coordinate of the zone. --- @param #ZONE_BASE self --- @return #nil. -function ZONE_BASE:GetVec2() - self:F2( self.ZoneName ) - - return nil -end - ---- Returns a @{Point#POINT_VEC2} of the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. -function ZONE_BASE:GetPointVec2() - self:F2( self.ZoneName ) - - local Vec2 = self:GetVec2() - - local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - - self:T2( { PointVec2 } ) - - return PointVec2 -end - - ---- Returns the @{DCSTypes#Vec3} of the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The Vec3 of the zone. -function ZONE_BASE:GetVec3( Height ) - self:F2( self.ZoneName ) - - Height = Height or 0 - - local Vec2 = self:GetVec2() - - local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { Vec3 } ) - - return Vec3 -end - ---- Returns a @{Point#POINT_VEC3} of the zone. --- @param #ZONE_BASE self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. -function ZONE_BASE:GetPointVec3( Height ) - self:F2( self.ZoneName ) - - local Vec3 = self:GetVec3( Height ) - - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - - self:T2( { PointVec3 } ) - - return PointVec3 -end - - ---- Define a random @{DCSTypes#Vec2} within the zone. --- @param #ZONE_BASE self --- @return Dcs.DCSTypes#Vec2 The Vec2 coordinates. -function ZONE_BASE:GetRandomVec2() - return nil -end - ---- Define a random @{Point#POINT_VEC2} within the zone. --- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. -function ZONE_BASE:GetRandomPointVec2() - return nil -end - ---- Define a random @{Point#POINT_VEC3} within the zone. --- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. -function ZONE_BASE:GetRandomPointVec3() - return nil -end - ---- Get the bounding square the zone. --- @param #ZONE_BASE self --- @return #nil The bounding square. -function ZONE_BASE:GetBoundingSquare() - --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } - return nil -end - ---- Bound the zone boundaries with a tires. --- @param #ZONE_BASE self -function ZONE_BASE:BoundZone() - self:F2() - -end - ---- Smokes the zone boundaries in a color. --- @param #ZONE_BASE self --- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -function ZONE_BASE:SmokeZone( SmokeColor ) - self:F2( SmokeColor ) - -end - ---- Set the randomization probability of a zone to be selected. --- @param #ZONE_BASE self --- @param ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -function ZONE_BASE:SetZoneProbability( ZoneProbability ) - self:F2( ZoneProbability ) - - self.ZoneProbability = ZoneProbability or 1 - return self -end - ---- Get the randomization probability of a zone to be selected. --- @param #ZONE_BASE self --- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. -function ZONE_BASE:GetZoneProbability() - self:F2() - - return self.ZoneProbability -end - ---- Get the zone taking into account the randomization probability of a zone to be selected. --- @param #ZONE_BASE self --- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. --- @return #nil The zone is not selected taking into account the randomization probability factor. -function ZONE_BASE:GetZoneMaybe() - self:F2() - - local Randomization = math.random() - if Randomization <= self.ZoneProbability then - return self - else - return nil - end -end - - ---- The ZONE_RADIUS class, defined by a zone name, a location and a radius. --- @type ZONE_RADIUS --- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone. --- @field Dcs.DCSTypes#Distance Radius The radius of the zone. --- @extends Core.Zone#ZONE_BASE -ZONE_RADIUS = { - ClassName="ZONE_RADIUS", - } - ---- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. --- @param #ZONE_RADIUS self --- @param #string ZoneName Name of the zone. --- @param Dcs.DCSTypes#Vec2 Vec2 The location of the zone. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) - local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS - self:F( { ZoneName, Vec2, Radius } ) - - self.Radius = Radius - self.Vec2 = Vec2 - - return self -end - ---- Bounds the zone with tires. --- @param #ZONE_RADIUS self --- @param #number Points (optional) The amount of points in the circle. --- @param #boolean UnBound If true the tyres will be destroyed. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) - - local Point = {} - local Vec2 = self:GetVec2() - - Points = Points and Points or 360 - - local Angle - local RadialBase = math.pi*2 - - -- - for Angle = 0, 360, (360 / Points ) do - local Radial = Angle * RadialBase / 360 - Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - - local CountryName = _DATABASE.COUNTRY_NAME[CountryID] - - local Tire = { - ["country"] = CountryName, - ["category"] = "Fortifications", - ["canCargo"] = false, - ["shape_name"] = "H-tyre_B_WF", - ["type"] = "Black_Tyre_WF", - --["unitId"] = Angle + 10000, - ["y"] = Point.y, - ["x"] = Point.x, - ["name"] = string.format( "%s-Tire #%0d", self:GetName(), Angle ), - ["heading"] = 0, - } -- end of ["group"] - - local Group = coalition.addStaticObject( CountryID, Tire ) - if UnBound and UnBound == true then - Group:destroy() - end - end - - return self -end - - ---- Smokes the zone boundaries in a color. --- @param #ZONE_RADIUS self --- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. --- @param #number Points (optional) The amount of points in the circle. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) - self:F2( SmokeColor ) - - local Point = {} - local Vec2 = self:GetVec2() - - Points = Points and Points or 360 - - local Angle - local RadialBase = math.pi*2 - - for Angle = 0, 360, 360 / Points do - local Radial = Angle * RadialBase / 360 - Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y ):Smoke( SmokeColor ) - end - - return self -end - - ---- Flares the zone boundaries in a color. --- @param #ZONE_RADIUS self --- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. --- @param #number Points (optional) The amount of points in the circle. --- @param Dcs.DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) - self:F2( { FlareColor, Azimuth } ) - - local Point = {} - local Vec2 = self:GetVec2() - - Points = Points and Points or 360 - - local Angle - local RadialBase = math.pi*2 - - for Angle = 0, 360, 360 / Points do - local Radial = Angle * RadialBase / 360 - Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y ):Flare( FlareColor, Azimuth ) - end - - return self -end - ---- Returns the radius of the zone. --- @param #ZONE_RADIUS self --- @return Dcs.DCSTypes#Distance The radius of the zone. -function ZONE_RADIUS:GetRadius() - self:F2( self.ZoneName ) - - self:T2( { self.Radius } ) - - return self.Radius -end - ---- Sets the radius of the zone. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return Dcs.DCSTypes#Distance The radius of the zone. -function ZONE_RADIUS:SetRadius( Radius ) - self:F2( self.ZoneName ) - - self.Radius = Radius - self:T2( { self.Radius } ) - - return self.Radius -end - ---- Returns the @{DCSTypes#Vec2} of the zone. --- @param #ZONE_RADIUS self --- @return Dcs.DCSTypes#Vec2 The location of the zone. -function ZONE_RADIUS:GetVec2() - self:F2( self.ZoneName ) - - self:T2( { self.Vec2 } ) - - return self.Vec2 -end - ---- Sets the @{DCSTypes#Vec2} of the zone. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec2 Vec2 The new location of the zone. --- @return Dcs.DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetVec2( Vec2 ) - self:F2( self.ZoneName ) - - self.Vec2 = Vec2 - - self:T2( { self.Vec2 } ) - - return self.Vec2 -end - ---- Returns the @{DCSTypes#Vec3} of the ZONE_RADIUS. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The point of the zone. -function ZONE_RADIUS:GetVec3( Height ) - self:F2( { self.ZoneName, Height } ) - - Height = Height or 0 - local Vec2 = self:GetVec2() - - local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { Vec3 } ) - - return Vec3 -end - - ---- Returns if a location is within the zone. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_RADIUS:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) - - local ZoneVec2 = self:GetVec2() - - if ZoneVec2 then - if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then - return true - end - end - - return false -end - ---- Returns if a point is within the zone. --- @param #ZONE_RADIUS self --- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. --- @return #boolean true if the point is within the zone. -function ZONE_RADIUS:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) - - local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) - - return InZone -end - ---- Returns a random Vec2 location within the zone. --- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Dcs.DCSTypes#Vec2 The random location within the zone. -function ZONE_RADIUS:GetRandomVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) - - local Point = {} - local Vec2 = self:GetVec2() - local _inner = inner or 0 - local _outer = outer or self:GetRadius() - - local angle = math.random() * math.pi * 2; - Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); - Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); - - self:T( { Point } ) - - return Point -end - ---- Returns a @{Point#POINT_VEC2} object reflecting a random 2D location within the zone. --- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC2 The @{Point#POINT_VEC2} object reflecting the random 3D location within the zone. -function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) - - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - - self:T3( { PointVec2 } ) - - return PointVec2 -end - ---- Returns a @{Point#POINT_VEC3} object reflecting a random 3D location within the zone. --- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#POINT_VEC3 The @{Point#POINT_VEC3} object reflecting the random 3D location within the zone. -function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) - self:F( self.ZoneName, inner, outer ) - - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) - - self:T3( { PointVec3 } ) - - return PointVec3 -end - - - ---- The ZONE class, defined by the zone name as defined within the Mission Editor. The location and the radius are automatically collected from the mission settings. --- @type ZONE --- @extends Core.Zone#ZONE_RADIUS -ZONE = { - ClassName="ZONE", - } - - ---- Constructor of ZONE, taking the zone name. --- @param #ZONE self --- @param #string ZoneName The name of the zone as defined within the mission editor. --- @return #ZONE -function ZONE:New( ZoneName ) - - local Zone = trigger.misc.getZone( ZoneName ) - - if not Zone then - error( "Zone " .. ZoneName .. " does not exist." ) - return nil - end - - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, { x = Zone.point.x, y = Zone.point.z }, Zone.radius ) ) - self:F( ZoneName ) - - self.Zone = Zone - - return self -end - - ---- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. --- @type ZONE_UNIT --- @field Wrapper.Unit#UNIT ZoneUNIT --- @extends Core.Zone#ZONE_RADIUS -ZONE_UNIT = { - ClassName="ZONE_UNIT", - } - ---- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius. --- @param #ZONE_UNIT self --- @param #string ZoneName Name of the zone. --- @param Wrapper.Unit#UNIT ZoneUNIT The unit as the center of the zone. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_UNIT self -function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) - self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) - - self.ZoneUNIT = ZoneUNIT - self.LastVec2 = ZoneUNIT:GetVec2() - - return self -end - - ---- Returns the current location of the @{Unit#UNIT}. --- @param #ZONE_UNIT self --- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. -function ZONE_UNIT:GetVec2() - self:F( self.ZoneName ) - - local ZoneVec2 = self.ZoneUNIT:GetVec2() - if ZoneVec2 then - self.LastVec2 = ZoneVec2 - return ZoneVec2 - else - return self.LastVec2 - end - - self:T( { ZoneVec2 } ) - - return nil -end - ---- Returns a random location within the zone. --- @param #ZONE_UNIT self --- @return Dcs.DCSTypes#Vec2 The random location within the zone. -function ZONE_UNIT:GetRandomVec2() - self:F( self.ZoneName ) - - local RandomVec2 = {} - local Vec2 = self.ZoneUNIT:GetVec2() - - if not Vec2 then - Vec2 = self.LastVec2 - end - - local angle = math.random() * math.pi*2; - RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - - self:T( { RandomVec2 } ) - - return RandomVec2 -end - ---- Returns the @{DCSTypes#Vec3} of the ZONE_UNIT. --- @param #ZONE_UNIT self --- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return Dcs.DCSTypes#Vec3 The point of the zone. -function ZONE_UNIT:GetVec3( Height ) - self:F2( self.ZoneName ) - - Height = Height or 0 - - local Vec2 = self:GetVec2() - - local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { Vec3 } ) - - return Vec3 -end - ---- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. --- @type ZONE_GROUP --- @field Wrapper.Group#GROUP ZoneGROUP --- @extends Core.Zone#ZONE_RADIUS -ZONE_GROUP = { - ClassName="ZONE_GROUP", - } - ---- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Group#GROUP} and a radius. --- @param #ZONE_GROUP self --- @param #string ZoneName Name of the zone. --- @param Wrapper.Group#GROUP ZoneGROUP The @{Group} as the center of the zone. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_GROUP self -function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) ) - self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) - - self.ZoneGROUP = ZoneGROUP - - return self -end - - ---- Returns the current location of the @{Group}. --- @param #ZONE_GROUP self --- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Group} location. -function ZONE_GROUP:GetVec2() - self:F( self.ZoneName ) - - local ZoneVec2 = self.ZoneGROUP:GetVec2() - - self:T( { ZoneVec2 } ) - - return ZoneVec2 -end - ---- Returns a random location within the zone of the @{Group}. --- @param #ZONE_GROUP self --- @return Dcs.DCSTypes#Vec2 The random location of the zone based on the @{Group} location. -function ZONE_GROUP:GetRandomVec2() - self:F( self.ZoneName ) - - local Point = {} - local Vec2 = self.ZoneGROUP:GetVec2() - - local angle = math.random() * math.pi*2; - Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - - self:T( { Point } ) - - return Point -end - - - --- Polygons - ---- The ZONE_POLYGON_BASE class defined by an array of @{DCSTypes#Vec2}, forming a polygon. --- @type ZONE_POLYGON_BASE --- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}. --- @extends Core.Zone#ZONE_BASE -ZONE_POLYGON_BASE = { - ClassName="ZONE_POLYGON_BASE", - } - ---- A points array. --- @type ZONE_POLYGON_BASE.ListVec2 --- @list - ---- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCSTypes#Vec2}, forming a polygon. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. --- @param #ZONE_POLYGON_BASE self --- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCSTypes#Vec2}, forming a polygon.. --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) - local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, PointsArray } ) - - local i = 0 - - self.Polygon = {} - - for i = 1, #PointsArray do - self.Polygon[i] = {} - self.Polygon[i].x = PointsArray[i].x - self.Polygon[i].y = PointsArray[i].y - end - - return self -end - ---- Flush polygon coordinates as a table in DCS.log. --- @param #ZONE_POLYGON_BASE self --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:Flush() - self:F2() - - self:E( { Polygon = self.ZoneName, Coordinates = self.Polygon } ) - - return self -end - ---- Smokes the zone boundaries in a color. --- @param #ZONE_POLYGON_BASE self --- @param #boolean UnBound If true, the tyres will be destroyed. --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:BoundZone( UnBound ) - - local i - local j - local Segments = 10 - - i = 1 - j = #self.Polygon - - while i <= #self.Polygon do - self:T( { i, j, self.Polygon[i], self.Polygon[j] } ) - - local DeltaX = self.Polygon[j].x - self.Polygon[i].x - local DeltaY = self.Polygon[j].y - self.Polygon[i].y - - for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - local PointX = self.Polygon[i].x + ( Segment * DeltaX / Segments ) - local PointY = self.Polygon[i].y + ( Segment * DeltaY / Segments ) - local Tire = { - ["country"] = "USA", - ["category"] = "Fortifications", - ["canCargo"] = false, - ["shape_name"] = "H-tyre_B_WF", - ["type"] = "Black_Tyre_WF", - ["y"] = PointY, - ["x"] = PointX, - ["name"] = string.format( "%s-Tire #%0d", self:GetName(), ((i - 1) * Segments) + Segment ), - ["heading"] = 0, - } -- end of ["group"] - - local Group = coalition.addStaticObject( country.id.USA, Tire ) - if UnBound and UnBound == true then - Group:destroy() - end - - end - j = i - i = i + 1 - end - - return self -end - - - ---- Smokes the zone boundaries in a color. --- @param #ZONE_POLYGON_BASE self --- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:SmokeZone( SmokeColor ) - self:F2( SmokeColor ) - - local i - local j - local Segments = 10 - - i = 1 - j = #self.Polygon - - while i <= #self.Polygon do - self:T( { i, j, self.Polygon[i], self.Polygon[j] } ) - - local DeltaX = self.Polygon[j].x - self.Polygon[i].x - local DeltaY = self.Polygon[j].y - self.Polygon[i].y - - for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - local PointX = self.Polygon[i].x + ( Segment * DeltaX / Segments ) - local PointY = self.Polygon[i].y + ( Segment * DeltaY / Segments ) - POINT_VEC2:New( PointX, PointY ):Smoke( SmokeColor ) - end - j = i - i = i + 1 - end - - return self -end - - - - ---- Returns if a location is within the zone. --- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html --- @param #ZONE_POLYGON_BASE self --- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) - - local Next - local Prev - local InPolygon = false - - Next = 1 - Prev = #self.Polygon - - while Next <= #self.Polygon do - self:T( { Next, Prev, self.Polygon[Next], self.Polygon[Prev] } ) - if ( ( ( self.Polygon[Next].y > Vec2.y ) ~= ( self.Polygon[Prev].y > Vec2.y ) ) and - ( Vec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( Vec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) - ) then - InPolygon = not InPolygon - end - self:T2( { InPolygon = InPolygon } ) - Prev = Next - Next = Next + 1 - end - - self:T( { InPolygon = InPolygon } ) - return InPolygon -end - ---- Define a random @{DCSTypes#Vec2} within the zone. --- @param #ZONE_POLYGON_BASE self --- @return Dcs.DCSTypes#Vec2 The Vec2 coordinate. -function ZONE_POLYGON_BASE:GetRandomVec2() - self:F2() - - --- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... - local Vec2Found = false - local Vec2 - local BS = self:GetBoundingSquare() - - self:T2( BS ) - - while Vec2Found == false do - Vec2 = { x = math.random( BS.x1, BS.x2 ), y = math.random( BS.y1, BS.y2 ) } - self:T2( Vec2 ) - if self:IsVec2InZone( Vec2 ) then - Vec2Found = true - end - end - - self:T2( Vec2 ) - - return Vec2 -end - ---- Return a @{Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. --- @param #ZONE_POLYGON_BASE self --- @return @{Point#POINT_VEC2} -function ZONE_POLYGON_BASE:GetRandomPointVec2() - self:F2() - - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - - self:T2( PointVec2 ) - - return PointVec2 -end - ---- Return a @{Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. --- @param #ZONE_POLYGON_BASE self --- @return @{Point#POINT_VEC3} -function ZONE_POLYGON_BASE:GetRandomPointVec3() - self:F2() - - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) - - self:T2( PointVec3 ) - - return PointVec3 -end - - ---- Get the bounding square the zone. --- @param #ZONE_POLYGON_BASE self --- @return #ZONE_POLYGON_BASE.BoundingSquare The bounding square. -function ZONE_POLYGON_BASE:GetBoundingSquare() - - local x1 = self.Polygon[1].x - local y1 = self.Polygon[1].y - local x2 = self.Polygon[1].x - local y2 = self.Polygon[1].y - - for i = 2, #self.Polygon do - self:T2( { self.Polygon[i], x1, y1, x2, y2 } ) - x1 = ( x1 > self.Polygon[i].x ) and self.Polygon[i].x or x1 - x2 = ( x2 < self.Polygon[i].x ) and self.Polygon[i].x or x2 - y1 = ( y1 > self.Polygon[i].y ) and self.Polygon[i].y or y1 - y2 = ( y2 < self.Polygon[i].y ) and self.Polygon[i].y or y2 - - end - - return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } -end - - - - - ---- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- @type ZONE_POLYGON --- @extends Core.Zone#ZONE_POLYGON_BASE -ZONE_POLYGON = { - ClassName="ZONE_POLYGON", - } - ---- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Group#GROUP} defined within the Mission Editor. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. --- @param #ZONE_POLYGON self --- @param #string ZoneName Name of the zone. --- @param Wrapper.Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. --- @return #ZONE_POLYGON self -function ZONE_POLYGON:New( ZoneName, ZoneGroup ) - - local GroupPoints = ZoneGroup:GetTaskRoute() - - local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, GroupPoints ) ) - self:F( { ZoneName, ZoneGroup, self.Polygon } ) - - return self -end - ---- This module contains the DATABASE class, managing the database of mission objects. --- --- ==== --- --- 1) @{#DATABASE} class, extends @{Base#BASE} --- =================================================== --- Mission designers can use the DATABASE class to refer to: --- --- * UNITS --- * GROUPS --- * CLIENTS --- * AIRPORTS --- * PLAYERSJOINED --- * PLAYERS --- --- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. --- --- Moose will automatically create one instance of the DATABASE class into the **global** object _DATABASE. --- Moose refers to _DATABASE within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. --- --- 1.1) DATABASE iterators --- ----------------------- --- You can iterate the database with the available iterator methods. --- The iterator methods will walk the DATABASE set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the DATABASE: --- --- * @{#DATABASE.ForEachUnit}: Calls a function for each @{UNIT} it finds within the DATABASE. --- * @{#DATABASE.ForEachGroup}: Calls a function for each @{GROUP} it finds within the DATABASE. --- * @{#DATABASE.ForEachPlayer}: Calls a function for each alive player it finds within the DATABASE. --- * @{#DATABASE.ForEachPlayerJoined}: Calls a function for each joined player it finds within the DATABASE. --- * @{#DATABASE.ForEachClient}: Calls a function for each @{CLIENT} it finds within the DATABASE. --- * @{#DATABASE.ForEachClientAlive}: Calls a function for each alive @{CLIENT} it finds within the DATABASE. --- --- === --- --- @module Database --- @author FlightControl - ---- DATABASE class --- @type DATABASE --- @extends Core.Base#BASE -DATABASE = { - ClassName = "DATABASE", - Templates = { - Units = {}, - Groups = {}, - ClientsByName = {}, - ClientsByID = {}, - }, - UNITS = {}, - STATICS = {}, - GROUPS = {}, - PLAYERS = {}, - PLAYERSJOINED = {}, - CLIENTS = {}, - AIRBASES = {}, - COUNTRY_ID = {}, - COUNTRY_NAME = {}, - NavPoints = {}, -} - -local _DATABASECoalition = - { - [1] = "Red", - [2] = "Blue", - } - -local _DATABASECategory = - { - ["plane"] = Unit.Category.AIRPLANE, - ["helicopter"] = Unit.Category.HELICOPTER, - ["vehicle"] = Unit.Category.GROUND_UNIT, - ["ship"] = Unit.Category.SHIP, - ["static"] = Unit.Category.STRUCTURE, - } - - ---- Creates a new DATABASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #DATABASE self --- @return #DATABASE --- @usage --- -- Define a new DATABASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE. --- DBObject = DATABASE:New() -function DATABASE:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self:SetEventPriority( 1 ) - - self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) - self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) - - -- Follow alive players and clients - self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - - self:_RegisterTemplates() - self:_RegisterGroupsAndUnits() - self:_RegisterClients() - self:_RegisterStatics() - self:_RegisterPlayers() - self:_RegisterAirbases() - - return self -end - ---- Finds a Unit based on the Unit Name. --- @param #DATABASE self --- @param #string UnitName --- @return Wrapper.Unit#UNIT The found Unit. -function DATABASE:FindUnit( UnitName ) - - local UnitFound = self.UNITS[UnitName] - return UnitFound -end - - ---- Adds a Unit based on the Unit Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddUnit( DCSUnitName ) - - if not self.UNITS[DCSUnitName] then - local UnitRegister = UNIT:Register( DCSUnitName ) - self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) - end - - return self.UNITS[DCSUnitName] -end - - ---- Deletes a Unit from the DATABASE based on the Unit Name. --- @param #DATABASE self -function DATABASE:DeleteUnit( DCSUnitName ) - - --self.UNITS[DCSUnitName] = nil -end - ---- Adds a Static based on the Static Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddStatic( DCSStaticName ) - - if not self.STATICS[DCSStaticName] then - self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) - end -end - - ---- Deletes a Static from the DATABASE based on the Static Name. --- @param #DATABASE self -function DATABASE:DeleteStatic( DCSStaticName ) - - --self.STATICS[DCSStaticName] = nil -end - ---- Finds a STATIC based on the StaticName. --- @param #DATABASE self --- @param #string StaticName --- @return Wrapper.Static#STATIC The found STATIC. -function DATABASE:FindStatic( StaticName ) - - local StaticFound = self.STATICS[StaticName] - return StaticFound -end - ---- Adds a Airbase based on the Airbase Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddAirbase( DCSAirbaseName ) - - if not self.AIRBASES[DCSAirbaseName] then - self.AIRBASES[DCSAirbaseName] = AIRBASE:Register( DCSAirbaseName ) - end -end - - ---- Deletes a Airbase from the DATABASE based on the Airbase Name. --- @param #DATABASE self -function DATABASE:DeleteAirbase( DCSAirbaseName ) - - --self.AIRBASES[DCSAirbaseName] = nil -end - ---- Finds a AIRBASE based on the AirbaseName. --- @param #DATABASE self --- @param #string AirbaseName --- @return Wrapper.Airbase#AIRBASE The found AIRBASE. -function DATABASE:FindAirbase( AirbaseName ) - - local AirbaseFound = self.AIRBASES[AirbaseName] - return AirbaseFound -end - - ---- Finds a CLIENT based on the ClientName. --- @param #DATABASE self --- @param #string ClientName --- @return Wrapper.Client#CLIENT The found CLIENT. -function DATABASE:FindClient( ClientName ) - - local ClientFound = self.CLIENTS[ClientName] - return ClientFound -end - - ---- Adds a CLIENT based on the ClientName in the DATABASE. --- @param #DATABASE self -function DATABASE:AddClient( ClientName ) - - if not self.CLIENTS[ClientName] then - self.CLIENTS[ClientName] = CLIENT:Register( ClientName ) - end - - return self.CLIENTS[ClientName] -end - - ---- Finds a GROUP based on the GroupName. --- @param #DATABASE self --- @param #string GroupName --- @return Wrapper.Group#GROUP The found GROUP. -function DATABASE:FindGroup( GroupName ) - - local GroupFound = self.GROUPS[GroupName] - return GroupFound -end - - ---- Adds a GROUP based on the GroupName in the DATABASE. --- @param #DATABASE self -function DATABASE:AddGroup( GroupName ) - - if not self.GROUPS[GroupName] then - self:E( { "Add GROUP:", GroupName } ) - self.GROUPS[GroupName] = GROUP:Register( GroupName ) - end - - return self.GROUPS[GroupName] -end - ---- Adds a player based on the Player Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddPlayer( UnitName, PlayerName ) - - if PlayerName then - self:E( { "Add player for unit:", UnitName, PlayerName } ) - self.PLAYERS[PlayerName] = self:FindUnit( UnitName ) - self.PLAYERSJOINED[PlayerName] = PlayerName - end -end - ---- Deletes a player from the DATABASE based on the Player Name. --- @param #DATABASE self -function DATABASE:DeletePlayer( PlayerName ) - - if PlayerName then - self:E( { "Clean player:", PlayerName } ) - self.PLAYERS[PlayerName] = nil - end -end - - ---- Instantiate new Groups within the DCSRTE. --- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined: --- SpawnCountryID, SpawnCategoryID --- This method is used by the SPAWN class. --- @param #DATABASE self --- @param #table SpawnTemplate --- @return #DATABASE self -function DATABASE:Spawn( SpawnTemplate ) - self:F( SpawnTemplate.name ) - - self:T( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } ) - - -- Copy the spawn variables of the template in temporary storage, nullify, and restore the spawn variables. - local SpawnCoalitionID = SpawnTemplate.CoalitionID - local SpawnCountryID = SpawnTemplate.CountryID - local SpawnCategoryID = SpawnTemplate.CategoryID - - -- Nullify - SpawnTemplate.CoalitionID = nil - SpawnTemplate.CountryID = nil - SpawnTemplate.CategoryID = nil - - self:_RegisterTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) - - self:T3( SpawnTemplate ) - coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) - - -- Restore - SpawnTemplate.CoalitionID = SpawnCoalitionID - SpawnTemplate.CountryID = SpawnCountryID - SpawnTemplate.CategoryID = SpawnCategoryID - - local SpawnGroup = self:AddGroup( SpawnTemplate.name ) - return SpawnGroup -end - ---- Set a status to a Group within the Database, this to check crossing events for example. -function DATABASE:SetStatusGroup( GroupName, Status ) - self:F2( Status ) - - self.Templates.Groups[GroupName].Status = Status -end - ---- Get a status to a Group within the Database, this to check crossing events for example. -function DATABASE:GetStatusGroup( GroupName ) - self:F2( Status ) - - if self.Templates.Groups[GroupName] then - return self.Templates.Groups[GroupName].Status - else - return "" - end -end - ---- Private method that registers new Group Templates within the DATABASE Object. --- @param #DATABASE self --- @param #table GroupTemplate --- @return #DATABASE self -function DATABASE:_RegisterTemplate( GroupTemplate, CoalitionID, CategoryID, CountryID ) - - local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name) - - local TraceTable = {} - - if not self.Templates.Groups[GroupTemplateName] then - self.Templates.Groups[GroupTemplateName] = {} - self.Templates.Groups[GroupTemplateName].Status = nil - end - - -- Delete the spans from the route, it is not needed and takes memory. - if GroupTemplate.route and GroupTemplate.route.spans then - GroupTemplate.route.spans = nil - end - - GroupTemplate.CategoryID = CategoryID - GroupTemplate.CoalitionID = CoalitionID - GroupTemplate.CountryID = CountryID - - self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName - self.Templates.Groups[GroupTemplateName].Template = GroupTemplate - self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId - self.Templates.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units - self.Templates.Groups[GroupTemplateName].Units = GroupTemplate.units - self.Templates.Groups[GroupTemplateName].CategoryID = CategoryID - self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionID - self.Templates.Groups[GroupTemplateName].CountryID = CountryID - - - TraceTable[#TraceTable+1] = "Group" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].GroupName - - TraceTable[#TraceTable+1] = "Coalition" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CoalitionID - TraceTable[#TraceTable+1] = "Category" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CategoryID - TraceTable[#TraceTable+1] = "Country" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CountryID - - TraceTable[#TraceTable+1] = "Units" - - for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do - - UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) - - self.Templates.Units[UnitTemplate.name] = {} - self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name - self.Templates.Units[UnitTemplate.name].Template = UnitTemplate - self.Templates.Units[UnitTemplate.name].GroupName = GroupTemplateName - self.Templates.Units[UnitTemplate.name].GroupTemplate = GroupTemplate - self.Templates.Units[UnitTemplate.name].GroupId = GroupTemplate.groupId - self.Templates.Units[UnitTemplate.name].CategoryID = CategoryID - self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionID - self.Templates.Units[UnitTemplate.name].CountryID = CountryID - - if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then - self.Templates.ClientsByName[UnitTemplate.name] = UnitTemplate - self.Templates.ClientsByName[UnitTemplate.name].CategoryID = CategoryID - self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionID - self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID - self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate - end - - TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplate.name].UnitName - end - - self:E( TraceTable ) -end - -function DATABASE:GetGroupTemplate( GroupName ) - local GroupTemplate = self.Templates.Groups[GroupName].Template - GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID - GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID - GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID - return GroupTemplate -end - -function DATABASE:GetGroupNameFromUnitName( UnitName ) - return self.Templates.Units[UnitName].GroupName -end - -function DATABASE:GetGroupTemplateFromUnitName( UnitName ) - return self.Templates.Units[UnitName].GroupTemplate -end - -function DATABASE:GetCoalitionFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CoalitionID -end - -function DATABASE:GetCategoryFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CategoryID -end - -function DATABASE:GetCountryFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CountryID -end - ---- Airbase - -function DATABASE:GetCoalitionFromAirbase( AirbaseName ) - return self.AIRBASES[AirbaseName]:GetCoalition() -end - -function DATABASE:GetCategoryFromAirbase( AirbaseName ) - return self.AIRBASES[AirbaseName]:GetCategory() -end - - - ---- Private method that registers all alive players in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterPlayers() - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - local UnitName = UnitData:getName() - local PlayerName = UnitData:getPlayerName() - if not self.PLAYERS[PlayerName] then - self:E( { "Add player for unit:", UnitName, PlayerName } ) - self:AddPlayer( UnitName, PlayerName ) - end - end - end - end - - return self -end - - ---- Private method that registers all Groups and Units within in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterGroupsAndUnits() - - local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSGroupId, DCSGroup in pairs( CoalitionData ) do - - if DCSGroup:isExist() then - local DCSGroupName = DCSGroup:getName() - - self:E( { "Register Group:", DCSGroupName } ) - self:AddGroup( DCSGroupName ) - - for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do - - local DCSUnitName = DCSUnit:getName() - self:E( { "Register Unit:", DCSUnitName } ) - self:AddUnit( DCSUnitName ) - end - else - self:E( { "Group does not exist: ", DCSGroup } ) - end - - end - end - - return self -end - ---- Private method that registers all Units of skill Client or Player within in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterClients() - - for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do - self:E( { "Register Client:", ClientName } ) - self:AddClient( ClientName ) - end - - return self -end - ---- @param #DATABASE self -function DATABASE:_RegisterStatics() - - local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSStaticId, DCSStatic in pairs( CoalitionData ) do - - if DCSStatic:isExist() then - local DCSStaticName = DCSStatic:getName() - - self:E( { "Register Static:", DCSStaticName } ) - self:AddStatic( DCSStaticName ) - else - self:E( { "Static does not exist: ", DCSStatic } ) - end - end - end - - return self -end - ---- @param #DATABASE self -function DATABASE:_RegisterAirbases() - - local CoalitionsData = { AirbasesRed = coalition.getAirbases( coalition.side.RED ), AirbasesBlue = coalition.getAirbases( coalition.side.BLUE ), AirbasesNeutral = coalition.getAirbases( coalition.side.NEUTRAL ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSAirbaseId, DCSAirbase in pairs( CoalitionData ) do - - local DCSAirbaseName = DCSAirbase:getName() - - self:E( { "Register Airbase:", DCSAirbaseName } ) - self:AddAirbase( DCSAirbaseName ) - end - end - - return self -end - - ---- Events - ---- Handles the OnBirth event for the alive units set. --- @param #DATABASE self --- @param Core.Event#EVENTDATA Event -function DATABASE:_EventOnBirth( Event ) - self:F2( { Event } ) - - if Event.IniDCSUnit then - if Event.IniObjectCategory == 3 then - self:AddStatic( Event.IniDCSUnitName ) - else - if Event.IniObjectCategory == 1 then - self:AddUnit( Event.IniDCSUnitName ) - self:AddGroup( Event.IniDCSGroupName ) - end - end - self:_EventOnPlayerEnterUnit( Event ) - end -end - - ---- Handles the OnDead or OnCrash event for alive units set. --- @param #DATABASE self --- @param Core.Event#EVENTDATA Event -function DATABASE:_EventOnDeadOrCrash( Event ) - self:F2( { Event } ) - - if Event.IniDCSUnit then - if Event.IniObjectCategory == 3 then - if self.STATICS[Event.IniDCSUnitName] then - self:DeleteStatic( Event.IniDCSUnitName ) - end - else - if Event.IniObjectCategory == 1 then - if self.UNITS[Event.IniDCSUnitName] then - self:DeleteUnit( Event.IniDCSUnitName ) - end - end - end - end -end - - ---- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). --- @param #DATABASE self --- @param Core.Event#EVENTDATA Event -function DATABASE:_EventOnPlayerEnterUnit( Event ) - self:F2( { Event } ) - - if Event.IniUnit then - if Event.IniObjectCategory == 1 then - self:AddUnit( Event.IniDCSUnitName ) - self:AddGroup( Event.IniDCSGroupName ) - local PlayerName = Event.IniUnit:GetPlayerName() - if not self.PLAYERS[PlayerName] then - self:AddPlayer( Event.IniUnitName, PlayerName ) - end - end - end -end - - ---- Handles the OnPlayerLeaveUnit event to clean the active players table. --- @param #DATABASE self --- @param Core.Event#EVENTDATA Event -function DATABASE:_EventOnPlayerLeaveUnit( Event ) - self:F2( { Event } ) - - if Event.IniUnit then - if Event.IniObjectCategory == 1 then - local PlayerName = Event.IniUnit:GetPlayerName() - if self.PLAYERS[PlayerName] then - self:DeletePlayer( PlayerName ) - end - end - end -end - ---- Iterators - ---- Iterate the DATABASE and call an iterator function for the given set, providing the Object for each element within the set and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive player in the database. --- @return #DATABASE self -function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) - self:F2( arg ) - - local function CoRoutine() - local Count = 0 - for ObjectID, Object in pairs( Set ) do - self:T2( Object ) - IteratorFunction( Object, unpack( arg ) ) - Count = Count + 1 --- if Count % 100 == 0 then --- coroutine.yield( false ) --- end - end - return true - end - --- local co = coroutine.create( CoRoutine ) - local co = CoRoutine - - local function Schedule() - --- local status, res = coroutine.resume( co ) - local status, res = co() - self:T3( { status, res } ) - - if status == false then - error( res ) - end - if res == false then - return true -- resume next time the loop - end - if FinalizeFunction then - FinalizeFunction( unpack( arg ) ) - end - return false - end - - local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the database. The function needs to accept a UNIT parameter. --- @return #DATABASE self -function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, FinalizeFunction, arg, self.UNITS ) - - return self -end - ---- Iterate the DATABASE and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the database. The function needs to accept a GROUP parameter. --- @return #DATABASE self -function DATABASE:ForEachGroup( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.GROUPS ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each **ALIVE** player, providing the player name and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an player in the database. The function needs to accept the player name. --- @return #DATABASE self -function DATABASE:ForEachPlayer( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.PLAYERS ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each player who has joined the mission, providing the Unit of the player and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is was a player in the database. The function needs to accept a UNIT parameter. --- @return #DATABASE self -function DATABASE:ForEachPlayerJoined( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.PLAYERSJOINED ) - - return self -end - ---- Iterate the DATABASE and call an iterator function for each CLIENT, providing the CLIENT to the function and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a CLIENT parameter. --- @return #DATABASE self -function DATABASE:ForEachClient( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.CLIENTS ) - - return self -end - - -function DATABASE:_RegisterTemplates() - self:F2() - - self.Navpoints = {} - self.UNITS = {} - --Build routines.db.units and self.Navpoints - for CoalitionName, coa_data in pairs(env.mission.coalition) do - - if (CoalitionName == 'red' or CoalitionName == 'blue') and type(coa_data) == 'table' then - --self.Units[coa_name] = {} - - local CoalitionSide = coalition.side[string.upper(CoalitionName)] - - ---------------------------------------------- - -- build nav points DB - self.Navpoints[CoalitionName] = {} - if coa_data.nav_points then --navpoints - for nav_ind, nav_data in pairs(coa_data.nav_points) do - - if type(nav_data) == 'table' then - self.Navpoints[CoalitionName][nav_ind] = routines.utils.deepCopy(nav_data) - - self.Navpoints[CoalitionName][nav_ind]['name'] = nav_data.callsignStr -- name is a little bit more self-explanatory. - self.Navpoints[CoalitionName][nav_ind]['point'] = {} -- point is used by SSE, support it. - self.Navpoints[CoalitionName][nav_ind]['point']['x'] = nav_data.x - self.Navpoints[CoalitionName][nav_ind]['point']['y'] = 0 - self.Navpoints[CoalitionName][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.upper(cntry_data.name) - local CountryID = cntry_data.id - - self.COUNTRY_ID[CountryName] = CountryID - self.COUNTRY_NAME[CountryID] = CountryName - - --self.Units[coa_name][countryName] = {} - --self.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 CategoryName = 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! - - --self.Units[coa_name][countryName][category] = {} - - for group_num, GroupTemplate in pairs(obj_type_data.group) do - - if GroupTemplate and GroupTemplate.units and type(GroupTemplate.units) == 'table' then --making sure again- this is a valid group - self:_RegisterTemplate( - GroupTemplate, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID - ) - end --if GroupTemplate and GroupTemplate.units then - end --for group_num, GroupTemplate 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 - - return self -end - - - - ---- **Core** - SET classes define **collections** of objects to perform **bulk actions** and logically **group** objects. --- --- === --- --- 1) @{Set#SET_BASE} class, extends @{Base#BASE} --- ============================================== --- The @{Set#SET_BASE} class defines the core functions that define a collection of objects. --- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. --- In this way, large loops can be done while not blocking the simulator main processing loop. --- The default **"yield interval"** is after 10 objects processed. --- The default **"time interval"** is after 0.001 seconds. --- --- 1.1) Add or remove objects from the SET --- --------------------------------------- --- Some key core functions are @{Set#SET_BASE.Add} and @{Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. --- --- 1.2) Define the SET iterator **"yield interval"** and the **"time interval"** --- ----------------------------------------------------------------------------- --- Modify the iterator intervals with the @{Set#SET_BASE.SetInteratorIntervals} method. --- You can set the **"yield interval"**, and the **"time interval"**. (See above). --- --- === --- --- 2) @{Set#SET_GROUP} class, extends @{Set#SET_BASE} --- ================================================== --- Mission designers can use the @{Set#SET_GROUP} class to build sets of groups belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Starting with certain prefix strings. --- --- 2.1) SET_GROUP construction method: --- ----------------------------------- --- Create a new SET_GROUP object with the @{#SET_GROUP.New} method: --- --- * @{#SET_GROUP.New}: Creates a new SET_GROUP object. --- --- 2.2) Add or Remove GROUP(s) from SET_GROUP: --- ------------------------------------------- --- GROUPS can be added and removed using the @{Set#SET_GROUP.AddGroupsByName} and @{Set#SET_GROUP.RemoveGroupsByName} respectively. --- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_GROUP. --- --- 2.3) SET_GROUP filter criteria: --- ------------------------------- --- You can set filter criteria to define the set of groups within the SET_GROUP. --- Filter criteria are defined by: --- --- * @{#SET_GROUP.FilterCoalitions}: Builds the SET_GROUP with the groups belonging to the coalition(s). --- * @{#SET_GROUP.FilterCategories}: Builds the SET_GROUP with the groups belonging to the category(ies). --- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the gruops belonging to the country(ies). --- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_GROUP, you can start filtering using: --- --- * @{#SET_GROUP.FilterStart}: Starts the filtering of the groups within the SET_GROUP and add or remove GROUP objects **dynamically**. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Zone#ZONE}. --- --- 2.4) SET_GROUP iterators: --- ------------------------- --- Once the filters have been defined and the SET_GROUP has been built, you can iterate the SET_GROUP with the available iterator methods. --- The iterator methods will walk the SET_GROUP set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_GROUP: --- --- * @{#SET_GROUP.ForEachGroup}: Calls a function for each alive group it finds within the SET_GROUP. --- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupPartlyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- --- ==== --- --- 3) @{Set#SET_UNIT} class, extends @{Set#SET_BASE} --- =================================================== --- Mission designers can use the @{Set#SET_UNIT} class to build sets of units belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Unit types --- * Starting with certain prefix strings. --- --- 3.1) SET_UNIT construction method: --- ---------------------------------- --- Create a new SET_UNIT object with the @{#SET_UNIT.New} method: --- --- * @{#SET_UNIT.New}: Creates a new SET_UNIT object. --- --- 3.2) Add or Remove UNIT(s) from SET_UNIT: --- ----------------------------------------- --- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. --- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. --- --- 3.3) SET_UNIT filter criteria: --- ------------------------------ --- You can set filter criteria to define the set of units within the SET_UNIT. --- Filter criteria are defined by: --- --- * @{#SET_UNIT.FilterCoalitions}: Builds the SET_UNIT with the units belonging to the coalition(s). --- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies). --- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s). --- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). --- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: --- --- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units within the SET_UNIT. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. --- --- 3.4) SET_UNIT iterators: --- ------------------------ --- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. --- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_UNIT: --- --- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT. --- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- --- Planned iterators methods in development are (so these are not yet available): --- --- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT. --- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. --- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. --- --- === --- --- 4) @{Set#SET_CLIENT} class, extends @{Set#SET_BASE} --- =================================================== --- Mission designers can use the @{Set#SET_CLIENT} class to build sets of units belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Client types --- * Starting with certain prefix strings. --- --- 4.1) SET_CLIENT construction method: --- ---------------------------------- --- Create a new SET_CLIENT object with the @{#SET_CLIENT.New} method: --- --- * @{#SET_CLIENT.New}: Creates a new SET_CLIENT object. --- --- 4.2) Add or Remove CLIENT(s) from SET_CLIENT: --- ----------------------------------------- --- CLIENTs can be added and removed using the @{Set#SET_CLIENT.AddClientsByName} and @{Set#SET_CLIENT.RemoveClientsByName} respectively. --- These methods take a single CLIENT name or an array of CLIENT names to be added or removed from SET_CLIENT. --- --- 4.3) SET_CLIENT filter criteria: --- ------------------------------ --- You can set filter criteria to define the set of clients within the SET_CLIENT. --- Filter criteria are defined by: --- --- * @{#SET_CLIENT.FilterCoalitions}: Builds the SET_CLIENT with the clients belonging to the coalition(s). --- * @{#SET_CLIENT.FilterCategories}: Builds the SET_CLIENT with the clients belonging to the category(ies). --- * @{#SET_CLIENT.FilterTypes}: Builds the SET_CLIENT with the clients belonging to the client type(s). --- * @{#SET_CLIENT.FilterCountries}: Builds the SET_CLIENT with the clients belonging to the country(ies). --- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_CLIENT, you can start filtering using: --- --- * @{#SET_CLIENT.FilterStart}: Starts the filtering of the clients within the SET_CLIENT. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Zone#ZONE}. --- --- 4.4) SET_CLIENT iterators: --- ------------------------ --- Once the filters have been defined and the SET_CLIENT has been built, you can iterate the SET_CLIENT with the available iterator methods. --- The iterator methods will walk the SET_CLIENT set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_CLIENT: --- --- * @{#SET_CLIENT.ForEachClient}: Calls a function for each alive client it finds within the SET_CLIENT. --- --- ==== --- --- 5) @{Set#SET_AIRBASE} class, extends @{Set#SET_BASE} --- ==================================================== --- Mission designers can use the @{Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: --- --- * Coalitions --- --- 5.1) SET_AIRBASE construction --- ----------------------------- --- Create a new SET_AIRBASE object with the @{#SET_AIRBASE.New} method: --- --- * @{#SET_AIRBASE.New}: Creates a new SET_AIRBASE object. --- --- 5.2) Add or Remove AIRBASEs from SET_AIRBASE --- -------------------------------------------- --- AIRBASEs can be added and removed using the @{Set#SET_AIRBASE.AddAirbasesByName} and @{Set#SET_AIRBASE.RemoveAirbasesByName} respectively. --- These methods take a single AIRBASE name or an array of AIRBASE names to be added or removed from SET_AIRBASE. --- --- 5.3) SET_AIRBASE filter criteria --- -------------------------------- --- You can set filter criteria to define the set of clients within the SET_AIRBASE. --- Filter criteria are defined by: --- --- * @{#SET_AIRBASE.FilterCoalitions}: Builds the SET_AIRBASE with the airbases belonging to the coalition(s). --- --- Once the filter criteria have been set for the SET_AIRBASE, you can start filtering using: --- --- * @{#SET_AIRBASE.FilterStart}: Starts the filtering of the airbases within the SET_AIRBASE. --- --- 5.4) SET_AIRBASE iterators: --- --------------------------- --- Once the filters have been defined and the SET_AIRBASE has been built, you can iterate the SET_AIRBASE with the available iterator methods. --- The iterator methods will walk the SET_AIRBASE set, and call for each airbase within the set a function that you provide. --- The following iterator methods are currently available within the SET_AIRBASE: --- --- * @{#SET_AIRBASE.ForEachAirbase}: Calls a function for each airbase it finds within the SET_AIRBASE. --- --- ==== --- --- ### Authors: --- --- * FlightControl : Design & Programming --- --- ### Contributions: --- --- --- @module Set - - ---- SET_BASE class --- @type SET_BASE --- @field #table Filter --- @field #table Set --- @field #table List --- @field Core.Scheduler#SCHEDULER CallScheduler --- @extends Core.Base#BASE -SET_BASE = { - ClassName = "SET_BASE", - Filter = {}, - Set = {}, - List = {}, - Index = {}, -} - ---- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_BASE self --- @return #SET_BASE --- @usage --- -- Define a new SET_BASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE. --- DBObject = SET_BASE:New() -function SET_BASE:New( Database ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Core.Set#SET_BASE - - self.Database = Database - - self.YieldInterval = 10 - self.TimeInterval = 0.001 - - self.Set = {} - - self.List = {} - self.List.__index = self.List - self.List = setmetatable( { Count = 0 }, self.List ) - - self.Index = {} - - self.CallScheduler = SCHEDULER:New( self ) - - self:SetEventPriority( 2 ) - - return self -end - ---- Finds an @{Base#BASE} object based on the object Name. --- @param #SET_BASE self --- @param #string ObjectName --- @return Core.Base#BASE The Object found. -function SET_BASE:_Find( ObjectName ) - - local ObjectFound = self.Set[ObjectName] - return ObjectFound -end - - ---- Gets the Set. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:GetSet() - self:F2() - - return self.Set -end - ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using a given ObjectName as the index. --- @param #SET_BASE self --- @param #string ObjectName --- @param Core.Base#BASE Object --- @return Core.Base#BASE The added BASE Object. -function SET_BASE:Add( ObjectName, Object ) - self:F2( ObjectName ) - - local t = { _ = Object } - - if self.List.last then - self.List.last._next = t - t._prev = self.List.last - self.List.last = t - else - -- this is the first node - self.List.first = t - self.List.last = t - end - - self.List.Count = self.List.Count + 1 - - self.Set[ObjectName] = t._ - - table.insert( self.Index, ObjectName ) - -end - ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using the Object Name as the index. --- @param #SET_BASE self --- @param Wrapper.Object#OBJECT Object --- @return Core.Base#BASE The added BASE Object. -function SET_BASE:AddObject( Object ) - self:F2( Object.ObjectName ) - - self:T( Object.UnitName ) - self:T( Object.ObjectName ) - self:Add( Object.ObjectName, Object ) - -end - - - ---- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. --- @param #SET_BASE self --- @param #string ObjectName -function SET_BASE:Remove( ObjectName ) - self:F( ObjectName ) - - local t = self.Set[ObjectName] - - self:E( { ObjectName, t } ) - - if t then - if t._next then - if t._prev then - t._next._prev = t._prev - t._prev._next = t._next - else - -- this was the first node - t._next._prev = nil - self.List._first = t._next - end - elseif t._prev then - -- this was the last node - t._prev._next = nil - self.List._last = t._prev - else - -- this was the only node - self.List._first = nil - self.List._last = nil - end - - t._next = nil - t._prev = nil - self.List.Count = self.List.Count - 1 - - for Index, Key in ipairs( self.Index ) do - if Key == ObjectName then - table.remove( self.Index, Index ) - break - end - end - - self.Set[ObjectName] = nil - - end - -end - ---- Gets a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. --- @param #SET_BASE self --- @param #string ObjectName --- @return Core.Base#BASE -function SET_BASE:Get( ObjectName ) - self:F( ObjectName ) - - local t = self.Set[ObjectName] - - self:T3( { ObjectName, t } ) - - return t - -end - ---- Gets the first object from the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return Core.Base#BASE -function SET_BASE:GetFirst() - self:F() - - local ObjectName = self.Index[1] - local FirstObject = self.Set[ObjectName] - self:T3( { FirstObject } ) - return FirstObject -end - ---- Gets the last object from the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return Core.Base#BASE -function SET_BASE:GetLast() - self:F() - - local ObjectName = self.Index[#self.Index] - local LastObject = self.Set[ObjectName] - self:T3( { LastObject } ) - return LastObject -end - ---- Gets a random object from the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return Core.Base#BASE -function SET_BASE:GetRandom() - self:F() - - local RandomItem = self.Set[self.Index[math.random(#self.Index)]] - - self:T3( { RandomItem } ) - - return RandomItem -end - - ---- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return #number Count -function SET_BASE:Count() - - return #self.Index -end - - - ---- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). --- @param #SET_BASE self --- @param #SET_BASE BaseSet --- @return #SET_BASE -function SET_BASE:SetDatabase( BaseSet ) - - -- Copy the filter criteria of the BaseSet - local OtherFilter = routines.utils.deepCopy( BaseSet.Filter ) - self.Filter = OtherFilter - - -- Now base the new Set on the BaseSet - self.Database = BaseSet:GetSet() - return self -end - - - ---- Define the SET iterator **"yield interval"** and the **"time interval"**. --- @param #SET_BASE self --- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. --- @param #number TimeInterval Sets the time in seconds when the main logic will resume the iterator loop. The default time is 0.001 seconds. --- @return #SET_BASE self -function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) - - self.YieldInterval = YieldInterval - self.TimeInterval = TimeInterval - - return self -end - - ---- Filters for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:FilterOnce() - - for ObjectName, Object in pairs( self.Database ) do - - if self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - end - end - - return self -end - ---- Starts the filtering for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:_FilterStart() - - for ObjectName, Object in pairs( self.Database ) do - - if self:IsIncludeObject( Object ) then - self:E( { "Adding Object:", ObjectName } ) - self:Add( ObjectName, Object ) - end - end - - self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) - self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) - - -- Follow alive players and clients - self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) - self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - - - return self -end - ---- Stops the filtering for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:FilterStop() - - self:UnHandleEvent( EVENTS.Birth ) - self:UnHandleEvent( EVENTS.Dead ) - self:UnHandleEvent( EVENTS.Crash ) - - return self -end - ---- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. --- @param #SET_BASE self --- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. --- @return Core.Base#BASE The closest object. -function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) - - local NearestObject = nil - local ClosestDistance = nil - - for ObjectID, ObjectData in pairs( self.Set ) do - if NearestObject == nil then - NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) - else - local Distance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) - if Distance < ClosestDistance then - NearestObject = ObjectData - ClosestDistance = Distance - end - end - end - - return NearestObject -end - - - ------ Private method that registers all alive players in the mission. ----- @param #SET_BASE self ----- @return #SET_BASE self ---function SET_BASE:_RegisterPlayers() --- --- local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } --- for CoalitionId, CoalitionData in pairs( CoalitionsData ) do --- for UnitId, UnitData in pairs( CoalitionData ) do --- self:T3( { "UnitData:", UnitData } ) --- if UnitData and UnitData:isExist() then --- local UnitName = UnitData:getName() --- if not self.PlayersAlive[UnitName] then --- self:E( { "Add player for unit:", UnitName, UnitData:getPlayerName() } ) --- self.PlayersAlive[UnitName] = UnitData:getPlayerName() --- end --- end --- end --- end --- --- return self ---end - ---- Events - ---- Handles the OnBirth event for the Set. --- @param #SET_BASE self --- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnBirth( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:AddInDatabase( Event ) - self:T3( ObjectName, Object ) - if Object and self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) - end - end -end - ---- Handles the OnDead or OnCrash event for alive units set. --- @param #SET_BASE self --- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnDeadOrCrash( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:FindInDatabase( Event ) - if ObjectName and Object ~= nil then - self:Remove( ObjectName ) - end - end -end - ---- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). --- @param #SET_BASE self --- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnPlayerEnterUnit( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:AddInDatabase( Event ) - self:T3( ObjectName, Object ) - if self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) - end - end -end - ---- Handles the OnPlayerLeaveUnit event to clean the active players table. --- @param #SET_BASE self --- @param Core.Event#EVENTDATA Event -function SET_BASE:_EventOnPlayerLeaveUnit( Event ) - self:F3( { Event } ) - - local ObjectName = Event.IniDCSUnit - if Event.IniDCSUnit then - if Event.IniDCSGroup then - local GroupUnits = Event.IniDCSGroup:getUnits() - local PlayerCount = 0 - for _, DCSUnit in pairs( GroupUnits ) do - if DCSUnit ~= Event.IniDCSUnit then - if DCSUnit:getPlayer() ~= nil then - PlayerCount = PlayerCount + 1 - end - end - end - self:E(PlayerCount) - if PlayerCount == 0 then - self:Remove( Event.IniDCSGroupName ) - end - end - end -end - --- Iterators - ---- Iterate the SET_BASE and derived classes and call an iterator function for the given SET_BASE, providing the Object for each element within the set and optional parameters. --- @param #SET_BASE self --- @param #function IteratorFunction The function that will be called. --- @return #SET_BASE self -function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) - self:F3( arg ) - - Set = Set or self:GetSet() - arg = arg or {} - - local function CoRoutine() - local Count = 0 - for ObjectID, ObjectData in pairs( Set ) do - local Object = ObjectData - self:T3( Object ) - if Function then - if Function( unpack( FunctionArguments ), Object ) == true then - IteratorFunction( Object, unpack( arg ) ) - end - else - IteratorFunction( Object, unpack( arg ) ) - end - Count = Count + 1 --- if Count % self.YieldInterval == 0 then --- coroutine.yield( false ) --- end - end - return true - end - --- local co = coroutine.create( CoRoutine ) - local co = CoRoutine - - local function Schedule() - --- local status, res = coroutine.resume( co ) - local status, res = co() - self:T3( { status, res } ) - - if status == false then - error( res ) - end - if res == false then - return true -- resume next time the loop - end - - return false - end - - --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) - Schedule() - - return self -end - - ------ Iterate the SET_BASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) --- --- return self ---end --- ------ Iterate the SET_BASE and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachPlayer( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_BASE and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachClient( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- Decides whether to include the Object --- @param #SET_BASE self --- @param #table Object --- @return #SET_BASE self -function SET_BASE:IsIncludeObject( Object ) - self:F3( Object ) - - return true -end - ---- Flushes the current SET_BASE contents in the log ... (for debugging reasons). --- @param #SET_BASE self --- @return #string A string with the names of the objects. -function SET_BASE:Flush() - self:F3() - - local ObjectNames = "" - for ObjectName, Object in pairs( self.Set ) do - ObjectNames = ObjectNames .. ObjectName .. ", " - end - self:E( { "Objects in Set:", ObjectNames } ) - - return ObjectNames -end - --- SET_GROUP - ---- SET_GROUP class --- @type SET_GROUP --- @extends Core.Set#SET_BASE -SET_GROUP = { - ClassName = "SET_GROUP", - Filter = { - Coalitions = nil, - Categories = nil, - Countries = nil, - GroupPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Group.Category.AIRPLANE, - helicopter = Group.Category.HELICOPTER, - ground = Group.Category.GROUND_UNIT, - ship = Group.Category.SHIP, - structure = Group.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_GROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_GROUP self --- @return #SET_GROUP --- @usage --- -- Define a new SET_GROUP Object. This DBObject will contain a reference to all alive GROUPS. --- DBObject = SET_GROUP:New() -function SET_GROUP:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.GROUPS ) ) - - return self -end - ---- Add GROUP(s) to SET_GROUP. --- @param Core.Set#SET_GROUP self --- @param #string AddGroupNames A single name or an array of GROUP names. --- @return self -function SET_GROUP:AddGroupsByName( AddGroupNames ) - - local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } - - for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do - self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) - end - - return self -end - ---- Remove GROUP(s) from SET_GROUP. --- @param Core.Set#SET_GROUP self --- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. --- @return self -function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) - - local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } - - for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do - self:Remove( RemoveGroupName.GroupName ) - end - - return self -end - - - - ---- Finds a Group based on the Group Name. --- @param #SET_GROUP self --- @param #string GroupName --- @return Wrapper.Group#GROUP The found Group. -function SET_GROUP:FindGroup( GroupName ) - - local GroupFound = self.Set[GroupName] - return GroupFound -end - - - ---- Builds a set of groups of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_GROUP self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_GROUP self -function SET_GROUP:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of groups out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_GROUP self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_GROUP self -function SET_GROUP:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - ---- Builds a set of groups of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_GROUP self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_GROUP self -function SET_GROUP:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of groups of defined GROUP prefixes. --- All the groups starting with the given prefixes will be included within the set. --- @param #SET_GROUP self --- @param #string Prefixes The prefix of which the group name starts with. --- @return #SET_GROUP self -function SET_GROUP:FilterPrefixes( Prefixes ) - if not self.Filter.GroupPrefixes then - self.Filter.GroupPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.GroupPrefixes[Prefix] = Prefix - end - return self -end - - ---- Starts the filtering. --- @param #SET_GROUP self --- @return #SET_GROUP self -function SET_GROUP:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_GROUP self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the GROUP --- @return #table The GROUP -function SET_GROUP:AddInDatabase( Event ) - self:F3( { Event } ) - - if Event.IniObjectCategory == 1 then - if not self.Database[Event.IniDCSGroupName] then - self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) - self:T3( self.Database[Event.IniDCSGroupName] ) - end - end - - return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_GROUP self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the GROUP --- @return #table The GROUP -function SET_GROUP:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. --- @param #SET_GROUP self --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroup( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsCompletelyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsPartlyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - - ------ Iterate the SET_GROUP and call an interator function for each **alive** player, providing the Group of the player and optional parameters. ----- @param #SET_GROUP self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ----- @return #SET_GROUP self ---function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_GROUP and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_GROUP self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ----- @return #SET_GROUP self ---function SET_GROUP:ForEachClient( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- --- @param #SET_GROUP self --- @param Wrapper.Group#GROUP MooseGroup --- @return #SET_GROUP self -function SET_GROUP:IsIncludeObject( MooseGroup ) - self:F2( MooseGroup ) - local MooseGroupInclude = true - - if self.Filter.Coalitions then - local MooseGroupCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MooseGroup:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MooseGroup:GetCoalition() then - MooseGroupCoalition = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCoalition - end - - if self.Filter.Categories then - local MooseGroupCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MooseGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MooseGroup:GetCategory() then - MooseGroupCategory = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCategory - end - - if self.Filter.Countries then - local MooseGroupCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MooseGroup:GetCountry(), CountryName } ) - if country.id[CountryName] == MooseGroup:GetCountry() then - MooseGroupCountry = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCountry - end - - if self.Filter.GroupPrefixes then - local MooseGroupPrefix = false - for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do - self:T3( { "Prefix:", string.find( MooseGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MooseGroup:GetName(), GroupPrefix, 1 ) then - MooseGroupPrefix = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupPrefix - end - - self:T2( MooseGroupInclude ) - return MooseGroupInclude -end - ---- SET_UNIT class --- @type SET_UNIT --- @extends Core.Set#SET_BASE -SET_UNIT = { - ClassName = "SET_UNIT", - Units = {}, - Filter = { - Coalitions = nil, - Categories = nil, - Types = nil, - Countries = nil, - UnitPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Unit.Category.AIRPLANE, - helicopter = Unit.Category.HELICOPTER, - ground = Unit.Category.GROUND_UNIT, - ship = Unit.Category.SHIP, - structure = Unit.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_UNIT object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_UNIT self --- @return #SET_UNIT --- @usage --- -- Define a new SET_UNIT Object. This DBObject will contain a reference to all alive Units. --- DBObject = SET_UNIT:New() -function SET_UNIT:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) - - return self -end - ---- Add UNIT(s) to SET_UNIT. --- @param #SET_UNIT self --- @param #string AddUnit A single UNIT. --- @return #SET_UNIT self -function SET_UNIT:AddUnit( AddUnit ) - self:F2( AddUnit:GetName() ) - - self:Add( AddUnit:GetName(), AddUnit ) - - return self -end - - ---- Add UNIT(s) to SET_UNIT. --- @param #SET_UNIT self --- @param #string AddUnitNames A single name or an array of UNIT names. --- @return #SET_UNIT self -function SET_UNIT:AddUnitsByName( AddUnitNames ) - - local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } - - self:T( AddUnitNamesArray ) - for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do - self:Add( AddUnitName, UNIT:FindByName( AddUnitName ) ) - end - - return self -end - ---- Remove UNIT(s) from SET_UNIT. --- @param Core.Set#SET_UNIT self --- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. --- @return self -function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - - local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } - - for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do - self:Remove( RemoveUnitName ) - end - - return self -end - - ---- Finds a Unit based on the Unit Name. --- @param #SET_UNIT self --- @param #string UnitName --- @return Wrapper.Unit#UNIT The found Unit. -function SET_UNIT:FindUnit( UnitName ) - - local UnitFound = self.Set[UnitName] - return UnitFound -end - - - ---- Builds a set of units of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_UNIT self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_UNIT self -function SET_UNIT:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of units out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_UNIT self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_UNIT self -function SET_UNIT:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - - ---- Builds a set of units of defined unit types. --- Possible current types are those types known within DCS world. --- @param #SET_UNIT self --- @param #string Types Can take those type strings known within DCS world. --- @return #SET_UNIT self -function SET_UNIT:FilterTypes( Types ) - if not self.Filter.Types then - self.Filter.Types = {} - end - if type( Types ) ~= "table" then - Types = { Types } - end - for TypeID, Type in pairs( Types ) do - self.Filter.Types[Type] = Type - end - return self -end - - ---- Builds a set of units of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_UNIT self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_UNIT self -function SET_UNIT:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of units of defined unit prefixes. --- All the units starting with the given prefixes will be included within the set. --- @param #SET_UNIT self --- @param #string Prefixes The prefix of which the unit name starts with. --- @return #SET_UNIT self -function SET_UNIT:FilterPrefixes( Prefixes ) - if not self.Filter.UnitPrefixes then - self.Filter.UnitPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.UnitPrefixes[Prefix] = Prefix - end - return self -end - ---- Builds a set of units having a radar of give types. --- All the units having a radar of a given type will be included within the set. --- @param #SET_UNIT self --- @param #table RadarTypes The radar types. --- @return #SET_UNIT self -function SET_UNIT:FilterHasRadar( RadarTypes ) - - self.Filter.RadarTypes = self.Filter.RadarTypes or {} - if type( RadarTypes ) ~= "table" then - RadarTypes = { RadarTypes } - end - for RadarTypeID, RadarType in pairs( RadarTypes ) do - self.Filter.RadarTypes[RadarType] = RadarType - end - return self -end - ---- Builds a set of SEADable units. --- @param #SET_UNIT self --- @return #SET_UNIT self -function SET_UNIT:FilterHasSEAD() - - self.Filter.SEAD = true - return self -end - - - ---- Starts the filtering. --- @param #SET_UNIT self --- @return #SET_UNIT self -function SET_UNIT:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_UNIT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the UNIT --- @return #table The UNIT -function SET_UNIT:AddInDatabase( Event ) - self:F3( { Event } ) - - if Event.IniObjectCategory == 1 then - if not self.Database[Event.IniDCSUnitName] then - self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName ) - self:T3( self.Database[Event.IniDCSUnitName] ) - end - end - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_UNIT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the UNIT --- @return #table The UNIT -function SET_UNIT:FindInDatabase( Event ) - self:E( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - - - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] -end - ---- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. --- @param #SET_UNIT self --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnit( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. --- @param #SET_UNIT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsCompletelyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. --- @param #SET_UNIT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Returns map of unit types. --- @param #SET_UNIT self --- @return #map<#string,#number> A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. -function SET_UNIT:GetUnitTypes() - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for UnitID, UnitData in pairs( self:GetSet() ) do - local TextUnit = UnitData -- Wrapper.Unit#UNIT - if TextUnit:IsAlive() then - local UnitType = TextUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return UnitTypes -end - - ---- Returns a comma separated string of the unit types with a count in the @{Set}. --- @param #SET_UNIT self --- @return #string The unit types string -function SET_UNIT:GetUnitTypesText() - self:F2() - - local MT = {} -- Message Text - local UnitTypes = self:GetUnitTypes() - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) -end - ---- Returns map of unit threat levels. --- @param #SET_UNIT self --- @return #table. -function SET_UNIT:GetUnitThreatLevels() - self:F2() - - local UnitThreatLevels = {} - - for UnitID, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - if ThreatUnit:IsAlive() then - local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() - local ThreatUnitName = ThreatUnit:GetName() - - UnitThreatLevels[UnitThreatLevel] = UnitThreatLevels[UnitThreatLevel] or {} - UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText = UnitThreatLevelText - UnitThreatLevels[UnitThreatLevel].Units = UnitThreatLevels[UnitThreatLevel].Units or {} - UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName] = ThreatUnit - end - end - - return UnitThreatLevels -end - ---- Calculate the maxium A2G threat level of the SET_UNIT. --- @param #SET_UNIT self -function SET_UNIT:CalculateThreatLevelA2G() - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - return MaxThreatLevelA2G - -end - - ---- Returns if the @{Set} has targets having a radar (of a given type). --- @param #SET_UNIT self --- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType --- @return #number The amount of radars in the Set with the given type -function SET_UNIT:HasRadar( RadarType ) - self:F2( RadarType ) - - local RadarCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT - local HasSensors - if RadarType then - HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) - else - HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) - end - self:T3(HasSensors) - if HasSensors then - RadarCount = RadarCount + 1 - end - end - - return RadarCount -end - ---- Returns if the @{Set} has targets that can be SEADed. --- @param #SET_UNIT self --- @return #number The amount of SEADable units in the Set -function SET_UNIT:HasSEAD() - self:F2() - - local SEADCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSEAD = UnitData -- Wrapper.Unit#UNIT - if UnitSEAD:IsAlive() then - local UnitSEADAttributes = UnitSEAD:GetDesc().attributes - - local HasSEAD = UnitSEAD:HasSEAD() - - self:T3(HasSEAD) - if HasSEAD then - SEADCount = SEADCount + 1 - end - end - end - - return SEADCount -end - ---- Returns if the @{Set} has ground targets. --- @param #SET_UNIT self --- @return #number The amount of ground targets in the Set. -function SET_UNIT:HasGroundUnits() - self:F2() - - local GroundUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Wrapper.Unit#UNIT - if UnitTest:IsGround() then - GroundUnitCount = GroundUnitCount + 1 - end - end - - return GroundUnitCount -end - ---- Returns if the @{Set} has friendly ground units. --- @param #SET_UNIT self --- @return #number The amount of ground targets in the Set. -function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) - self:F2() - - local FriendlyUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Wrapper.Unit#UNIT - if UnitTest:IsFriendly( FriendlyCoalition ) then - FriendlyUnitCount = FriendlyUnitCount + 1 - end - end - - return FriendlyUnitCount -end - - - ------ Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ----- @param #SET_UNIT self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ----- @return #SET_UNIT self ---function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_UNIT self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ----- @return #SET_UNIT self ---function SET_UNIT:ForEachClient( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- --- @param #SET_UNIT self --- @param Wrapper.Unit#UNIT MUnit --- @return #SET_UNIT self -function SET_UNIT:IsIncludeObject( MUnit ) - self:F2( MUnit ) - local MUnitInclude = true - - if self.Filter.Coalitions then - local MUnitCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then - MUnitCoalition = true - end - end - MUnitInclude = MUnitInclude and MUnitCoalition - end - - if self.Filter.Categories then - local MUnitCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then - MUnitCategory = true - end - end - MUnitInclude = MUnitInclude and MUnitCategory - end - - if self.Filter.Types then - local MUnitType = false - for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) - if TypeName == MUnit:GetTypeName() then - MUnitType = true - end - end - MUnitInclude = MUnitInclude and MUnitType - end - - if self.Filter.Countries then - local MUnitCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) - if country.id[CountryName] == MUnit:GetCountry() then - MUnitCountry = true - end - end - MUnitInclude = MUnitInclude and MUnitCountry - end - - if self.Filter.UnitPrefixes then - local MUnitPrefix = false - for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do - self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) - if string.find( MUnit:GetName(), UnitPrefix, 1 ) then - MUnitPrefix = true - end - end - MUnitInclude = MUnitInclude and MUnitPrefix - end - - if self.Filter.RadarTypes then - local MUnitRadar = false - for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do - self:T3( { "Radar:", RadarType } ) - if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then - if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. - self:T3( "RADAR Found" ) - end - MUnitRadar = true - end - end - MUnitInclude = MUnitInclude and MUnitRadar - end - - if self.Filter.SEAD then - local MUnitSEAD = false - if MUnit:HasSEAD() == true then - self:T3( "SEAD Found" ) - MUnitSEAD = true - end - MUnitInclude = MUnitInclude and MUnitSEAD - end - - self:T2( MUnitInclude ) - return MUnitInclude -end - - ---- SET_CLIENT - ---- SET_CLIENT class --- @type SET_CLIENT --- @extends Core.Set#SET_BASE -SET_CLIENT = { - ClassName = "SET_CLIENT", - Clients = {}, - Filter = { - Coalitions = nil, - Categories = nil, - Types = nil, - Countries = nil, - ClientPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Unit.Category.AIRPLANE, - helicopter = Unit.Category.HELICOPTER, - ground = Unit.Category.GROUND_UNIT, - ship = Unit.Category.SHIP, - structure = Unit.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_CLIENT object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_CLIENT self --- @return #SET_CLIENT --- @usage --- -- Define a new SET_CLIENT Object. This DBObject will contain a reference to all Clients. --- DBObject = SET_CLIENT:New() -function SET_CLIENT:New() - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CLIENTS ) ) - - return self -end - ---- Add CLIENT(s) to SET_CLIENT. --- @param Core.Set#SET_CLIENT self --- @param #string AddClientNames A single name or an array of CLIENT names. --- @return self -function SET_CLIENT:AddClientsByName( AddClientNames ) - - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } - - for AddClientID, AddClientName in pairs( AddClientNamesArray ) do - self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) - end - - return self -end - ---- Remove CLIENT(s) from SET_CLIENT. --- @param Core.Set#SET_CLIENT self --- @param Wrapper.Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. --- @return self -function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) - - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } - - for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do - self:Remove( RemoveClientName.ClientName ) - end - - return self -end - - ---- Finds a Client based on the Client Name. --- @param #SET_CLIENT self --- @param #string ClientName --- @return Wrapper.Client#CLIENT The found Client. -function SET_CLIENT:FindClient( ClientName ) - - local ClientFound = self.Set[ClientName] - return ClientFound -end - - - ---- Builds a set of clients of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_CLIENT self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_CLIENT self -function SET_CLIENT:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of clients out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_CLIENT self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_CLIENT self -function SET_CLIENT:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - - ---- Builds a set of clients of defined client types. --- Possible current types are those types known within DCS world. --- @param #SET_CLIENT self --- @param #string Types Can take those type strings known within DCS world. --- @return #SET_CLIENT self -function SET_CLIENT:FilterTypes( Types ) - if not self.Filter.Types then - self.Filter.Types = {} - end - if type( Types ) ~= "table" then - Types = { Types } - end - for TypeID, Type in pairs( Types ) do - self.Filter.Types[Type] = Type - end - return self -end - - ---- Builds a set of clients of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_CLIENT self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_CLIENT self -function SET_CLIENT:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of clients of defined client prefixes. --- All the clients starting with the given prefixes will be included within the set. --- @param #SET_CLIENT self --- @param #string Prefixes The prefix of which the client name starts with. --- @return #SET_CLIENT self -function SET_CLIENT:FilterPrefixes( Prefixes ) - if not self.Filter.ClientPrefixes then - self.Filter.ClientPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.ClientPrefixes[Prefix] = Prefix - end - return self -end - - - - ---- Starts the filtering. --- @param #SET_CLIENT self --- @return #SET_CLIENT self -function SET_CLIENT:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_CLIENT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the CLIENT --- @return #table The CLIENT -function SET_CLIENT:AddInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_CLIENT self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the CLIENT --- @return #table The CLIENT -function SET_CLIENT:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Iterate the SET_CLIENT and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. --- @param #SET_CLIENT self --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClient( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. --- @param #SET_CLIENT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. --- @param #SET_CLIENT self --- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Core.Zone#ZONE_BASE ZoneObject - -- @param Wrapper.Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- --- @param #SET_CLIENT self --- @param Wrapper.Client#CLIENT MClient --- @return #SET_CLIENT self -function SET_CLIENT:IsIncludeObject( MClient ) - self:F2( MClient ) - - local MClientInclude = true - - if MClient then - local MClientName = MClient.UnitName - - if self.Filter.Coalitions then - local MClientCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - local ClientCoalitionID = _DATABASE:GetCoalitionFromClientTemplate( MClientName ) - self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == ClientCoalitionID then - MClientCoalition = true - end - end - self:T( { "Evaluated Coalition", MClientCoalition } ) - MClientInclude = MClientInclude and MClientCoalition - end - - if self.Filter.Categories then - local MClientCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - local ClientCategoryID = _DATABASE:GetCategoryFromClientTemplate( MClientName ) - self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then - MClientCategory = true - end - end - self:T( { "Evaluated Category", MClientCategory } ) - MClientInclude = MClientInclude and MClientCategory - end - - if self.Filter.Types then - local MClientType = false - for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) - if TypeName == MClient:GetTypeName() then - MClientType = true - end - end - self:T( { "Evaluated Type", MClientType } ) - MClientInclude = MClientInclude and MClientType - end - - if self.Filter.Countries then - local MClientCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) - self:T3( { "Country:", ClientCountryID, country.id[CountryName], CountryName } ) - if country.id[CountryName] and country.id[CountryName] == ClientCountryID then - MClientCountry = true - end - end - self:T( { "Evaluated Country", MClientCountry } ) - MClientInclude = MClientInclude and MClientCountry - end - - if self.Filter.ClientPrefixes then - local MClientPrefix = false - for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do - self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) - if string.find( MClient.UnitName, ClientPrefix, 1 ) then - MClientPrefix = true - end - end - self:T( { "Evaluated Prefix", MClientPrefix } ) - MClientInclude = MClientInclude and MClientPrefix - end - end - - self:T2( MClientInclude ) - return MClientInclude -end - ---- SET_AIRBASE - ---- SET_AIRBASE class --- @type SET_AIRBASE --- @extends Core.Set#SET_BASE -SET_AIRBASE = { - ClassName = "SET_AIRBASE", - Airbases = {}, - Filter = { - Coalitions = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - airdrome = Airbase.Category.AIRDROME, - helipad = Airbase.Category.HELIPAD, - ship = Airbase.Category.SHIP, - }, - }, -} - - ---- Creates a new SET_AIRBASE object, building a set of airbases belonging to a coalitions and categories. --- @param #SET_AIRBASE self --- @return #SET_AIRBASE self --- @usage --- -- Define a new SET_AIRBASE Object. The DatabaseSet will contain a reference to all Airbases. --- DatabaseSet = SET_AIRBASE:New() -function SET_AIRBASE:New() - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.AIRBASES ) ) - - return self -end - ---- Add AIRBASEs to SET_AIRBASE. --- @param Core.Set#SET_AIRBASE self --- @param #string AddAirbaseNames A single name or an array of AIRBASE names. --- @return self -function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) - - local AddAirbaseNamesArray = ( type( AddAirbaseNames ) == "table" ) and AddAirbaseNames or { AddAirbaseNames } - - for AddAirbaseID, AddAirbaseName in pairs( AddAirbaseNamesArray ) do - self:Add( AddAirbaseName, AIRBASE:FindByName( AddAirbaseName ) ) - end - - return self -end - ---- Remove AIRBASEs from SET_AIRBASE. --- @param Core.Set#SET_AIRBASE self --- @param Wrapper.Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. --- @return self -function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) - - local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } - - for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do - self:Remove( RemoveAirbaseName.AirbaseName ) - end - - return self -end - - ---- Finds a Airbase based on the Airbase Name. --- @param #SET_AIRBASE self --- @param #string AirbaseName --- @return Wrapper.Airbase#AIRBASE The found Airbase. -function SET_AIRBASE:FindAirbase( AirbaseName ) - - local AirbaseFound = self.Set[AirbaseName] - return AirbaseFound -end - - - ---- Builds a set of airbases of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_AIRBASE self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of airbases out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_AIRBASE self --- @param #string Categories Can take the following values: "airdrome", "helipad", "ship". --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - ---- Starts the filtering. --- @param #SET_AIRBASE self --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_AIRBASE self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the AIRBASE --- @return #table The AIRBASE -function SET_AIRBASE:AddInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_AIRBASE self --- @param Core.Event#EVENTDATA Event --- @return #string The name of the AIRBASE --- @return #table The AIRBASE -function SET_AIRBASE:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Iterate the SET_AIRBASE and call an interator function for each AIRBASE, providing the AIRBASE and optional parameters. --- @param #SET_AIRBASE self --- @param #function IteratorFunction The function that will be called when there is an alive AIRBASE in the SET_AIRBASE. The function needs to accept a AIRBASE parameter. --- @return #SET_AIRBASE self -function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_AIRBASE while identifying the nearest @{Airbase#AIRBASE} from a @{Point#POINT_VEC2}. --- @param #SET_AIRBASE self --- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Airbase#AIRBASE}. --- @return Wrapper.Airbase#AIRBASE The closest @{Airbase#AIRBASE}. -function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) - - local NearestAirbase = self:FindNearestObjectFromPointVec2( PointVec2 ) - return NearestAirbase -end - - - ---- --- @param #SET_AIRBASE self --- @param Wrapper.Airbase#AIRBASE MAirbase --- @return #SET_AIRBASE self -function SET_AIRBASE:IsIncludeObject( MAirbase ) - self:F2( MAirbase ) - - local MAirbaseInclude = true - - if MAirbase then - local MAirbaseName = MAirbase:GetName() - - if self.Filter.Coalitions then - local MAirbaseCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - local AirbaseCoalitionID = _DATABASE:GetCoalitionFromAirbase( MAirbaseName ) - self:T3( { "Coalition:", AirbaseCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == AirbaseCoalitionID then - MAirbaseCoalition = true - end - end - self:T( { "Evaluated Coalition", MAirbaseCoalition } ) - MAirbaseInclude = MAirbaseInclude and MAirbaseCoalition - end - - if self.Filter.Categories then - local MAirbaseCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - local AirbaseCategoryID = _DATABASE:GetCategoryFromAirbase( MAirbaseName ) - self:T3( { "Category:", AirbaseCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == AirbaseCategoryID then - MAirbaseCategory = true - end - end - self:T( { "Evaluated Category", MAirbaseCategory } ) - MAirbaseInclude = MAirbaseInclude and MAirbaseCategory - end - end - - self:T2( MAirbaseInclude ) - return MAirbaseInclude -end ---- **Core** - **POINT\_VEC** classes define an **extensive API** to **manage 3D points** in the simulation space. --- --- 1) @{Point#POINT_VEC3} class, extends @{Base#BASE} --- ================================================== --- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. --- --- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. --- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. --- --- ## 1.1) POINT_VEC3 constructor --- --- A new POINT_VEC3 instance can be created with: --- --- * @{Point#POINT_VEC3.New}(): a 3D point. --- * @{Point#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{DCSTypes#Vec3}. --- --- ## 1.2) Manupulate the X, Y, Z coordinates of the point --- --- A POINT_VEC3 class works in 3D space. It contains internally an X, Y, Z coordinate. --- Methods exist to manupulate these coordinates. --- --- The current X, Y, Z axis can be retrieved with the methods @{#POINT_VEC3.GetX}(), @{#POINT_VEC3.GetY}(), @{#POINT_VEC3.GetZ}() respectively. --- The methods @{#POINT_VEC3.SetX}(), @{#POINT_VEC3.SetY}(), @{#POINT_VEC3.SetZ}() change the respective axis with a new value. --- The current axis values can be changed by using the methods @{#POINT_VEC3.AddX}(), @{#POINT_VEC3.AddY}(), @{#POINT_VEC3.AddZ}() --- to add or substract a value from the current respective axis value. --- Note that the Set and Add methods return the current POINT_VEC3 object, so these manipulation methods can be chained... For example: --- --- local Vec3 = PointVec3:AddX( 100 ):AddZ( 150 ):GetVec3() --- --- ## 1.3) Create waypoints for routes --- --- A POINT_VEC3 can prepare waypoints for Ground, Air and Naval groups to be embedded into a Route. --- --- --- ## 1.5) Smoke, flare, explode, illuminate --- --- At the point a smoke, flare, explosion and illumination bomb can be triggered. Use the following methods: --- --- ### 1.5.1) Smoke --- --- * @{#POINT_VEC3.Smoke}(): To smoke the point in a certain color. --- * @{#POINT_VEC3.SmokeBlue}(): To smoke the point in blue. --- * @{#POINT_VEC3.SmokeRed}(): To smoke the point in red. --- * @{#POINT_VEC3.SmokeOrange}(): To smoke the point in orange. --- * @{#POINT_VEC3.SmokeWhite}(): To smoke the point in white. --- * @{#POINT_VEC3.SmokeGreen}(): To smoke the point in green. --- --- ### 1.5.2) Flare --- --- * @{#POINT_VEC3.Flare}(): To flare the point in a certain color. --- * @{#POINT_VEC3.FlareRed}(): To flare the point in red. --- * @{#POINT_VEC3.FlareYellow}(): To flare the point in yellow. --- * @{#POINT_VEC3.FlareWhite}(): To flare the point in white. --- * @{#POINT_VEC3.FlareGreen}(): To flare the point in green. --- --- ### 1.5.3) Explode --- --- * @{#POINT_VEC3.Explosion}(): To explode the point with a certain intensity. --- --- ### 1.5.4) Illuminate --- --- * @{#POINT_VEC3.IlluminationBomb}(): To illuminate the point. --- --- --- 2) @{Point#POINT_VEC2} class, extends @{Point#POINT_VEC3} --- ========================================================= --- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. --- --- 2.1) POINT_VEC2 constructor --- --------------------------- --- A new POINT_VEC2 instance can be created with: --- --- * @{Point#POINT_VEC2.New}(): a 2D point, taking an additional height parameter. --- * @{Point#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{DCSTypes#Vec2}. --- --- ## 1.2) Manupulate the X, Altitude, Y coordinates of the 2D point --- --- A POINT_VEC2 class works in 2D space, with an altitude setting. It contains internally an X, Altitude, Y coordinate. --- Methods exist to manupulate these coordinates. --- --- The current X, Altitude, Y axis can be retrieved with the methods @{#POINT_VEC2.GetX}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetY}() respectively. --- The methods @{#POINT_VEC2.SetX}(), @{#POINT_VEC2.SetAlt}(), @{#POINT_VEC2.SetY}() change the respective axis with a new value. --- The current Lat(itude), Alt(itude), Lon(gitude) values can also be retrieved with the methods @{#POINT_VEC2.GetLat}(), @{#POINT_VEC2.GetAlt}(), @{#POINT_VEC2.GetLon}() respectively. --- The current axis values can be changed by using the methods @{#POINT_VEC2.AddX}(), @{#POINT_VEC2.AddAlt}(), @{#POINT_VEC2.AddY}() --- to add or substract a value from the current respective axis value. --- Note that the Set and Add methods return the current POINT_VEC2 object, so these manipulation methods can be chained... For example: --- --- local Vec2 = PointVec2:AddX( 100 ):AddY( 2000 ):GetVec2() --- --- === --- --- **API CHANGE HISTORY** --- ====================== --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-03: POINT\_VEC3:**Explosion( ExplosionIntensity )** added. --- 2017-03-03: POINT\_VEC3:**IlluminationBomb()** added. --- --- 2017-02-18: POINT\_VEC3:**NewFromVec2( Vec2, LandHeightAdd )** added. --- --- 2016-08-12: POINT\_VEC3:**Translate( Distance, Angle )** added. --- --- 2016-08-06: Made PointVec3 and Vec3, PointVec2 and Vec2 terminology used in the code consistent. --- --- * Replaced method _Point_Vec3() to **Vec3**() where the code manages a Vec3. Replaced all references to the method. --- * Replaced method _Point_Vec2() to **Vec2**() where the code manages a Vec2. Replaced all references to the method. --- * Replaced method Random_Point_Vec3() to **RandomVec3**() where the code manages a Vec3. Replaced all references to the method. --- . --- === --- --- ### Authors: --- --- * FlightControl : Design & Programming --- --- ### Contributions: --- --- @module Point - ---- The POINT_VEC3 class --- @type POINT_VEC3 --- @field #number x The x coordinate in 3D space. --- @field #number y The y coordinate in 3D space. --- @field #number z The z coordiante in 3D space. --- @field Utilities.Utils#SMOKECOLOR SmokeColor --- @field Utilities.Utils#FLARECOLOR FlareColor --- @field #POINT_VEC3.RoutePointAltType RoutePointAltType --- @field #POINT_VEC3.RoutePointType RoutePointType --- @field #POINT_VEC3.RoutePointAction RoutePointAction --- @extends Core.Base#BASE -POINT_VEC3 = { - ClassName = "POINT_VEC3", - Metric = true, - RoutePointAltType = { - BARO = "BARO", - }, - RoutePointType = { - TakeOffParking = "TakeOffParking", - TurningPoint = "Turning Point", - }, - RoutePointAction = { - FromParkingArea = "From Parking Area", - TurningPoint = "Turning Point", - }, -} - ---- The POINT_VEC2 class --- @type POINT_VEC2 --- @field Dcs.DCSTypes#Distance x The x coordinate in meters. --- @field Dcs.DCSTypes#Distance y the y coordinate in meters. --- @extends Core.Point#POINT_VEC3 -POINT_VEC2 = { - ClassName = "POINT_VEC2", -} - - -do -- POINT_VEC3 - ---- RoutePoint AltTypes --- @type POINT_VEC3.RoutePointAltType --- @field BARO "BARO" - ---- RoutePoint Types --- @type POINT_VEC3.RoutePointType --- @field TakeOffParking "TakeOffParking" --- @field TurningPoint "Turning Point" - ---- RoutePoint Actions --- @type POINT_VEC3.RoutePointAction --- @field FromParkingArea "From Parking Area" --- @field TurningPoint "Turning Point" - --- Constructor. - ---- Create a new POINT_VEC3 object. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. --- @param Dcs.DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. --- @return Core.Point#POINT_VEC3 self -function POINT_VEC3:New( x, y, z ) - - local self = BASE:Inherit( self, BASE:New() ) - self.x = x - self.y = y - self.z = z - - return self -end - ---- Create a new POINT_VEC3 object from Vec2 coordinates. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. --- @return Core.Point#POINT_VEC3 self -function POINT_VEC3:NewFromVec2( Vec2, LandHeightAdd ) - - local LandHeight = land.getHeight( Vec2 ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - self = self:New( Vec2.x, LandHeight, Vec2.y ) - - self:F2( self ) - - return self -end - ---- Create a new POINT_VEC3 object from Vec3 coordinates. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Core.Point#POINT_VEC3 self -function POINT_VEC3:NewFromVec3( Vec3 ) - - self = self:New( Vec3.x, Vec3.y, Vec3.z ) - self:F2( self ) - return self -end - - ---- Return the coordinates of the POINT_VEC3 in Vec3 format. --- @param #POINT_VEC3 self --- @return Dcs.DCSTypes#Vec3 The Vec3 coodinate. -function POINT_VEC3:GetVec3() - return { x = self.x, y = self.y, z = self.z } -end - ---- Return the coordinates of the POINT_VEC3 in Vec2 format. --- @param #POINT_VEC3 self --- @return Dcs.DCSTypes#Vec2 The Vec2 coodinate. -function POINT_VEC3:GetVec2() - return { x = self.x, y = self.z } -end - - ---- Return the x coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The x coodinate. -function POINT_VEC3:GetX() - return self.x -end - ---- Return the y coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The y coodinate. -function POINT_VEC3:GetY() - return self.y -end - ---- Return the z coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The z coodinate. -function POINT_VEC3:GetZ() - return self.z -end - ---- Set the x coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number x The x coordinate. --- @return #POINT_VEC3 -function POINT_VEC3:SetX( x ) - self.x = x - return self -end - ---- Set the y coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number y The y coordinate. --- @return #POINT_VEC3 -function POINT_VEC3:SetY( y ) - self.y = y - return self -end - ---- Set the z coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number z The z coordinate. --- @return #POINT_VEC3 -function POINT_VEC3:SetZ( z ) - self.z = z - return self -end - ---- Add to the x coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number x The x coordinate value to add to the current x coodinate. --- @return #POINT_VEC3 -function POINT_VEC3:AddX( x ) - self.x = self.x + x - return self -end - ---- Add to the y coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number y The y coordinate value to add to the current y coodinate. --- @return #POINT_VEC3 -function POINT_VEC3:AddY( y ) - self.y = self.y + y - return self -end - ---- Add to the z coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #number z The z coordinate value to add to the current z coodinate. --- @return #POINT_VEC3 -function POINT_VEC3:AddZ( z ) - self.z = self.z +z - return self -end - ---- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance OuterRadius --- @param Dcs.DCSTypes#Distance InnerRadius --- @return Dcs.DCSTypes#Vec2 Vec2 -function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { OuterRadius, InnerRadius } ) - - local Theta = 2 * math.pi * math.random() - local Radials = math.random() + math.random() - if Radials > 1 then - Radials = 2 - Radials - end - - local RadialMultiplier - if InnerRadius and InnerRadius <= OuterRadius then - RadialMultiplier = ( OuterRadius - InnerRadius ) * Radials + InnerRadius - else - RadialMultiplier = OuterRadius * Radials - end - - local RandomVec2 - if OuterRadius > 0 then - RandomVec2 = { x = math.cos( Theta ) * RadialMultiplier + self:GetX(), y = math.sin( Theta ) * RadialMultiplier + self:GetZ() } - else - RandomVec2 = { x = self:GetX(), y = self:GetZ() } - end - - return RandomVec2 -end - ---- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance OuterRadius --- @param Dcs.DCSTypes#Distance InnerRadius --- @return #POINT_VEC2 -function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { OuterRadius, InnerRadius } ) - - return POINT_VEC2:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) -end - ---- Return a random Vec3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance OuterRadius --- @param Dcs.DCSTypes#Distance InnerRadius --- @return Dcs.DCSTypes#Vec3 Vec3 -function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) - - local RandomVec2 = self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - local y = self:GetY() + math.random( InnerRadius, OuterRadius ) - local RandomVec3 = { x = RandomVec2.x, y = y, z = RandomVec2.y } - - return RandomVec3 -end - ---- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance OuterRadius --- @param Dcs.DCSTypes#Distance InnerRadius --- @return #POINT_VEC3 -function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) - - return POINT_VEC3:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) -end - - ---- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. -function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) - return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } -end - ---- Get a correction in radians of the real magnetic north of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number CorrectionRadians The correction in radians. -function POINT_VEC3:GetNorthCorrectionRadians() - local TargetVec3 = self:GetVec3() - local lat, lon = coord.LOtoLL(TargetVec3) - local north_posit = coord.LLtoLO(lat + 1, lon) - return math.atan2( north_posit.z - TargetVec3.z, north_posit.x - TargetVec3.x ) -end - - ---- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. --- @return #number DirectionRadians The direction in radians. -function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) - local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) - --DirectionRadians = DirectionRadians + self:GetNorthCorrectionRadians() - if DirectionRadians < 0 then - DirectionRadians = DirectionRadians + 2 * math.pi -- put dir in range of 0 to 2*pi ( the full circle ) - end - return DirectionRadians -end - ---- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return Dcs.DCSTypes#Distance Distance The distance in meters. -function POINT_VEC3:Get2DDistance( TargetPointVec3 ) - local TargetVec3 = TargetPointVec3:GetVec3() - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 -end - ---- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return Dcs.DCSTypes#Distance Distance The distance in meters. -function POINT_VEC3:Get3DDistance( TargetPointVec3 ) - local TargetVec3 = TargetPointVec3:GetVec3() - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 -end - ---- Provides a Bearing / Range string --- @param #POINT_VEC3 self --- @param #number AngleRadians The angle in randians --- @param #number Distance The distance --- @return #string The BR Text -function POINT_VEC3:ToStringBR( AngleRadians, Distance ) - - AngleRadians = UTILS.Round( UTILS.ToDegree( AngleRadians ), 0 ) - if self:IsMetric() then - Distance = UTILS.Round( Distance / 1000, 2 ) - else - Distance = UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) - end - - local s = string.format( '%03d', AngleRadians ) .. ' for ' .. Distance - - s = s .. self:GetAltitudeText() -- When the POINT is a VEC2, there will be no altitude shown. - - return s -end - ---- Provides a Bearing / Range string --- @param #POINT_VEC3 self --- @param #number AngleRadians The angle in randians --- @param #number Distance The distance --- @return #string The BR Text -function POINT_VEC3:ToStringLL( acc, DMS ) - - acc = acc or 3 - local lat, lon = coord.LOtoLL( self:GetVec3() ) - return UTILS.tostringLL(lat, lon, acc, DMS) -end - ---- Return the altitude text of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #string Altitude text. -function POINT_VEC3:GetAltitudeText() - if self:IsMetric() then - return ' at ' .. UTILS.Round( self:GetY(), 0 ) - else - return ' at ' .. UTILS.Round( UTILS.MetersToFeet( self:GetY() ), 0 ) - end -end - ---- Return a BR string from a POINT_VEC3 to the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return #string The BR text. -function POINT_VEC3:GetBRText( TargetPointVec3 ) - local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 ) - local AngleRadians = self:GetDirectionRadians( DirectionVec3 ) - local Distance = self:Get2DDistance( TargetPointVec3 ) - return self:ToStringBR( AngleRadians, Distance ) -end - ---- Sets the POINT_VEC3 metric or NM. --- @param #POINT_VEC3 self --- @param #boolean Metric true means metric, false means NM. -function POINT_VEC3:SetMetric( Metric ) - self.Metric = Metric -end - ---- Gets if the POINT_VEC3 is metric or NM. --- @param #POINT_VEC3 self --- @return #boolean Metric true means metric, false means NM. -function POINT_VEC3:IsMetric() - return self.Metric -end - ---- Add a Distance in meters from the POINT_VEC3 horizontal plane, with the given angle, and calculate the new POINT_VEC3. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. --- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. --- @return #POINT_VEC3 The new calculated POINT_VEC3. -function POINT_VEC3:Translate( Distance, Angle ) - local SX = self:GetX() - local SZ = self:GetZ() - local Radians = Angle / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TZ = Distance * math.sin( Radians ) + SZ - - return POINT_VEC3:New( TX, self:GetY(), TZ ) -end - - - ---- Build an air type route point. --- @param #POINT_VEC3 self --- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. --- @param #POINT_VEC3.RoutePointType Type The route point type. --- @param #POINT_VEC3.RoutePointAction Action The route point action. --- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. --- @param #boolean SpeedLocked true means the speed is locked. --- @return #table The route point. -function POINT_VEC3:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked ) - self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) - - local RoutePoint = {} - RoutePoint.x = self.x - RoutePoint.y = self.z - RoutePoint.alt = self.y - RoutePoint.alt_type = AltType - - RoutePoint.type = Type - RoutePoint.action = Action - - RoutePoint.speed = Speed / 3.6 - RoutePoint.speed_locked = true - --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] - - - RoutePoint.task = {} - RoutePoint.task.id = "ComboTask" - RoutePoint.task.params = {} - RoutePoint.task.params.tasks = {} - - - return RoutePoint -end - ---- Build an ground type route point. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Speed Speed Speed in km/h. --- @param #POINT_VEC3.RoutePointAction Formation The route point Formation. --- @return #table The route point. -function POINT_VEC3:RoutePointGround( Speed, Formation ) - self:F2( { Formation, Speed } ) - - local RoutePoint = {} - RoutePoint.x = self.x - RoutePoint.y = self.z - - RoutePoint.action = Formation or "" - - - RoutePoint.speed = Speed / 3.6 - RoutePoint.speed_locked = true - --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] - - - RoutePoint.task = {} - RoutePoint.task.id = "ComboTask" - RoutePoint.task.params = {} - RoutePoint.task.params.tasks = {} - - - return RoutePoint -end - ---- Creates an explosion at the point of a certain intensity. --- @param #POINT_VEC3 self --- @param #number ExplosionIntensity -function POINT_VEC3:Explosion( ExplosionIntensity ) - self:F2( { ExplosionIntensity } ) - trigger.action.explosion( self:GetVec3(), ExplosionIntensity ) -end - ---- Creates an illumination bomb at the point. --- @param #POINT_VEC3 self -function POINT_VEC3:IlluminationBomb() - self:F2() - trigger.action.illuminationBomb( self:GetVec3() ) -end - - ---- Smokes the point in a color. --- @param #POINT_VEC3 self --- @param Utilities.Utils#SMOKECOLOR SmokeColor -function POINT_VEC3:Smoke( SmokeColor ) - self:F2( { SmokeColor } ) - trigger.action.smoke( self:GetVec3(), SmokeColor ) -end - ---- Smoke the POINT_VEC3 Green. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeGreen() - self:F2() - self:Smoke( SMOKECOLOR.Green ) -end - ---- Smoke the POINT_VEC3 Red. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeRed() - self:F2() - self:Smoke( SMOKECOLOR.Red ) -end - ---- Smoke the POINT_VEC3 White. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeWhite() - self:F2() - self:Smoke( SMOKECOLOR.White ) -end - ---- Smoke the POINT_VEC3 Orange. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeOrange() - self:F2() - self:Smoke( SMOKECOLOR.Orange ) -end - ---- Smoke the POINT_VEC3 Blue. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeBlue() - self:F2() - self:Smoke( SMOKECOLOR.Blue ) -end - ---- Flares the point in a color. --- @param #POINT_VEC3 self --- @param Utilities.Utils#FLARECOLOR FlareColor --- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:Flare( FlareColor, Azimuth ) - self:F2( { FlareColor } ) - trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) -end - ---- Flare the POINT_VEC3 White. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareWhite( Azimuth ) - self:F2( Azimuth ) - self:Flare( FLARECOLOR.White, Azimuth ) -end - ---- Flare the POINT_VEC3 Yellow. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareYellow( Azimuth ) - self:F2( Azimuth ) - self:Flare( FLARECOLOR.Yellow, Azimuth ) -end - ---- Flare the POINT_VEC3 Green. --- @param #POINT_VEC3 self --- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareGreen( Azimuth ) - self:F2( Azimuth ) - self:Flare( FLARECOLOR.Green, Azimuth ) -end - ---- Flare the POINT_VEC3 Red. --- @param #POINT_VEC3 self -function POINT_VEC3:FlareRed( Azimuth ) - self:F2( Azimuth ) - self:Flare( FLARECOLOR.Red, Azimuth ) -end - -end - -do -- POINT_VEC2 - - - ---- POINT_VEC2 constructor. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. --- @param Dcs.DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. --- @return Core.Point#POINT_VEC2 -function POINT_VEC2:New( x, y, LandHeightAdd ) - - local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) - self:F2( self ) - - return self -end - ---- Create a new POINT_VEC2 object from Vec2 coordinates. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. --- @return Core.Point#POINT_VEC2 self -function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) - - local LandHeight = land.getHeight( Vec2 ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( self ) - - return self -end - ---- Create a new POINT_VEC2 object from Vec3 coordinates. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Core.Point#POINT_VEC2 self -function POINT_VEC2:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, BASE:New() ) - local Vec2 = { x = Vec3.x, y = Vec3.z } - - local LandHeight = land.getHeight( Vec2 ) - - self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( self ) - - return self -end - ---- Return the x coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #number The x coodinate. -function POINT_VEC2:GetX() - return self.x -end - ---- Return the y coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #number The y coodinate. -function POINT_VEC2:GetY() - return self.z -end - ---- Return the altitude (height) of the land at the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #number The land altitude. -function POINT_VEC2:GetAlt() - return land.getHeight( { x = self.x, y = self.z } ) -end - ---- Return Return the Lat(itude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.x). --- @param #POINT_VEC2 self --- @return #number The x coodinate. -function POINT_VEC2:GetLat() - return self.x -end - ---- Return the Lon(gitude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.z). --- @param #POINT_VEC2 self --- @return #number The y coodinate. -function POINT_VEC2:GetLon() - return self.z -end - ---- Set the x coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number x The x coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:SetX( x ) - self.x = x - return self -end - ---- Set the y coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number y The y coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:SetY( y ) - self.z = y - return self -end - ---- Set the Lat(itude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.x). --- @param #POINT_VEC2 self --- @param #number x The x coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:SetLat( x ) - self.x = x - return self -end - ---- Set the altitude of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number Altitude The land altitude. If nothing (nil) is given, then the current land altitude is set. --- @return #POINT_VEC2 -function POINT_VEC2:SetAlt( Altitude ) - self.y = Altitude or land.getHeight( { x = self.x, y = self.z } ) - return self -end - ---- Set the Lon(gitude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.z). --- @param #POINT_VEC2 self --- @param #number y The y coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:SetLon( z ) - self.z = z - return self -end - ---- Add to the x coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number x The x coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:AddX( x ) - self.x = self.x + x - return self -end - ---- Add to the y coordinate of the POINT_VEC2. --- @param #POINT_VEC2 self --- @param #number y The y coordinate. --- @return #POINT_VEC2 -function POINT_VEC2:AddY( y ) - self.z = self.z + y - return self -end - ---- Add to the current land height an altitude. --- @param #POINT_VEC2 self --- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. --- @return #POINT_VEC2 -function POINT_VEC2:AddAlt( Altitude ) - self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 - return self -end - - - ---- Calculate the distance from a reference @{#POINT_VEC2}. --- @param #POINT_VEC2 self --- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. --- @return Dcs.DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. -function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) - self:F2( PointVec2Reference ) - - local Distance = ( ( PointVec2Reference:GetX() - self:GetX() ) ^ 2 + ( PointVec2Reference:GetY() - self:GetY() ) ^2 ) ^0.5 - - self:T2( Distance ) - return Distance -end - ---- Calculate the distance from a reference @{DCSTypes#Vec2}. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}. --- @return Dcs.DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters. -function POINT_VEC2:DistanceFromVec2( Vec2Reference ) - self:F2( Vec2Reference ) - - local Distance = ( ( Vec2Reference.x - self:GetX() ) ^ 2 + ( Vec2Reference.y - self:GetY() ) ^2 ) ^0.5 - - self:T2( Distance ) - return Distance -end - - ---- Return no text for the altitude of the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #string Empty string. -function POINT_VEC2:GetAltitudeText() - return '' -end - ---- Add a Distance in meters from the POINT_VEC2 orthonormal plane, with the given angle, and calculate the new POINT_VEC2. --- @param #POINT_VEC2 self --- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. --- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. --- @return #POINT_VEC2 The new calculated POINT_VEC2. -function POINT_VEC2:Translate( Distance, Angle ) - local SX = self:GetX() - local SY = self:GetY() - local Radians = Angle / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TY = Distance * math.sin( Radians ) + SY - - return POINT_VEC2:New( TX, TY ) -end - -end - - ---- **Core** - MESSAGE class takes are of the **real-time notifications** and **messages to players** during a simulation. --- --- ![Banner Image](..\Presentations\MESSAGE\Dia1.JPG) --- --- === --- --- # 1) @{Message#MESSAGE} class, extends @{Base#BASE} --- --- Message System to display Messages to Clients, Coalitions or All. --- Messages are shown on the display panel for an amount of seconds, and will then disappear. --- Messages can contain a category which is indicating the category of the message. --- --- ## 1.1) MESSAGE construction --- --- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. --- To send messages, you need to use the To functions. --- --- ## 1.2) Send messages to an audience --- --- Messages are sent: --- --- * To a @{Client} using @{Message#MESSAGE.ToClient}(). --- * To a @{Group} using @{Message#MESSAGE.ToGroup}() --- * To a coalition using @{Message#MESSAGE.ToCoalition}(). --- * To the red coalition using @{Message#MESSAGE.ToRed}(). --- * To the blue coalition using @{Message#MESSAGE.ToBlue}(). --- * To all Players using @{Message#MESSAGE.ToAll}(). --- --- ## 1.3) Send conditionally to an audience --- --- Messages can be sent conditionally to an audience (when a condition is true): --- --- * To all players using @{Message#MESSAGE.ToAllIf}(). --- * To a coalition using @{Message#MESSAGE.ToCoalitionIf}(). --- --- --- @module Message - ---- The MESSAGE class --- @type MESSAGE --- @extends Core.Base#BASE -MESSAGE = { - ClassName = "MESSAGE", - MessageCategory = 0, - MessageID = 0, -} - - ---- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. --- @param self --- @param #string MessageText is the text of the Message. --- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. --- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". --- @return #MESSAGE --- @usage --- -- Create a series of new Messages. --- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". --- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) - - -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - if MessageCategory:sub(-1) ~= "\n" then - self.MessageCategory = MessageCategory .. ": " - else - self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" - end - else - self.MessageCategory = "" - end - - self.MessageDuration = MessageDuration or 5 - self.MessageTime = timer.getTime() - self.MessageText = MessageText - - self.MessageSent = false - self.MessageGroup = false - self.MessageCoalition = false - - return self -end - ---- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". --- @param #MESSAGE self --- @param Wrapper.Client#CLIENT Client is the Group of the Client. --- @return #MESSAGE --- @usage --- -- Send the 2 messages created with the @{New} method to the Client Group. --- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. --- ClientGroup = Group.getByName( "ClientGroup" ) --- --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) --- MessageClient1:ToClient( ClientGroup ) --- MessageClient2:ToClient( ClientGroup ) -function MESSAGE:ToClient( Client ) - self:F( Client ) - - if Client and Client:GetClientGroupID() then - - local ClientGroupID = Client:GetClientGroupID() - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to a Group. --- @param #MESSAGE self --- @param Wrapper.Group#GROUP Group is the Group. --- @return #MESSAGE -function MESSAGE:ToGroup( Group ) - self:F( Group.GroupName ) - - if Group then - - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end ---- Sends a MESSAGE to the Blue coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the BLUE coalition. --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageBLUE:ToBlue() -function MESSAGE:ToBlue() - self:F() - - self:ToCoalition( coalition.side.BLUE ) - - return self -end - ---- Sends a MESSAGE to the Red Coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToRed() -function MESSAGE:ToRed( ) - self:F() - - self:ToCoalition( coalition.side.RED ) - - return self -end - ---- Sends a MESSAGE to a Coalition. --- @param #MESSAGE self --- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToCoalition( coalition.side.RED ) -function MESSAGE:ToCoalition( CoalitionSide ) - self:F( CoalitionSide ) - - if CoalitionSide then - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to a Coalition if the given Condition is true. --- @param #MESSAGE self --- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @return #MESSAGE -function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) - self:F( CoalitionSide ) - - if Condition and Condition == true then - self:ToCoalition( CoalitionSide ) - end - - return self -end - ---- Sends a MESSAGE to all players. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created to all players. --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) --- MessageAll:ToAll() -function MESSAGE:ToAll() - self:F() - - self:ToCoalition( coalition.side.RED ) - self:ToCoalition( coalition.side.BLUE ) - - return self -end - - ---- Sends a MESSAGE to all players if the given Condition is true. --- @param #MESSAGE self --- @return #MESSAGE -function MESSAGE:ToAllIf( Condition ) - - if Condition and Condition == true then - self:ToCoalition( coalition.side.RED ) - self:ToCoalition( coalition.side.BLUE ) - end - - return self -end ---- **Core** - The **FSM** (**F**inite **S**tate **M**achine) class and derived **FSM\_** classes --- are design patterns allowing efficient (long-lasting) processes and workflows. --- --- ![Banner Image](..\Presentations\FSM\Dia1.JPG) --- --- === --- --- A FSM can only be in one of a finite number of states. --- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. --- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. --- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. --- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. --- --- The FSM class supports a **hierarchical implementation of a Finite State Machine**, --- that is, it allows to **embed existing FSM implementations in a master FSM**. --- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes. --- --- ![Workflow Example](..\Presentations\FSM\Dia2.JPG) --- --- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone, --- orders him to destroy x targets and account the results. --- Other examples of ready made FSM could be: --- --- * route a plane to a zone flown by a human --- * detect targets by an AI and report to humans --- * account for destroyed targets by human players --- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle --- * let an AI patrol a zone --- --- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, --- because **the goal of MOOSE is to simplify mission design complexity for mission building**. --- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. --- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, --- and tailored** by mission designers through **the implementation of Transition Handlers**. --- Each of these FSM implementation classes start either with: --- --- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. --- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. --- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. --- --- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections. --- --- ##__Dislaimer:__ --- The FSM class development is based on a finite state machine implementation made by Conroy Kyle. --- The state machine can be found on [github](https://github.com/kyleconroy/lua-state-machine) --- I've reworked this development (taken the concept), and created a **hierarchical state machine** out of it, embedded within the DCS simulator. --- Additionally, I've added extendability and created an API that allows seamless FSM implementation. --- --- === --- --- # 1) @{#FSM} class, extends @{Base#BASE} --- --- ![Transition Rules and Transition Handlers and Event Triggers](..\Presentations\FSM\Dia3.JPG) --- --- The FSM class is the base class of all FSM\_ derived classes. It implements the main functionality to define and execute Finite State Machines. --- The derived FSM\_ classes extend the Finite State Machine functionality to run a workflow process for a specific purpose or component. --- --- Finite State Machines have **Transition Rules**, **Transition Handlers** and **Event Triggers**. --- --- The **Transition Rules** define the "Process Flow Boundaries", that is, --- the path that can be followed hopping from state to state upon triggered events. --- If an event is triggered, and there is no valid path found for that event, --- an error will be raised and the FSM will stop functioning. --- --- The **Transition Handlers** are special methods that can be defined by the mission designer, following a defined syntax. --- If the FSM object finds a method of such a handler, then the method will be called by the FSM, passing specific parameters. --- The method can then define its own custom logic to implement the FSM workflow, and to conduct other actions. --- --- The **Event Triggers** are methods that are defined by the FSM, which the mission designer can use to implement the workflow. --- Most of the time, these Event Triggers are used within the Transition Handler methods, so that a workflow is created running through the state machine. --- --- As explained above, a FSM supports **Linear State Transitions** and **Hierarchical State Transitions**, and both can be mixed to make a comprehensive FSM implementation. --- The below documentation has a seperate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**. --- --- ## 1.1) FSM Linear Transitions --- --- Linear Transitions are Transition Rules allowing an FSM to transition from one or multiple possible **From** state(s) towards a **To** state upon a Triggered **Event**. --- The Lineair transition rule evaluation will always be done from the **current state** of the FSM. --- If no valid Transition Rule can be found in the FSM, the FSM will log an error and stop. --- --- ### 1.1.1) FSM Transition Rules --- --- The FSM has transition rules that it follows and validates, as it walks the process. --- These rules define when an FSM can transition from a specific state towards an other specific state upon a triggered event. --- --- The method @{#FSM.AddTransition}() specifies a new possible Transition Rule for the FSM. --- --- The initial state can be defined using the method @{#FSM.SetStartState}(). The default start state of an FSM is "None". --- --- Find below an example of a Linear Transition Rule definition for an FSM. --- --- local Fsm3Switch = FSM:New() -- #FsmDemo --- FsmSwitch:SetStartState( "Off" ) --- FsmSwitch:AddTransition( "Off", "SwitchOn", "On" ) --- FsmSwitch:AddTransition( "Off", "SwitchMiddle", "Middle" ) --- FsmSwitch:AddTransition( "On", "SwitchOff", "Off" ) --- FsmSwitch:AddTransition( "Middle", "SwitchOff", "Off" ) --- --- The above code snippet models a 3-way switch Linear Transition: --- --- * It can be switched **On** by triggering event **SwitchOn**. --- * It can be switched to the **Middle** position, by triggering event **SwitchMiddle**. --- * It can be switched **Off** by triggering event **SwitchOff**. --- * Note that once the Switch is **On** or **Middle**, it can only be switched **Off**. --- --- ### Some additional comments: --- --- Note that Linear Transition Rules **can be declared in a few variations**: --- --- * The From states can be **a table of strings**, indicating that the transition rule will be valid **if the current state** of the FSM will be **one of the given From states**. --- * The From state can be a **"*"**, indicating that **the transition rule will always be valid**, regardless of the current state of the FSM. --- --- The below code snippet shows how the two last lines can be rewritten and consensed. --- --- FsmSwitch:AddTransition( { "On", "Middle" }, "SwitchOff", "Off" ) --- --- ### 1.1.2) Transition Handling --- --- ![Transition Handlers](..\Presentations\FSM\Dia4.JPG) --- --- An FSM transitions in **4 moments** when an Event is being triggered and processed. --- The mission designer can define for each moment specific logic within methods implementations following a defined API syntax. --- These methods define the flow of the FSM process; because in those methods the FSM Internal Events will be triggered. --- --- * To handle **State** transition moments, create methods starting with OnLeave or OnEnter concatenated with the State name. --- * To handle **Event** transition moments, create methods starting with OnBefore or OnAfter concatenated with the Event name. --- --- **The OnLeave and OnBefore transition methods may return false, which will cancel the transition!** --- --- Transition Handler methods need to follow the above specified naming convention, but are also passed parameters from the FSM. --- These parameters are on the correct order: From, Event, To: --- --- * From = A string containing the From state. --- * Event = A string containing the Event name that was triggered. --- * To = A string containing the To state. --- --- On top, each of these methods can have a variable amount of parameters passed. See the example in section [1.1.3](#1.1.3\)-event-triggers). --- --- ### 1.1.3) Event Triggers --- --- ![Event Triggers](..\Presentations\FSM\Dia5.JPG) --- --- The FSM creates for each Event two **Event Trigger methods**. --- There are two modes how Events can be triggered, which is **synchronous** and **asynchronous**: --- --- * The method **FSM:Event()** triggers an Event that will be processed **synchronously** or **immediately**. --- * The method **FSM:__Event( __seconds__ )** triggers an Event that will be processed **asynchronously** over time, waiting __x seconds__. --- --- The destinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time. --- Processing will just continue. Synchronous Event Trigger methods are useful to change states of the FSM immediately, but may have a larger processing impact. --- --- The following example provides a little demonstration on the difference between synchronous and asynchronous Event Triggering. --- --- function FSM:OnAfterEvent( From, Event, To, Amount ) --- self:T( { Amount = Amount } ) --- end --- --- local Amount = 1 --- FSM:__Event( 5, Amount ) --- --- Amount = Amount + 1 --- FSM:Event( Text, Amount ) --- --- In this example, the **:OnAfterEvent**() Transition Handler implementation will get called when **Event** is being triggered. --- Before we go into more detail, let's look at the last 4 lines of the example. --- The last line triggers synchronously the **Event**, and passes Amount as a parameter. --- The 3rd last line of the example triggers asynchronously **Event**. --- Event will be processed after 5 seconds, and Amount is given as a parameter. --- --- The output of this little code fragment will be: --- --- * Amount = 2 --- * Amount = 2 --- --- Because ... When Event was asynchronously processed after 5 seconds, Amount was set to 2. So be careful when processing and passing values and objects in asynchronous processing! --- --- ### 1.1.4) Linear Transition Example --- --- This example is fully implemented in the MOOSE test mission on GITHUB: [FSM-100 - Transition Explanation](https://github.com/FlightControl-Master/MOOSE/blob/master/Moose%20Test%20Missions/FSM%20-%20Finite%20State%20Machine/FSM-100%20-%20Transition%20Explanation/FSM-100%20-%20Transition%20Explanation.lua) --- --- It models a unit standing still near Batumi, and flaring every 5 seconds while switching between a Green flare and a Red flare. --- The purpose of this example is not to show how exciting flaring is, but it demonstrates how a Linear Transition FSM can be build. --- Have a look at the source code. The source code is also further explained below in this section. --- --- The example creates a new FsmDemo object from class FSM. --- It will set the start state of FsmDemo to state **Green**. --- Two Linear Transition Rules are created, where upon the event **Switch**, --- the FsmDemo will transition from state **Green** to **Red** and from **Red** back to **Green**. --- --- ![Transition Example](..\Presentations\FSM\Dia6.JPG) --- --- local FsmDemo = FSM:New() -- #FsmDemo --- FsmDemo:SetStartState( "Green" ) --- FsmDemo:AddTransition( "Green", "Switch", "Red" ) --- FsmDemo:AddTransition( "Red", "Switch", "Green" ) --- --- In the above example, the FsmDemo could flare every 5 seconds a Green or a Red flare into the air. --- The next code implements this through the event handling method **OnAfterSwitch**. --- --- ![Transition Flow](..\Presentations\FSM\Dia7.JPG) --- --- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) --- self:T( { From, Event, To, FsmUnit } ) --- --- if From == "Green" then --- FsmUnit:Flare(FLARECOLOR.Green) --- else --- if From == "Red" then --- FsmUnit:Flare(FLARECOLOR.Red) --- end --- end --- self:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. --- end --- --- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the first Switch event to happen in 5 seconds. --- --- The OnAfterSwitch implements a loop. The last line of the code fragment triggers the Switch Event within 5 seconds. --- Upon the event execution (after 5 seconds), the OnAfterSwitch method is called of FsmDemo (cfr. the double point notation!!! ":"). --- The OnAfterSwitch method receives from the FSM the 3 transition parameter details ( From, Event, To ), --- and one additional parameter that was given when the event was triggered, which is in this case the Unit that is used within OnSwitchAfter. --- --- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) --- --- For debugging reasons the received parameters are traced within the DCS.log. --- --- self:T( { From, Event, To, FsmUnit } ) --- --- The method will check if the From state received is either "Green" or "Red" and will flare the respective color from the FsmUnit. --- --- if From == "Green" then --- FsmUnit:Flare(FLARECOLOR.Green) --- else --- if From == "Red" then --- FsmUnit:Flare(FLARECOLOR.Red) --- end --- end --- --- It is important that the Switch event is again triggered, otherwise, the FsmDemo would stop working after having the first Event being handled. --- --- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. --- --- The below code fragment extends the FsmDemo, demonstrating multiple **From states declared as a table**, adding a **Linear Transition Rule**. --- The new event **Stop** will cancel the Switching process. --- The transition for event Stop can be executed if the current state of the FSM is either "Red" or "Green". --- --- local FsmDemo = FSM:New() -- #FsmDemo --- FsmDemo:SetStartState( "Green" ) --- FsmDemo:AddTransition( "Green", "Switch", "Red" ) --- FsmDemo:AddTransition( "Red", "Switch", "Green" ) --- FsmDemo:AddTransition( { "Red", "Green" }, "Stop", "Stopped" ) --- --- The transition for event Stop can also be simplified, as any current state of the FSM is valid. --- --- FsmDemo:AddTransition( "*", "Stop", "Stopped" ) --- --- So... When FsmDemo:Stop() is being triggered, the state of FsmDemo will transition from Red or Green to Stopped. --- And there is no transition handling method defined for that transition, thus, no new event is being triggered causing the FsmDemo process flow to halt. --- --- ## 1.5) FSM Hierarchical Transitions --- --- Hierarchical Transitions allow to re-use readily available and implemented FSMs. --- This becomes in very useful for mission building, where mission designers build complex processes and workflows, --- combining smaller FSMs to one single FSM. --- --- The FSM can embed **Sub-FSMs** that will execute and return **multiple possible Return (End) States**. --- Depending upon **which state is returned**, the main FSM can continue the flow **triggering specific events**. --- --- The method @{#FSM.AddProcess}() adds a new Sub-FSM to the FSM. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- YYYY-MM-DD: CLASS:**NewFunction**( Params ) replaces CLASS:_OldFunction_( Params ) --- YYYY-MM-DD: CLASS:**NewFunction( Params )** added --- --- Hereby the change log: --- --- * 2016-12-18: Released. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * [**Pikey**](https://forums.eagle.ru/member.php?u=62835): Review of documentation & advice for improvements. --- --- ### Authors: --- --- * [**FlightControl**](https://forums.eagle.ru/member.php?u=89536): Design & Programming & documentation. --- --- @module Fsm - -do -- FSM - - --- FSM class - -- @type FSM - -- @extends Core.Base#BASE - FSM = { - ClassName = "FSM", - } - - --- Creates a new FSM object. - -- @param #FSM self - -- @return #FSM - function FSM:New( FsmT ) - - -- Inherits from BASE - self = BASE:Inherit( self, BASE:New() ) - - self.options = options or {} - self.options.subs = self.options.subs or {} - self.current = self.options.initial or 'none' - self.Events = {} - self.subs = {} - self.endstates = {} - - self.Scores = {} - - self._StartState = "none" - self._Transitions = {} - self._Processes = {} - self._EndStates = {} - self._Scores = {} - self._EventSchedules = {} - - self.CallScheduler = SCHEDULER:New( self ) - - - return self - end - - - --- Sets the start state of the FSM. - -- @param #FSM self - -- @param #string State A string defining the start state. - function FSM:SetStartState( State ) - - self._StartState = State - self.current = State - end - - - --- Returns the start state of the FSM. - -- @param #FSM self - -- @return #string A string containing the start state. - function FSM:GetStartState() - - return self._StartState or {} - end - - --- Add a new transition rule to the FSM. - -- A transition rule defines when and if the FSM can transition from a state towards another state upon a triggered event. - -- @param #FSM self - -- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states. - -- @param #string Event The Event name. - -- @param #string To The To state. - function FSM:AddTransition( From, Event, To ) - - local Transition = {} - Transition.From = From - Transition.Event = Event - Transition.To = To - - self:T( Transition ) - - self._Transitions[Transition] = Transition - self:_eventmap( self.Events, Transition ) - end - - - --- Returns a table of the transition rules defined within the FSM. - -- @return #table - function FSM:GetTransitions() - - return self._Transitions or {} - end - - --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Controllable} by the task. - -- @param #FSM self - -- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states. - -- @param #string Event The Event name. - -- @param Core.Fsm#FSM_PROCESS Process An sub-process FSM. - -- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM. - -- @return Core.Fsm#FSM_PROCESS The SubFSM. - function FSM:AddProcess( From, Event, Process, ReturnEvents ) - self:T( { From, Event, Process, ReturnEvents } ) - - local Sub = {} - Sub.From = From - Sub.Event = Event - Sub.fsm = Process - Sub.StartEvent = "Start" - Sub.ReturnEvents = ReturnEvents - - self._Processes[Sub] = Sub - - self:_submap( self.subs, Sub, nil ) - - self:AddTransition( From, Event, From ) - - return Process - end - - - --- Returns a table of the SubFSM rules defined within the FSM. - -- @return #table - function FSM:GetProcesses() - - return self._Processes or {} - end - - function FSM:GetProcess( From, Event ) - - for ProcessID, Process in pairs( self:GetProcesses() ) do - if Process.From == From and Process.Event == Event then - self:T( Process ) - return Process.fsm - end - end - - error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) - end - - --- Adds an End state. - function FSM:AddEndState( State ) - - self._EndStates[State] = State - self.endstates[State] = State - end - - --- Returns the End states. - function FSM:GetEndStates() - - return self._EndStates or {} - end - - - --- Adds a score for the FSM to be achieved. - -- @param #FSM self - -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). - -- @param #string ScoreText is a text describing the score that is given according the status. - -- @param #number Score is a number providing the score of the status. - -- @return #FSM self - function FSM:AddScore( State, ScoreText, Score ) - self:F2( { State, ScoreText, Score } ) - - self._Scores[State] = self._Scores[State] or {} - self._Scores[State].ScoreText = ScoreText - self._Scores[State].Score = Score - - return self - end - - --- Adds a score for the FSM_PROCESS to be achieved. - -- @param #FSM self - -- @param #string From is the From State of the main process. - -- @param #string Event is the Event of the main process. - -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). - -- @param #string ScoreText is a text describing the score that is given according the status. - -- @param #number Score is a number providing the score of the status. - -- @return #FSM self - function FSM:AddScoreProcess( From, Event, State, ScoreText, Score ) - self:F2( { Event, State, ScoreText, Score } ) - - local Process = self:GetProcess( From, Event ) - - self:T( { Process = Process._Name, Scores = Process._Scores, State = State, ScoreText = ScoreText, Score = Score } ) - Process._Scores[State] = Process._Scores[State] or {} - Process._Scores[State].ScoreText = ScoreText - Process._Scores[State].Score = Score - - return Process - end - - --- Returns a table with the scores defined. - function FSM:GetScores() - - return self._Scores or {} - end - - --- Returns a table with the Subs defined. - function FSM:GetSubs() - - return self.options.subs - end - - - function FSM:LoadCallBacks( CallBackTable ) - - for name, callback in pairs( CallBackTable or {} ) do - self[name] = callback - end - - end - - function FSM:_eventmap( Events, EventStructure ) - - local Event = EventStructure.Event - local __Event = "__" .. EventStructure.Event - self[Event] = self[Event] or self:_create_transition(Event) - self[__Event] = self[__Event] or self:_delayed_transition(Event) - self:T( "Added methods: " .. Event .. ", " .. __Event ) - Events[Event] = self.Events[Event] or { map = {} } - self:_add_to_map( Events[Event].map, EventStructure ) - - end - - function FSM:_submap( subs, sub, name ) - self:F( { sub = sub, name = name } ) - subs[sub.From] = subs[sub.From] or {} - subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} - - -- Make the reference table weak. - -- setmetatable( subs[sub.From][sub.Event], { __mode = "k" } ) - - subs[sub.From][sub.Event][sub] = {} - subs[sub.From][sub.Event][sub].fsm = sub.fsm - subs[sub.From][sub.Event][sub].StartEvent = sub.StartEvent - subs[sub.From][sub.Event][sub].ReturnEvents = sub.ReturnEvents or {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. - subs[sub.From][sub.Event][sub].name = name - subs[sub.From][sub.Event][sub].fsmparent = self - end - - - function FSM:_call_handler( handler, params, EventName ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - if self[handler] then - self:T( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) - return Value - end - end - - function FSM._handler( self, EventName, ... ) - - local Can, to = self:can( EventName ) - - if to == "*" then - to = self.current - end - - if Can then - local from = self.current - local params = { from, EventName, to, ... } - - if self.Controllable then - self:T( "FSM Transition for " .. self.Controllable.ControllableName .. " :" .. self.current .. " --> " .. EventName .. " --> " .. to ) - else - self:T( "FSM Transition:" .. self.current .. " --> " .. EventName .. " --> " .. to ) - end - - if ( self:_call_handler("onbefore" .. EventName, params, EventName ) == false ) - or ( self:_call_handler("OnBefore" .. EventName, params, EventName ) == false ) - or ( self:_call_handler("onleave" .. from, params, EventName ) == false ) - or ( self:_call_handler("OnLeave" .. from, params, EventName ) == false ) then - self:T( "Cancel Transition" ) - return false - end - - self.current = to - - local execute = true - - local subtable = self:_gosub( from, EventName ) - for _, sub in pairs( subtable ) do - --if sub.nextevent then - -- self:F2( "nextevent = " .. sub.nextevent ) - -- self[sub.nextevent]( self ) - --end - self:T( "calling sub start event: " .. sub.StartEvent ) - sub.fsm.fsmparent = self - sub.fsm.ReturnEvents = sub.ReturnEvents - sub.fsm[sub.StartEvent]( sub.fsm ) - execute = false - end - - local fsmparent, Event = self:_isendstate( to ) - if fsmparent and Event then - self:F2( { "end state: ", fsmparent, Event } ) - self:_call_handler("onenter" .. to, params, EventName ) - self:_call_handler("OnEnter" .. to, params, EventName ) - self:_call_handler("onafter" .. EventName, params, EventName ) - self:_call_handler("OnAfter" .. EventName, params, EventName ) - self:_call_handler("onstatechange", params, EventName ) - fsmparent[Event]( fsmparent ) - execute = false - end - - if execute then - -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! - --if from ~= to then - self:_call_handler("onenter" .. to, params, EventName ) - self:_call_handler("OnEnter" .. to, params, EventName ) - --end - - self:_call_handler("onafter" .. EventName, params, EventName ) - self:_call_handler("OnAfter" .. EventName, params, EventName ) - - self:_call_handler("onstatechange", params, EventName ) - end - else - self:T( "Cannot execute transition." ) - self:T( { From = self.current, Event = EventName, To = to, Can = Can } ) - end - - return nil - end - - function FSM:_delayed_transition( EventName ) - return function( self, DelaySeconds, ... ) - self:T2( "Delayed Event: " .. EventName ) - local CallID = 0 - if DelaySeconds ~= nil then - if DelaySeconds < 0 then -- Only call the event ONCE! - DelaySeconds = math.abs( DelaySeconds ) - if not self._EventSchedules[EventName] then - CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) - self._EventSchedules[EventName] = CallID - else - -- reschedule - end - else - CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) - end - else - error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." ) - end - self:T2( { CallID = CallID } ) - end - end - - function FSM:_create_transition( EventName ) - return function( self, ... ) return self._handler( self, EventName , ... ) end - end - - function FSM:_gosub( ParentFrom, ParentEvent ) - local fsmtable = {} - if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then - self:T( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) - return self.subs[ParentFrom][ParentEvent] - else - return {} - end - end - - function FSM:_isendstate( Current ) - local FSMParent = self.fsmparent - if FSMParent and self.endstates[Current] then - self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) - FSMParent.current = Current - local ParentFrom = FSMParent.current - self:T( ParentFrom ) - self:T( self.ReturnEvents ) - local Event = self.ReturnEvents[Current] - self:T( { ParentFrom, Event, self.ReturnEvents } ) - if Event then - return FSMParent, Event - else - self:T( { "Could not find parent event name for state ", ParentFrom } ) - end - end - - return nil - end - - function FSM:_add_to_map( Map, Event ) - self:F3( { Map, Event } ) - if type(Event.From) == 'string' then - Map[Event.From] = Event.To - else - for _, From in ipairs(Event.From) do - Map[From] = Event.To - end - end - self:T3( { Map, Event } ) - end - - function FSM:GetState() - return self.current - end - - - function FSM:Is( State ) - return self.current == State - end - - function FSM:is(state) - return self.current == state - end - - function FSM:can(e) - local Event = self.Events[e] - self:F3( { self.current, Event } ) - local To = Event and Event.map[self.current] or Event.map['*'] - return To ~= nil, To - end - - function FSM:cannot(e) - return not self:can(e) - end - -end - -do -- FSM_CONTROLLABLE - - --- FSM_CONTROLLABLE class - -- @type FSM_CONTROLLABLE - -- @field Wrapper.Controllable#CONTROLLABLE Controllable - -- @extends Core.Fsm#FSM - FSM_CONTROLLABLE = { - ClassName = "FSM_CONTROLLABLE", - } - - --- Creates a new FSM_CONTROLLABLE object. - -- @param #FSM_CONTROLLABLE self - -- @param #table FSMT Finite State Machine Table - -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. - -- @return #FSM_CONTROLLABLE - function FSM_CONTROLLABLE:New( FSMT, Controllable ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New( FSMT ) ) -- Core.Fsm#FSM_CONTROLLABLE - - if Controllable then - self:SetControllable( Controllable ) - end - - self:AddTransition( "*", "Stop", "Stopped" ) - - --- OnBefore Transition Handler for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] OnBeforeStop - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] OnAfterStop - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] Stop - -- @param #FSM_CONTROLLABLE self - - --- Asynchronous Event Trigger for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] __Stop - -- @param #FSM_CONTROLLABLE self - -- @param #number Delay The delay in seconds. - - --- OnLeave Transition Handler for State Stopped. - -- @function [parent=#FSM_CONTROLLABLE] OnLeaveStopped - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Stopped. - -- @function [parent=#FSM_CONTROLLABLE] OnEnterStopped - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - return self - end - - --- OnAfter Transition Handler for Event Stop. - -- @function [parent=#FSM_CONTROLLABLE] OnAfterStop - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To) - - -- Clear all pending schedules - self.CallScheduler:Clear() - end - - --- Sets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. - -- @param #FSM_CONTROLLABLE self - -- @param Wrapper.Controllable#CONTROLLABLE FSMControllable - -- @return #FSM_CONTROLLABLE - function FSM_CONTROLLABLE:SetControllable( FSMControllable ) - self:F( FSMControllable ) - self.Controllable = FSMControllable - end - - --- Gets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. - -- @param #FSM_CONTROLLABLE self - -- @return Wrapper.Controllable#CONTROLLABLE - function FSM_CONTROLLABLE:GetControllable() - return self.Controllable - end - - function FSM_CONTROLLABLE:_call_handler( handler, params, EventName ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - - if self[handler] then - self:F3( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler ) - return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) - end - end - -end - -do -- FSM_PROCESS - - --- FSM_PROCESS class - -- @type FSM_PROCESS - -- @field Tasking.Task#TASK Task - -- @extends Core.Fsm#FSM_CONTROLLABLE - FSM_PROCESS = { - ClassName = "FSM_PROCESS", - } - - --- Creates a new FSM_PROCESS object. - -- @param #FSM_PROCESS self - -- @return #FSM_PROCESS - function FSM_PROCESS:New( Controllable, Task ) - - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS - - self:F( Controllable, Task ) - - self:Assign( Controllable, Task ) - - return self - end - - function FSM_PROCESS:Init( FsmProcess ) - self:T( "No Initialisation" ) - end - - function FSM_PROCESS:_call_handler( handler, params, EventName ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in FSM_PROCESS call handler:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - - if self[handler] then - self:F3( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler ) - return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) - end - end - - --- Creates a new FSM_PROCESS object based on this FSM_PROCESS. - -- @param #FSM_PROCESS self - -- @return #FSM_PROCESS - function FSM_PROCESS:Copy( Controllable, Task ) - self:T( { self:GetClassNameAndID() } ) - - - local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS - - NewFsm:Assign( Controllable, Task ) - - -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS - NewFsm:Init( self ) - - -- Set Start State - NewFsm:SetStartState( self:GetStartState() ) - - -- Copy Transitions - for TransitionID, Transition in pairs( self:GetTransitions() ) do - NewFsm:AddTransition( Transition.From, Transition.Event, Transition.To ) - end - - -- Copy Processes - for ProcessID, Process in pairs( self:GetProcesses() ) do - self:E( { Process} ) - local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) - end - - -- Copy End States - for EndStateID, EndState in pairs( self:GetEndStates() ) do - self:T( EndState ) - NewFsm:AddEndState( EndState ) - end - - -- Copy the score tables - for ScoreID, Score in pairs( self:GetScores() ) do - self:T( Score ) - NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) - end - - return NewFsm - end - - --- Removes an FSM_PROCESS object. - -- @param #FSM_PROCESS self - -- @return #FSM_PROCESS - function FSM_PROCESS:Remove() - self:T( { self:GetClassNameAndID() } ) - - -- Copy Processes - for ProcessID, Process in pairs( self:GetProcesses() ) do - self:E( { Process} ) - Process.fsm:Remove() - Process.fsm = nil - end - - return self - end - - --- Sets the task of the process. - -- @param #FSM_PROCESS self - -- @param Tasking.Task#TASK Task - -- @return #FSM_PROCESS - function FSM_PROCESS:SetTask( Task ) - - self.Task = Task - - return self - end - - --- Gets the task of the process. - -- @param #FSM_PROCESS self - -- @return Tasking.Task#TASK - function FSM_PROCESS:GetTask() - - return self.Task - end - - --- Gets the mission of the process. - -- @param #FSM_PROCESS self - -- @return Tasking.Mission#MISSION - function FSM_PROCESS:GetMission() - - return self.Task.Mission - end - - --- Gets the mission of the process. - -- @param #FSM_PROCESS self - -- @return Tasking.CommandCenter#COMMANDCENTER - function FSM_PROCESS:GetCommandCenter() - - return self:GetTask():GetMission():GetCommandCenter() - end - --- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. - - --- Send a message of the @{Task} to the Group of the Unit. --- @param #FSM_PROCESS self -function FSM_PROCESS:Message( Message ) - self:F( { Message = Message } ) - - local CC = self:GetCommandCenter() - local TaskGroup = self.Controllable:GetGroup() - - local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit - PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets. - local Callsign = self.Controllable:GetCallsign() - local Prefix = Callsign and " @ " .. Callsign .. PlayerName or "" - - Message = Prefix .. ": " .. Message - CC:MessageToGroup( Message, TaskGroup ) -end - - - - - --- Assign the process to a @{Unit} and activate the process. - -- @param #FSM_PROCESS self - -- @param Task.Tasking#TASK Task - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @return #FSM_PROCESS self - function FSM_PROCESS:Assign( ProcessUnit, Task ) - self:T( { Task, ProcessUnit } ) - - self:SetControllable( ProcessUnit ) - self:SetTask( Task ) - - --self.ProcessGroup = ProcessUnit:GetGroup() - - return self - end - - function FSM_PROCESS:onenterAssigned( ProcessUnit ) - self:T( "Assign" ) - - self.Task:Assign() - end - - function FSM_PROCESS:onenterFailed( ProcessUnit ) - self:T( "Failed" ) - - self.Task:Fail() - end - - function FSM_PROCESS:onenterSuccess( ProcessUnit ) - self:T( "Success" ) - - self.Task:Success() - end - - --- StateMachine callback function for a FSM_PROCESS - -- @param #FSM_PROCESS self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function FSM_PROCESS:onstatechange( ProcessUnit, From, Event, To, Dummy ) - self:T( { ProcessUnit, From, Event, To, Dummy, self:IsTrace() } ) - - if self:IsTrace() then - MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() - end - - self:T( self._Scores[To] ) - -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... - if self._Scores[To] then - - local Task = self.Task - local Scoring = Task:GetScoring() - if Scoring then - Scoring:_AddMissionTaskScore( Task.Mission, ProcessUnit, self._Scores[To].ScoreText, self._Scores[To].Score ) - end - end - end - -end - -do -- FSM_TASK - - --- FSM_TASK class - -- @type FSM_TASK - -- @field Tasking.Task#TASK Task - -- @extends Core.Fsm#FSM - FSM_TASK = { - ClassName = "FSM_TASK", - } - - --- Creates a new FSM_TASK object. - -- @param #FSM_TASK self - -- @param #table FSMT - -- @param Tasking.Task#TASK Task - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return #FSM_TASK - function FSM_TASK:New( FSMT ) - - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( FSMT ) ) -- Core.Fsm#FSM_TASK - - self["onstatechange"] = self.OnStateChange - - return self - end - - function FSM_TASK:_call_handler( handler, params, EventName ) - if self[handler] then - self:T( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - return self[handler]( self, unpack( params ) ) - end - end - -end -- FSM_TASK - -do -- FSM_SET - - --- FSM_SET class - -- @type FSM_SET - -- @field Core.Set#SET_BASE Set - -- @extends Core.Fsm#FSM - FSM_SET = { - ClassName = "FSM_SET", - } - - --- Creates a new FSM_SET object. - -- @param #FSM_SET self - -- @param #table FSMT Finite State Machine Table - -- @param Set_SET_BASE FSMSet (optional) The Set object that the FSM_SET governs. - -- @return #FSM_SET - function FSM_SET:New( FSMSet ) - - -- Inherits from BASE - self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_SET - - if FSMSet then - self:Set( FSMSet ) - end - - return self - end - - --- Sets the SET_BASE object that the FSM_SET governs. - -- @param #FSM_SET self - -- @param Core.Set#SET_BASE FSMSet - -- @return #FSM_SET - function FSM_SET:Set( FSMSet ) - self:F( FSMSet ) - self.Set = FSMSet - end - - --- Gets the SET_BASE object that the FSM_SET governs. - -- @param #FSM_SET self - -- @return Core.Set#SET_BASE - function FSM_SET:Get() - return self.Controllable - end - - function FSM_SET:_call_handler( handler, params, EventName ) - if self[handler] then - self:T( "Calling " .. handler ) - self._EventSchedules[EventName] = nil - return self[handler]( self, self.Set, unpack( params ) ) - end - end - -end -- FSM_SET - ---- This module contains the OBJECT class. --- --- 1) @{Object#OBJECT} class, extends @{Base#BASE} --- =========================================================== --- The @{Object#OBJECT} class is a wrapper class to handle the DCS Object objects: --- --- * Support all DCS Object APIs. --- * Enhance with Object specific APIs not in the DCS Object API set. --- * Manage the "state" of the DCS Object. --- --- 1.1) OBJECT constructor: --- ------------------------------ --- The OBJECT class provides the following functions to construct a OBJECT instance: --- --- * @{Object#OBJECT.New}(): Create a OBJECT instance. --- --- 1.2) OBJECT methods: --- -------------------------- --- The following methods can be used to identify an Object object: --- --- * @{Object#OBJECT.GetID}(): Returns the ID of the Object object. --- --- === --- --- @module Object - ---- The OBJECT class --- @type OBJECT --- @extends Core.Base#BASE --- @field #string ObjectName The name of the Object. -OBJECT = { - ClassName = "OBJECT", - ObjectName = "", -} - ---- A DCSObject --- @type DCSObject --- @field id_ The ID of the controllable in DCS - ---- Create a new OBJECT from a DCSObject --- @param #OBJECT self --- @param Dcs.DCSWrapper.Object#Object ObjectName The Object name --- @return #OBJECT self -function OBJECT:New( ObjectName, Test ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( ObjectName ) - self.ObjectName = ObjectName - - return self -end - - ---- Returns the unit's unique identifier. --- @param Wrapper.Object#OBJECT self --- @return Dcs.DCSWrapper.Object#Object.ID ObjectID --- @return #nil The DCS Object is not existing or alive. -function OBJECT:GetID() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - local ObjectID = DCSObject:getID() - return ObjectID - end - - return nil -end - ---- Destroys the OBJECT. --- @param #OBJECT self --- @return #nil The DCS Unit is not existing or alive. -function OBJECT:Destroy() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - - DCSObject:destroy() - end - - return nil -end - - - - ---- This module contains the IDENTIFIABLE class. --- --- 1) @{#IDENTIFIABLE} class, extends @{Object#OBJECT} --- =============================================================== --- The @{#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: --- --- * Support all DCS Identifiable APIs. --- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. --- * Manage the "state" of the DCS Identifiable. --- --- 1.1) IDENTIFIABLE constructor: --- ------------------------------ --- The IDENTIFIABLE class provides the following functions to construct a IDENTIFIABLE instance: --- --- * @{#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. --- --- 1.2) IDENTIFIABLE methods: --- -------------------------- --- The following methods can be used to identify an identifiable object: --- --- * @{#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. --- * @{#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. --- * @{#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. --- * @{#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. --- * @{#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. --- * @{#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. --- --- --- === --- --- @module Identifiable - ---- The IDENTIFIABLE class --- @type IDENTIFIABLE --- @extends Wrapper.Object#OBJECT --- @field #string IdentifiableName The name of the identifiable. -IDENTIFIABLE = { - ClassName = "IDENTIFIABLE", - IdentifiableName = "", -} - -local _CategoryName = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicoper", - [Unit.Category.GROUND_UNIT] = "Ground Identifiable", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - ---- Create a new IDENTIFIABLE from a DCSIdentifiable --- @param #IDENTIFIABLE self --- @param Dcs.DCSWrapper.Identifiable#Identifiable IdentifiableName The DCS Identifiable name --- @return #IDENTIFIABLE self -function IDENTIFIABLE:New( IdentifiableName ) - local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) - self:F2( IdentifiableName ) - self.IdentifiableName = IdentifiableName - return self -end - ---- Returns if the Identifiable is alive. --- @param #IDENTIFIABLE self --- @return #boolean true if Identifiable is alive. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:IsAlive() - self:F3( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableIsAlive = DCSIdentifiable:isExist() - return IdentifiableIsAlive - end - - return false -end - - - - ---- Returns DCS Identifiable object name. --- The function provides access to non-activated objects too. --- @param #IDENTIFIABLE self --- @return #string The name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableName = self.IdentifiableName - return IdentifiableName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns the type name of the DCS Identifiable. --- @param #IDENTIFIABLE self --- @return #string The type name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetTypeName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableTypeName = DCSIdentifiable:getTypeName() - self:T3( IdentifiableTypeName ) - return IdentifiableTypeName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns category of the DCS Identifiable. --- @param #IDENTIFIABLE self --- @return Dcs.DCSWrapper.Object#Object.Category The category ID -function IDENTIFIABLE:GetCategory() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - local ObjectCategory = DCSObject:getCategory() - self:T3( ObjectCategory ) - return ObjectCategory - end - - return nil -end - - ---- Returns the DCS Identifiable category name as defined within the DCS Identifiable Descriptor. --- @param #IDENTIFIABLE self --- @return #string The DCS Identifiable Category Name -function IDENTIFIABLE:GetCategoryName() - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCategoryName = _CategoryName[ self:GetDesc().category ] - return IdentifiableCategoryName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns coalition of the Identifiable. --- @param #IDENTIFIABLE self --- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The side of the coalition. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCoalition() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCoalition = DCSIdentifiable:getCoalition() - self:T3( IdentifiableCoalition ) - return IdentifiableCoalition - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns country of the Identifiable. --- @param #IDENTIFIABLE self --- @return Dcs.DCScountry#country.id The country identifier. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCountry() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCountry = DCSIdentifiable:getCountry() - self:T3( IdentifiableCountry ) - return IdentifiableCountry - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - - ---- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. --- @param #IDENTIFIABLE self --- @return Dcs.DCSWrapper.Identifiable#Identifiable.Desc The Identifiable descriptor. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetDesc() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableDesc = DCSIdentifiable:getDesc() - self:T2( IdentifiableDesc ) - return IdentifiableDesc - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Gets the CallSign of the IDENTIFIABLE, which is a blank by default. --- @param #IDENTIFIABLE self --- @return #string The CallSign of the IDENTIFIABLE. -function IDENTIFIABLE:GetCallsign() - return '' -end - - -function IDENTIFIABLE:GetThreatLevel() - - return 0, "Scenery" -end ---- This module contains the POSITIONABLE class. --- --- 1) @{Positionable#POSITIONABLE} class, extends @{Identifiable#IDENTIFIABLE} --- =========================================================== --- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the POSITIONABLE objects: --- --- * Support all DCS APIs. --- * Enhance with POSITIONABLE specific APIs not in the DCS API set. --- * Manage the "state" of the POSITIONABLE. --- --- 1.1) POSITIONABLE constructor: --- ------------------------------ --- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: --- --- * @{Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. --- --- 1.2) POSITIONABLE methods: --- -------------------------- --- The following methods can be used to identify an measurable object: --- --- * @{Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. --- * @{Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. --- --- === --- --- @module Positionable - ---- The POSITIONABLE class --- @type POSITIONABLE --- @extends Wrapper.Identifiable#IDENTIFIABLE --- @field #string PositionableName The name of the measurable. -POSITIONABLE = { - ClassName = "POSITIONABLE", - PositionableName = "", -} - ---- A DCSPositionable --- @type DCSPositionable --- @field id_ The ID of the controllable in DCS - ---- Create a new POSITIONABLE from a DCSPositionable --- @param #POSITIONABLE self --- @param Dcs.DCSWrapper.Positionable#Positionable PositionableName The POSITIONABLE name --- @return #POSITIONABLE self -function POSITIONABLE:New( PositionableName ) - local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) - - self.PositionableName = PositionableName - return self -end - ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPositionVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePosition = DCSPositionable:getPosition().p - self:T3( PositionablePosition ) - return PositionablePosition - end - - return nil -end - ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionableVec2 = {} - PositionableVec2.x = PositionableVec3.x - PositionableVec2.y = PositionableVec3.z - - self:T2( PositionableVec2 ) - return PositionableVec2 - end - - return nil -end - ---- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPointVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) - - self:T2( PositionablePointVec2 ) - return PositionablePointVec2 - end - - return nil -end - ---- Returns a POINT_VEC3 object indicating the point in 3D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPointVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = self:GetPositionVec3() - - local PositionablePointVec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) - - self:T2( PositionablePointVec3 ) - return PositionablePointVec3 - end - - return nil -end - - ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetRandomVec3( Radius ) - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p - local PositionableRandomVec3 = {} - local angle = math.random() * math.pi*2; - PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; - PositionableRandomVec3.y = PositionablePointVec3.y - PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; - - self:T3( PositionableRandomVec3 ) - return PositionableRandomVec3 - end - - return nil -end - ---- Returns the @{DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - self:T3( PositionableVec3 ) - return PositionableVec3 - end - - return nil -end - ---- Returns the altitude of the POSITIONABLE. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Distance The altitude of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetAltitude() - self:F2() - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --Dcs.DCSTypes#Vec3 - return PositionablePointVec3.y - end - - return nil -end - ---- Returns if the Positionable is located above a runway. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #boolean true if Positionable is above a runway. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:IsAboveRunway() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local Vec2 = self:GetVec2() - local SurfaceType = land.getSurfaceType( Vec2 ) - local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY - - self:T2( IsAboveRunway ) - return IsAboveRunway - end - - return nil -end - - - ---- Returns the POSITIONABLE heading in degrees. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The POSTIONABLE heading -function POSITIONABLE:GetHeading() - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local PositionablePosition = DCSPositionable:getPosition() - if PositionablePosition then - local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) - if PositionableHeading < 0 then - PositionableHeading = PositionableHeading + 2 * math.pi - end - PositionableHeading = PositionableHeading * 180 / math.pi - self:T2( PositionableHeading ) - return PositionableHeading - end - end - - return nil -end - - ---- Returns true if the POSITIONABLE is in the air. --- Polymorphic, is overridden in GROUP and UNIT. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #boolean true if in the air. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:InAir() - self:F2( self.PositionableName ) - - return nil -end - - ---- Returns the POSITIONABLE velocity vector. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Vec3 The velocity vector --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocity() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVelocityVec3 = DCSPositionable:getVelocity() - self:T3( PositionableVelocityVec3 ) - return PositionableVelocityVec3 - end - - return nil -end - ---- Returns the POSITIONABLE velocity in km/h. --- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The velocity in km/h --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocityKMH() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local VelocityVec3 = self:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - self:T3( Velocity ) - return Velocity - end - - return nil -end - ---- Returns a message with the callsign embedded (if there is one). --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. --- @return Core.Message#MESSAGE -function POSITIONABLE:GetMessage( Message, Duration, Name ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - Name = Name or self:GetTypeName() - return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. Name .. ")" ) - end - - return nil -end - ---- Send a message to all coalitions. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToAll( Message, Duration, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToAll() - end - - return nil -end - ---- Send a message to a coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTYpes#Duration Duration The duration of the message. --- @param Dcs.DCScoalition#coalition MessageCoalition The Coalition receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToCoalition( MessageCoalition ) - end - - return nil -end - - ---- Send a message to the red coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTYpes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToRed( Message, Duration, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToRed() - end - - return nil -end - ---- Send a message to the blue coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToBlue( Message, Duration, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToBlue() - end - - return nil -end - ---- Send a message to a client. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param Wrapper.Client#CLIENT Client The client object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToClient( Client ) - end - - return nil -end - ---- Send a message to a @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - if DCSObject:isExist() then - self:GetMessage( Message, Duration, Name ):ToGroup( MessageGroup ) - end - end - - return nil -end - ---- Send a message to the players in the @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #POSITIONABLE self --- @param #string Message The message text --- @param Dcs.DCSTypes#Duration Duration The duration of the message. --- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. -function POSITIONABLE:Message( Message, Duration, Name ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration, Name ):ToGroup( self ) - end - - return nil -end - - - - - ---- This module contains the CONTROLLABLE class. --- --- 1) @{Controllable#CONTROLLABLE} class, extends @{Positionable#POSITIONABLE} --- =========================================================== --- The @{Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: --- --- * Support all DCS Controllable APIs. --- * Enhance with Controllable specific APIs not in the DCS Controllable API set. --- * Handle local Controllable Controller. --- * Manage the "state" of the DCS Controllable. --- --- 1.1) CONTROLLABLE constructor --- ----------------------------- --- The CONTROLLABLE class provides the following functions to construct a CONTROLLABLE instance: --- --- * @{#CONTROLLABLE.New}(): Create a CONTROLLABLE instance. --- --- 1.2) CONTROLLABLE task methods --- ------------------------------ --- Several controllable task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#SetTask} method to assign the task to the CONTROLLABLE. --- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. --- Each task description where applicable indicates for which controllable category the task is valid. --- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- --- ### 1.2.1) Assigned task methods --- --- Assigned task methods make the controllable execute the task where the location of the (possible) targets of the task are known before being detected. --- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- --- Find below a list of the **assigned task** methods: --- --- * @{#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Controllable. --- * @{#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.TaskBombing}: (AIR) Delivering weapon at the point on the ground. --- * @{#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. --- * @{#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire some or all ammunition at a VEC2 point. --- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. --- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. --- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. --- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. --- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. --- --- ### 1.2.2) EnRoute task methods --- --- EnRoute tasks require the targets of the task need to be detected by the controllable (using its sensors) before the task can be executed: --- --- * @{#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{#CONTROLLABLE.EnRouteTaskEngageControllable}: (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskFAC_EngageControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- --- ### 1.2.3) Preparation task methods --- --- There are certain task methods that allow to tailor the task behaviour: --- --- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- --- ### 1.2.4) Obtain the mission from controllable templates --- --- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another: --- --- * @{#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- 1.3) CONTROLLABLE Command methods --- -------------------------- --- Controllable **command methods** prepare the execution of commands using the @{#CONTROLLABLE.SetCommand} method: --- --- * @{#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- --- 1.4) CONTROLLABLE Option methods --- ------------------------- --- Controllable **Option methods** change the behaviour of the Controllable while being alive. --- --- ### 1.4.1) Rule of Engagement: --- --- * @{#CONTROLLABLE.OptionROEWeaponFree} --- * @{#CONTROLLABLE.OptionROEOpenFire} --- * @{#CONTROLLABLE.OptionROEReturnFire} --- * @{#CONTROLLABLE.OptionROEEvadeFire} --- --- To check whether an ROE option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} --- --- ### 1.4.2) Rule on thread: --- --- * @{#CONTROLLABLE.OptionROTNoReaction} --- * @{#CONTROLLABLE.OptionROTPassiveDefense} --- * @{#CONTROLLABLE.OptionROTEvadeFire} --- * @{#CONTROLLABLE.OptionROTVertical} --- --- To test whether an ROT option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{#CONTROLLABLE.OptionROTVerticalPossible} --- --- === --- --- @module Controllable - ---- The CONTROLLABLE class --- @type CONTROLLABLE --- @extends Wrapper.Positionable#POSITIONABLE --- @field Dcs.DCSWrapper.Controllable#Controllable DCSControllable The DCS controllable class. --- @field #string ControllableName The name of the controllable. -CONTROLLABLE = { - ClassName = "CONTROLLABLE", - ControllableName = "", - WayPointFunctions = {}, -} - ---- Create a new CONTROLLABLE from a DCSControllable --- @param #CONTROLLABLE self --- @param Dcs.DCSWrapper.Controllable#Controllable ControllableName The DCS Controllable name --- @return #CONTROLLABLE self -function CONTROLLABLE:New( ControllableName ) - local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) - self:F2( ControllableName ) - self.ControllableName = ControllableName - - self.TaskScheduler = SCHEDULER:New( self ) - return self -end - --- DCS Controllable methods support. - ---- Get the controller for the CONTROLLABLE. --- @param #CONTROLLABLE self --- @return Dcs.DCSController#Controller -function CONTROLLABLE:_GetController() - self:F2( { self.ControllableName } ) - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllableController = DCSControllable:getController() - self:T3( ControllableController ) - return ControllableController - end - - return nil -end - --- Get methods - ---- Returns the UNITs wrappers of the DCS Units of the Controllable (default is a GROUP). --- @param #CONTROLLABLE self --- @return #list The UNITs wrappers. -function CONTROLLABLE:GetUnits() - self:F2( { self.ControllableName } ) - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local DCSUnits = DCSControllable:getUnits() - local Units = {} - for Index, UnitData in pairs( DCSUnits ) do - Units[#Units+1] = UNIT:Find( UnitData ) - end - self:T3( Units ) - return Units - end - - return nil -end - - ---- Returns the health. Dead controllables have health <= 1.0. --- @param #CONTROLLABLE self --- @return #number The controllable health value (unit or group average). --- @return #nil The controllable is not existing or alive. -function CONTROLLABLE:GetLife() - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local UnitLife = 0 - local Units = self:GetUnits() - if #Units == 1 then - local Unit = Units[1] -- Wrapper.Unit#UNIT - UnitLife = Unit:GetLife() - else - local UnitLifeTotal = 0 - for UnitID, Unit in pairs( Units ) do - local Unit = Unit -- Wrapper.Unit#UNIT - UnitLifeTotal = UnitLifeTotal + Unit:GetLife() - end - UnitLife = UnitLifeTotal / #Units - end - return UnitLife - end - - return nil -end - - - --- Tasks - ---- Clear all tasks from the controllable. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE -function CONTROLLABLE:ClearTasks() - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:resetTask() - return self - end - - return nil -end - - ---- Popping current Task from the controllable. --- @param #CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:PopCurrentTask() - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:popTask() - return self - end - - return nil -end - ---- Pushing Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:PushTask( DCSTask, WaitTime ) - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller:pushTask( DCSTask ) - - if WaitTime then - self.TaskScheduler:Schedule( Controller, Controller.pushTask, { DCSTask }, WaitTime ) - else - Controller:pushTask( DCSTask ) - end - - return self - end - - return nil -end - ---- Clearing the Task Queue and Setting the Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:SetTask( DCSTask, WaitTime ) - self:F2( { DCSTask } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local Controller = self:_GetController() - self:T3( Controller ) - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller.setTask( Controller, DCSTask ) - - if not WaitTime then - Controller:setTask( DCSTask ) - else - self.TaskScheduler:Schedule( Controller, Controller.setTask, { DCSTask }, WaitTime ) - end - - return self - end - - return nil -end - - ---- Return a condition section for a controlled task. --- @param #CONTROLLABLE self --- @param Dcs.DCSTime#Time time --- @param #string userFlag --- @param #boolean userFlagValue --- @param #string condition --- @param Dcs.DCSTime#Time duration --- @param #number lastWayPoint --- return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) - self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) - - local DCSStopCondition = {} - DCSStopCondition.time = time - DCSStopCondition.userFlag = userFlag - DCSStopCondition.userFlagValue = userFlagValue - DCSStopCondition.condition = condition - DCSStopCondition.duration = duration - DCSStopCondition.lastWayPoint = lastWayPoint - - self:T3( { DCSStopCondition } ) - return DCSStopCondition -end - ---- Return a Controlled Task taking a Task and a TaskCondition. --- @param #CONTROLLABLE self --- @param Dcs.DCSTasking.Task#Task DCSTask --- @param #DCSStopCondition DCSStopCondition --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) - self:F2( { DCSTask, DCSStopCondition } ) - - local DCSTaskControlled - - DCSTaskControlled = { - id = 'ControlledTask', - params = { - task = DCSTask, - stopCondition = DCSStopCondition - } - } - - self:T3( { DCSTaskControlled } ) - return DCSTaskControlled -end - ---- Return a Combo Task taking an array of Tasks. --- @param #CONTROLLABLE self --- @param Dcs.DCSTasking.Task#TaskArray DCSTasks Array of @{DCSTasking.Task#Task} --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskCombo( DCSTasks ) - self:F2( { DCSTasks } ) - - local DCSTaskCombo - - DCSTaskCombo = { - id = 'ComboTask', - params = { - tasks = DCSTasks - } - } - - for TaskID, Task in ipairs( DCSTasks ) do - self:E( Task ) - end - - self:T3( { DCSTaskCombo } ) - return DCSTaskCombo -end - ---- Return a WrappedAction Task taking a Command. --- @param #CONTROLLABLE self --- @param Dcs.DCSCommand#Command DCSCommand --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) - self:F2( { DCSCommand } ) - - local DCSTaskWrappedAction - - DCSTaskWrappedAction = { - id = "WrappedAction", - enabled = true, - number = Index, - auto = false, - params = { - action = DCSCommand, - }, - } - - self:T3( { DCSTaskWrappedAction } ) - return DCSTaskWrappedAction -end - ---- Executes a command action --- @param #CONTROLLABLE self --- @param Dcs.DCSCommand#Command DCSCommand --- @return #CONTROLLABLE self -function CONTROLLABLE:SetCommand( DCSCommand ) - self:F2( DCSCommand ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:setCommand( DCSCommand ) - return self - end - - return nil -end - ---- Perform a switch waypoint command --- @param #CONTROLLABLE self --- @param #number FromWayPoint --- @param #number ToWayPoint --- @return Dcs.DCSTasking.Task#Task --- @usage --- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. --- HeliGroup = GROUP:FindByName( "Helicopter" ) --- --- --- Route the helicopter back to the FARP after 60 seconds. --- -- We use the SCHEDULER class to do this. --- SCHEDULER:New( nil, --- function( HeliGroup ) --- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) --- HeliGroup:SetCommand( CommandRTB ) --- end, { HeliGroup }, 90 --- ) -function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) - self:F2( { FromWayPoint, ToWayPoint } ) - - local CommandSwitchWayPoint = { - id = 'SwitchWaypoint', - params = { - fromWaypointIndex = FromWayPoint, - goToWaypointIndex = ToWayPoint, - }, - } - - self:T3( { CommandSwitchWayPoint } ) - return CommandSwitchWayPoint -end - ---- Perform stop route command --- @param #CONTROLLABLE self --- @param #boolean StopRoute --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:CommandStopRoute( StopRoute, Index ) - self:F2( { StopRoute, Index } ) - - local CommandStopRoute = { - id = 'StopRoute', - params = { - value = StopRoute, - }, - } - - self:T3( { CommandStopRoute } ) - return CommandStopRoute -end - - --- TASKS FOR AIR CONTROLLABLES - - ---- (AIR) Attack a Controllable. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- AttackGroup = { - -- id = 'AttackGroup', - -- params = { - -- groupId = Group.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'AttackGroup', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR) Attack the Unit. --- @param #CONTROLLABLE self --- @param Wrapper.Unit#UNIT AttackUnit The UNIT. --- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskAttackUnit( AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack } ) - - local DCSTask - DCSTask = { - id = 'AttackUnit', - params = { - unitId = AttackUnit:GetID(), - groupAttack = GroupAttack or false, - visible = Visible or false, - expend = WeaponExpend or "Auto", - directionEnabled = Direction and true or false, - direction = Direction, - altitudeEnabled = Altitude and true or false, - altitude = Altitude or 30, - attackQtyLimit = AttackQty and true or false, - attackQty = AttackQty, - weaponType = 1073741822, - }, - } - - self:E( DCSTask ) - - return DCSTask -end - - ---- (AIR) Delivering weapon at the point on the ground. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) Desired quantity of passes. The parameter is not the same in AttackGroup and AttackUnit tasks. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- Bombing = { --- id = 'Bombing', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'Bombing', - params = { - point = Vec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point to hold the position. --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) - self:F2( { self.ControllableName, Point, Altitude, Speed } ) - - -- pattern = enum AI.Task.OribtPattern, - -- point = Vec2, - -- point2 = Vec2, - -- speed = Distance, - -- altitude = Distance - - local LandHeight = land.getHeight( Point ) - - self:T3( { LandHeight } ) - - local DCSTask = { id = 'Orbit', - params = { pattern = AI.Task.OrbitPattern.CIRCLE, - point = Point, - speed = Speed, - altitude = Altitude + LandHeight - } - } - - - -- local AITask = { id = 'ControlledTask', - -- params = { task = { id = 'Orbit', - -- params = { pattern = AI.Task.OrbitPattern.CIRCLE, - -- point = Point, - -- speed = Speed, - -- altitude = Altitude + LandHeight - -- } - -- }, - -- stopCondition = { duration = Duration - -- } - -- } - -- } - -- ) - - return DCSTask -end - ---- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- @param #CONTROLLABLE self --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) - self:F2( { self.ControllableName, Altitude, Speed } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllablePoint = self:GetVec2() - return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) - end - - return nil -end - - - ---- (AIR) Hold position at the current position of the first unit of the controllable. --- @param #CONTROLLABLE self --- @param #number Duration The maximum duration in seconds to hold the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskHoldPosition() - self:F2( { self.ControllableName } ) - - return self:TaskOrbitCircle( 30, 10 ) -end - - - - ---- (AIR) Attacking the map object (building, structure, e.t.c). --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- AttackMapObject = { --- id = 'AttackMapObject', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'AttackMapObject', - params = { - point = Vec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Delivering weapon on the runway. --- @param #CONTROLLABLE self --- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- BombingRunway = { --- id = 'BombingRunway', --- params = { --- runwayId = AirdromeId, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'BombingRunway', - params = { - point = Airbase:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Refueling from the nearest tanker. No parameters. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskRefueling() - self:F2( { self.ControllableName } ) - --- Refueling = { --- id = 'Refueling', --- params = {} --- } - - local DCSTask - DCSTask = { id = 'Refueling', - params = { - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR HELICOPTER) Landing at the ground. For helicopters only. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) - self:F2( { self.ControllableName, Point, Duration } ) - --- Land = { --- id= 'Land', --- params = { --- point = Vec2, --- durationFlag = boolean, --- duration = Time --- } --- } - - local DCSTask - if Duration and Duration > 0 then - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = true, - duration = Duration, - }, - } - else - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = false, - }, - } - end - - self:T3( DCSTask ) - return DCSTask -end - ---- (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- @param #CONTROLLABLE self --- @param Core.Zone#ZONE Zone The zone where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) - self:F2( { self.ControllableName, Zone, Duration, RandomPoint } ) - - local Point - if RandomPoint then - Point = Zone:GetRandomVec2() - else - Point = Zone:GetVec2() - end - - local DCSTask = self:TaskLandAtVec2( Point, Duration ) - - self:T3( DCSTask ) - return DCSTask -end - - - ---- (AIR) Following another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- If another controllable is on land the unit / controllable will orbit around. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE FollowControllable The controllable to be followed. --- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) - self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) - --- Follow = { --- id = 'Follow', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number --- } --- } - - local LastWaypointIndexFlag = false - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { - id = 'Follow', - params = { - groupId = FollowControllable:GetID(), - pos = Vec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Escort another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- The unit / controllable will also protect that controllable from threats of specified types. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. --- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) - self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) - --- Escort = { --- id = 'Escort', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number, --- engagementDistMax = Distance, --- targetTypes = array of AttributeName, --- } --- } - - local LastWaypointIndexFlag = false - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { id = 'Escort', - params = { - groupId = FollowControllable:GetID(), - pos = Vec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex, - engagementDistMax = EngagementDistance, - targetTypes = TargetTypes, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - --- GROUND TASKS - ---- (GROUND) Fire at a VEC2 point until ammunition is finished. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 The point to fire at. --- @param Dcs.DCSTypes#Distance Radius The radius of the zone to deploy the fire at. --- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted). --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount ) - self:F2( { self.ControllableName, Vec2, Radius, AmmoCount } ) - - -- FireAtPoint = { - -- id = 'FireAtPoint', - -- params = { - -- point = Vec2, - -- radius = Distance, - -- expendQty = number, - -- expendQtyEnabled = boolean, - -- } - -- } - - local DCSTask - DCSTask = { id = 'FireAtPoint', - params = { - point = Vec2, - radius = Radius, - expendQty = 100, -- dummy value - expendQtyEnabled = false, - } - } - - if AmmoCount then - DCSTask.params.expendQty = AmmoCount - DCSTask.params.expendQtyEnabled = true - end - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Hold ground controllable from moving. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskHold() - self:F2( { self.ControllableName } ) - --- Hold = { --- id = 'Hold', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Hold', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) - --- FAC_AttackGroup = { --- id = 'FAC_AttackGroup', --- params = { --- groupId = Group.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_AttackGroup', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - --- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES - ---- (AIR) Engaging targets of defined types. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) - self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) - --- EngageTargets ={ --- id = 'EngageTargets', --- params = { --- maxDist = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargets', - params = { - maxDist = Distance, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Engaging a targets of defined types at circle-shaped zone. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the zone. --- @param Dcs.DCSTypes#Distance Radius Radius of the zone. --- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) - self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) - --- EngageTargetsInZone = { --- id = 'EngageTargetsInZone', --- params = { --- point = Vec2, --- zoneRadius = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargetsInZone', - params = { - point = Vec2, - zoneRadius = Radius, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- EngageControllable = { - -- id = 'EngageControllable ', - -- params = { - -- groupId = Group.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- priority = number, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'EngageControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - priority = Priority, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Search and attack the Unit. --- @param #CONTROLLABLE self --- @param Wrapper.Unit#UNIT EngageUnit The UNIT. --- @param #number Priority (optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param Dcs.DCSTypes#Distance Altitude (optional) Desired altitude to perform the unit engagement. --- @param #boolean Visible (optional) Unit must be visible. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageUnit( EngageUnit, Priority, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack ) - self:F2( { self.ControllableName, EngageUnit, Priority, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, Visible, ControllableAttack } ) - - -- EngageUnit = { - -- id = 'EngageUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- priority = number, - -- } - -- } - - local DCSTask - DCSTask = { id = 'EngageUnit', - params = { - unitId = EngageUnit:GetID(), - priority = Priority or 1, - groupAttack = GroupAttack or false, - visible = Visible or false, - expend = WeaponExpend or "Auto", - directionEnabled = Direction and true or false, - direction = Direction, - altitudeEnabled = Altitude and true or false, - altitude = Altitude, - attackQtyLimit = AttackQty and true or false, - attackQty = AttackQty, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskAWACS( ) - self:F2( { self.ControllableName } ) - --- AWACS = { --- id = 'AWACS', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'AWACS', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskTanker( ) - self:F2( { self.ControllableName } ) - --- Tanker = { --- id = 'Tanker', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Tanker', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for ground units/controllables - ---- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEWR( ) - self:F2( { self.ControllableName } ) - --- EWR = { --- id = 'EWR', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'EWR', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for airborne and ground units/controllables - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) - --- FAC_EngageControllable = { --- id = 'FAC_EngageControllable', --- params = { --- groupId = Group.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean, --- priority = number, --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_EngageControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - priority = Priority, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Distance Radius The maximal distance from the FAC to a target. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) - self:F2( { self.ControllableName, Radius, Priority } ) - --- FAC = { --- id = 'FAC', --- params = { --- radius = Distance, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'FAC', - params = { - radius = Radius, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - - ---- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to wait. --- @param #number Duration The duration in seconds to wait. --- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. --- @return Dcs.DCSTasking.Task#Task The DCS task structure -function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) - self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) - - local DCSTask - DCSTask = { id = 'Embarking', - params = { x = Point.x, - y = Point.y, - duration = Duration, - controllablesForEmbarking = { EmbarkingControllable.ControllableID }, - durationFlag = true, - distributionFlag = false, - distribution = {}, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Embark to a Transport landed at a location. - ---- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec2 Point The point where to wait. --- @param #number Radius The radius of the embarking zone around the Point. --- @return Dcs.DCSTasking.Task#Task The DCS task structure. -function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) - self:F2( { self.ControllableName, Point, Radius } ) - - local DCSTask --Dcs.DCSTasking.Task#Task - DCSTask = { id = 'EmbarkToTransport', - params = { x = Point.x, - y = Point.y, - zoneRadius = Radius, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR + GROUND) Return a mission task from a mission template. --- @param #CONTROLLABLE self --- @param #table TaskMission A table containing the mission task. --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskMission( TaskMission ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { TaskMission, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- Return a Misson task to follow a given route defined by Points. --- @param #CONTROLLABLE self --- @param #table Points A table of route points. --- @return Dcs.DCSTasking.Task#Task -function CONTROLLABLE:TaskRoute( Points ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { route = { points = Points, }, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR + GROUND) Make the Controllable move to fly to a given point. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:RouteToVec2( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllablePoint = self:GetUnit( 1 ):GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.y - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - ---- (AIR + GROUND) Make the Controllable move to a given point. --- @param #CONTROLLABLE self --- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:RouteToVec3( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllableVec3 = self:GetUnit( 1 ):GetVec3() - - local PointFrom = {} - PointFrom.x = ControllableVec3.x - PointFrom.y = ControllableVec3.z - PointFrom.alt = ControllableVec3.y - PointFrom.alt_type = "BARO" - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.z - PointTo.alt = Point.y - PointTo.alt_type = "BARO" - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - - - ---- Make the controllable to follow a given route. --- @param #CONTROLLABLE self --- @param #table GoPoints A table of Route Points. --- @return #CONTROLLABLE self -function CONTROLLABLE:Route( GoPoints ) - self:F2( GoPoints ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Points = routines.utils.deepCopy( GoPoints ) - local MissionTask = { id = 'Mission', params = { route = { points = Points, }, }, } - local Controller = self:_GetController() - --Controller.setTask( Controller, MissionTask ) - self.TaskScheduler:Schedule( Controller, Controller.setTask, { MissionTask }, 1 ) - return self - end - - return nil -end - - - ---- (AIR + GROUND) Route the controllable to a given zone. --- The controllable final destination point can be randomized. --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Core.Zone#ZONE Zone The zone where to route to. --- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. --- @param #number Speed The speed. --- @param Base#FORMATION Formation The formation string. -function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) - self:F2( Zone ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = Formation or "Cone" - PointFrom.speed = 20 / 1.6 - - - local PointTo = {} - local ZonePoint - - if Randomize then - ZonePoint = Zone:GetRandomVec2() - else - ZonePoint = Zone:GetVec2() - end - - PointTo.x = ZonePoint.x - PointTo.y = ZonePoint.y - PointTo.type = "Turning Point" - - if Formation then - PointTo.action = Formation - else - PointTo.action = "Cone" - end - - if Speed then - PointTo.speed = Speed - else - PointTo.speed = 20 / 1.6 - end - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self - end - - return nil -end - ---- (AIR) Return the Controllable to an @{Airbase#AIRBASE} --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Wrapper.Airbase#AIRBASE ReturnAirbase The @{Airbase#AIRBASE} to return to. --- @param #number Speed (optional) The speed. --- @return #string The route -function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) - self:F2( { ReturnAirbase, Speed } ) - --- Example --- [4] = --- { --- ["alt"] = 45, --- ["type"] = "Land", --- ["action"] = "Landing", --- ["alt_type"] = "BARO", --- ["formation_template"] = "", --- ["properties"] = --- { --- ["vnav"] = 1, --- ["scale"] = 0, --- ["angle"] = 0, --- ["vangle"] = 0, --- ["steer"] = 2, --- }, -- end of ["properties"] --- ["ETA"] = 527.81058817743, --- ["airdromeId"] = 12, --- ["y"] = 243127.2973737, --- ["x"] = -5406.2803440839, --- ["name"] = "DictKey_WptName_53", --- ["speed"] = 138.88888888889, --- ["ETA_locked"] = false, --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] --- ["speed_locked"] = true, --- }, -- end of [4] - - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - local ControllableVelocity = self:GetMaxVelocity() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = ControllableVelocity - - - local PointTo = {} - local AirbasePoint = ReturnAirbase:GetVec2() - - PointTo.x = AirbasePoint.x - PointTo.y = AirbasePoint.y - PointTo.type = "Land" - PointTo.action = "Landing" - PointTo.airdromeId = ReturnAirbase:GetID()-- Airdrome ID - self:T(PointTo.airdromeId) - --PointTo.alt = 0 - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - local Route = { points = Points, } - - return Route - end - - return nil -end - --- Commands - ---- Do Script command --- @param #CONTROLLABLE self --- @param #string DoScript --- @return #DCSCommand -function CONTROLLABLE:CommandDoScript( DoScript ) - - local DCSDoScript = { - id = "Script", - params = { - command = DoScript, - }, - } - - self:T3( DCSDoScript ) - return DCSDoScript -end - - ---- Return the mission template of the controllable. --- @param #CONTROLLABLE self --- @return #table The MissionTemplate --- TODO: Rework the method how to retrieve a template ... -function CONTROLLABLE:GetTaskMission() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template ) -end - ---- Return the mission route of the controllable. --- @param #CONTROLLABLE self --- @return #table The mission route defined by points. -function CONTROLLABLE:GetTaskRoute() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) -end - ---- Return the route of a controllable by using the @{Database#DATABASE} class. --- @param #CONTROLLABLE self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Controllable - local ControllableName = string.match( self:GetName(), ".*#" ) - if ControllableName then - ControllableName = ControllableName:sub( 1, -2 ) - else - ControllableName = self:GetName() - end - - self:T3( { ControllableName } ) - - local Template = _DATABASE.Templates.Controllables[ControllableName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Controllable : " .. ControllableName ) - end - - return nil -end - - ---- Return the detected targets of the controllable. --- The optional parametes specify the detection methods that can be applied. --- If no detection method is given, the detection will use all the available methods by default. --- @param Wrapper.Controllable#CONTROLLABLE self --- @param #boolean DetectVisual (optional) --- @param #boolean DetectOptical (optional) --- @param #boolean DetectRadar (optional) --- @param #boolean DetectIRST (optional) --- @param #boolean DetectRWR (optional) --- @param #boolean DetectDLINK (optional) --- @return #table DetectedTargets -function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local DetectionVisual = ( DetectVisual and DetectVisual == true ) and Controller.Detection.VISUAL or nil - local DetectionOptical = ( DetectOptical and DetectOptical == true ) and Controller.Detection.OPTICAL or nil - local DetectionRadar = ( DetectRadar and DetectRadar == true ) and Controller.Detection.RADAR or nil - local DetectionIRST = ( DetectIRST and DetectIRST == true ) and Controller.Detection.IRST or nil - local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil - local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil - - - return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) - end - - return nil -end - -function CONTROLLABLE:IsTargetDetected( DCSObject ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - = self:_GetController().isTargetDetected( self:_GetController(), DCSObject, - Controller.Detection.VISUAL, - Controller.Detection.OPTIC, - Controller.Detection.RADAR, - Controller.Detection.IRST, - Controller.Detection.RWR, - Controller.Detection.DLINK - ) - return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - end - - return nil -end - --- Options - ---- Can the CONTROLLABLE hold their weapons? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEHoldFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Holding weapons. --- @param Wrapper.Controllable#CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self -function CONTROLLABLE:OptionROEHoldFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack returning on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEReturnFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Return fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEReturnFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack designated targets? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEOpenFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Openfire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEOpenFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack targets of opportunity? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEWeaponFreePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Weapon free. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEWeaponFree() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE ignore enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTNoReactionPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- No evasion on enemy threats. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTNoReaction() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade using passive defenses? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTPassiveDefensePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Evasion passive defense. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTPassiveDefense() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTEvadeFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTEvadeFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on fire using vertical manoeuvres? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTVerticalPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire using vertical manoeuvres. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTVertical() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) - end - - return self - end - - return nil -end - ---- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. --- Use the method @{Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. --- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. --- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! --- @param #CONTROLLABLE self --- @param #table WayPoints If WayPoints is given, then use the route. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointInitialize( WayPoints ) - self:F( { WayPoints } ) - - if WayPoints then - self.WayPoints = WayPoints - else - self.WayPoints = self:GetTaskRoute() - end - - return self -end - ---- Get the current WayPoints set with the WayPoint functions( Note that the WayPoints can be nil, although there ARE waypoints). --- @param #CONTROLLABLE self --- @return #table WayPoints If WayPoints is given, then return the WayPoints structure. -function CONTROLLABLE:GetWayPoints() - self:F( ) - - if self.WayPoints then - return self.WayPoints - end - - return nil -end - ---- Registers a waypoint function that will be executed when the controllable moves over the WayPoint. --- @param #CONTROLLABLE self --- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1! --- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. --- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) - self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) - - table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex ) - self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPoint, WayPointIndex, WayPointFunction, arg ) - return self -end - - -function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionArguments ) - self:F2( { WayPoint, WayPointIndex, FunctionString, FunctionArguments } ) - - local DCSTask - - local DCSScript = {} - DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " - - if FunctionArguments and #FunctionArguments > 0 then - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")" - else - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" - end - - DCSTask = self:TaskWrappedAction( - self:CommandDoScript( - table.concat( DCSScript ) - ), WayPointIndex - ) - - self:T3( DCSTask ) - - return DCSTask - -end - ---- Executes the WayPoint plan. --- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. --- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! --- @param #CONTROLLABLE self --- @param #number WayPoint The WayPoint from where to execute the mission. --- @param #number WaitTime The amount seconds to wait before initiating the mission. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) - self:F( { WayPoint, WaitTime } ) - - if not WayPoint then - WayPoint = 1 - end - - -- When starting the mission from a certain point, the TaskPoints need to be deleted before the given WayPoint. - for TaskPointID = 1, WayPoint - 1 do - table.remove( self.WayPoints, 1 ) - end - - self:T3( self.WayPoints ) - - self:SetTask( self:TaskRoute( self.WayPoints ), WaitTime ) - - return self -end - --- Message APIs--- This module contains the GROUP class. --- --- 1) @{Group#GROUP} class, extends @{Controllable#CONTROLLABLE} --- ============================================================= --- The @{Group#GROUP} class is a wrapper class to handle the DCS Group objects: --- --- * Support all DCS Group APIs. --- * Enhance with Group specific APIs not in the DCS Group API set. --- * Handle local Group Controller. --- * Manage the "state" of the DCS Group. --- --- **IMPORTANT: ONE SHOULD NEVER SANATIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).** --- --- 1.1) GROUP reference methods --- ----------------------- --- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class). --- --- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Group or the DCS GroupName. --- --- Another thing to know is that GROUP objects do not "contain" the DCS Group object. --- The GROUP methods will reference the DCS Group object by name when it is needed during API execution. --- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and log an exception in the DCS.log file. --- --- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance: --- --- * @{#GROUP.Find}(): Find a GROUP instance from the _DATABASE object using a DCS Group object. --- * @{#GROUP.FindByName}(): Find a GROUP instance from the _DATABASE object using a DCS Group name. --- --- ## 1.2) GROUP task methods --- --- A GROUP is a @{Controllable}. See the @{Controllable} task methods section for a description of the task methods. --- --- ### 1.2.4) Obtain the mission from group templates --- --- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: --- --- * @{Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- ## 1.3) GROUP Command methods --- --- A GROUP is a @{Controllable}. See the @{Controllable} command methods section for a description of the command methods. --- --- ## 1.4) GROUP option methods --- --- A GROUP is a @{Controllable}. See the @{Controllable} option methods section for a description of the option methods. --- --- ## 1.5) GROUP Zone validation methods --- --- The group can be validated whether it is completely, partly or not within a @{Zone}. --- Use the following Zone validation methods on the group: --- --- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Zone}. --- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. --- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. --- --- The zone can be of any @{Zone} class derived from @{Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. --- --- ## 1.6) GROUP AI methods --- --- A GROUP has AI methods to control the AI activation. --- --- * @{#GROUP.SetAIOnOff}(): Turns the GROUP AI On or Off. --- * @{#GROUP.SetAIOn}(): Turns the GROUP AI On. --- * @{#GROUP.SetAIOff}(): Turns the GROUP AI Off. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-07: GROUP:**HandleEvent( Event, EventFunction )** added. --- 2017-03-07: GROUP:**UnHandleEvent( Event )** added. --- --- 2017-01-24: GROUP:**SetAIOnOff( AIOnOff )** added. --- --- 2017-01-24: GROUP:**SetAIOn()** added. --- --- 2017-01-24: GROUP:**SetAIOff()** added. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff(). --- --- ### Authors: --- --- * **FlightControl**: Design & Programming --- --- @module Group --- @author FlightControl - ---- The GROUP class --- @type GROUP --- @extends Wrapper.Controllable#CONTROLLABLE --- @field #string GroupName The name of the group. -GROUP = { - ClassName = "GROUP", -} - ---- Create a new GROUP from a DCSGroup --- @param #GROUP self --- @param Dcs.DCSWrapper.Group#Group GroupName The DCS Group name --- @return #GROUP self -function GROUP:Register( GroupName ) - self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) - self:F2( GroupName ) - self.GroupName = GroupName - - self:SetEventPriority( 4 ) - return self -end - --- Reference methods. - ---- Find the GROUP wrapper class instance using the DCS Group. --- @param #GROUP self --- @param Dcs.DCSWrapper.Group#Group DCSGroup The DCS Group. --- @return #GROUP The GROUP. -function GROUP:Find( DCSGroup ) - - local GroupName = DCSGroup:getName() -- Wrapper.Group#GROUP - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - ---- Find the created GROUP using the DCS Group Name. --- @param #GROUP self --- @param #string GroupName The DCS Group Name. --- @return #GROUP The GROUP. -function GROUP:FindByName( GroupName ) - - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - --- DCS Group methods support. - ---- Returns the DCS Group. --- @param #GROUP self --- @return Dcs.DCSWrapper.Group#Group The DCS Group. -function GROUP:GetDCSObject() - local DCSGroup = Group.getByName( self.GroupName ) - - if DCSGroup then - return DCSGroup - end - - return nil -end - ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePosition = DCSPositionable:getUnits()[1]:getPosition().p - self:T3( PositionablePosition ) - return PositionablePosition - end - - return nil -end - ---- Returns if the DCS Group is alive. --- When the group exists at run-time, this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean true if the DCS Group is alive. -function GROUP:IsAlive() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupIsAlive = DCSGroup:isExist() and DCSGroup:getUnit(1) ~= nil - self:T3( GroupIsAlive ) - return GroupIsAlive - end - - return nil -end - ---- Destroys the DCS Group and all of its DCS Units. --- Note that this destroy method also raises a destroy event at run-time. --- So all event listeners will catch the destroy event of this DCS Group. --- @param #GROUP self -function GROUP:Destroy() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - self:CreateEventCrash( timer.getTime(), UnitData ) - end - DCSGroup:destroy() - DCSGroup = nil - end - - return nil -end - ---- Returns category of the DCS Group. --- @param #GROUP self --- @return Dcs.DCSWrapper.Group#Group.Category The category ID -function GROUP:GetCategory() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - return GroupCategory - end - - return nil -end - ---- Returns the category name of the #GROUP. --- @param #GROUP self --- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship -function GROUP:GetCategoryName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local CategoryNames = { - [Group.Category.AIRPLANE] = "Airplane", - [Group.Category.HELICOPTER] = "Helicopter", - [Group.Category.GROUND] = "Ground Unit", - [Group.Category.SHIP] = "Ship", - } - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - - return CategoryNames[GroupCategory] - end - - return nil -end - - ---- Returns the coalition of the DCS Group. --- @param #GROUP self --- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The coalition side of the DCS Group. -function GROUP:GetCoalition() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCoalition = DCSGroup:getCoalition() - self:T3( GroupCoalition ) - return GroupCoalition - end - - return nil -end - ---- Returns the country of the DCS Group. --- @param #GROUP self --- @return Dcs.DCScountry#country.id The country identifier. --- @return #nil The DCS Group is not existing or alive. -function GROUP:GetCountry() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCountry = DCSGroup:getUnit(1):getCountry() - self:T3( GroupCountry ) - return GroupCountry - end - - return nil -end - ---- Returns the UNIT wrapper class with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the UNIT wrapper class to be returned. --- @return Wrapper.Unit#UNIT The UNIT wrapper class. -function GROUP:GetUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) - self:T2( UnitFound ) - return UnitFound - end - - return nil -end - ---- Returns the DCS Unit with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the DCS Unit to be returned. --- @return Dcs.DCSWrapper.Unit#Unit The DCS Unit. -function GROUP:GetDCSUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) - self:T3( DCSUnitFound ) - return DCSUnitFound - end - - return nil -end - ---- Returns current size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed the size of the DCS Group is changed. --- @param #GROUP self --- @return #number The DCS Group size. -function GROUP:GetSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupSize = DCSGroup:getSize() - self:T3( GroupSize ) - return GroupSize - end - - return nil -end - ---- ---- Returns the initial size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged. --- @param #GROUP self --- @return #number The DCS Group initial size. -function GROUP:GetInitialSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupInitialSize = DCSGroup:getInitialSize() - self:T3( GroupInitialSize ) - return GroupInitialSize - end - - return nil -end - - ---- Returns the DCS Units of the DCS Group. --- @param #GROUP self --- @return #table The DCS Units. -function GROUP:GetDCSUnits() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnits = DCSGroup:getUnits() - self:T3( DCSUnits ) - return DCSUnits - end - - return nil -end - - ---- Activates a GROUP. --- @param #GROUP self -function GROUP:Activate() - self:F2( { self.GroupName } ) - trigger.action.activateGroup( self:GetDCSObject() ) - return self:GetDCSObject() -end - - ---- Gets the type name of the group. --- @param #GROUP self --- @return #string The type name of the group. -function GROUP:GetTypeName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupTypeName = DCSGroup:getUnit(1):getTypeName() - self:T3( GroupTypeName ) - return( GroupTypeName ) - end - - return nil -end - ---- Gets the CallSign of the first DCS Unit of the DCS Group. --- @param #GROUP self --- @return #string The CallSign of the first DCS Unit of the DCS Group. -function GROUP:GetCallsign() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCallSign = DCSGroup:getUnit(1):getCallsign() - self:T3( GroupCallSign ) - return GroupCallSign - end - - return nil -end - ---- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. --- @param #GROUP self --- @return Dcs.DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. -function GROUP:GetVec2() - self:F2( self.GroupName ) - - local UnitPoint = self:GetUnit(1) - UnitPoint:GetVec2() - local GroupPointVec2 = UnitPoint:GetVec2() - self:T3( GroupPointVec2 ) - return GroupPointVec2 -end - ---- Returns the current Vec3 vector of the first DCS Unit in the GROUP. --- @return Dcs.DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. -function GROUP:GetVec3() - self:F2( self.GroupName ) - - local GroupVec3 = self:GetUnit(1):GetVec3() - self:T3( GroupVec3 ) - return GroupVec3 -end - - - -do -- Is Zone methods - ---- Returns true if all units of the group are within a @{Zone}. --- @param #GROUP self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsCompletelyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - if Zone:IsVec3InZone( Unit:GetVec3() ) then - else - return false - end - end - - return true -end - ---- Returns true if some units of the group are within a @{Zone}. --- @param #GROUP self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsPartlyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - if Zone:IsVec3InZone( Unit:GetVec3() ) then - return true - end - end - - return false -end - ---- Returns true if none of the group units of the group are within a @{Zone}. --- @param #GROUP self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsNotInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - if Zone:IsVec3InZone( Unit:GetVec3() ) then - return false - end - end - - return true -end - ---- Returns if the group is of an air category. --- If the group is a helicopter or a plane, then this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean Air category evaluation result. -function GROUP:IsAir() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the DCS Group contains Helicopters. --- @param #GROUP self --- @return #boolean true if DCS Group contains Helicopters. -function GROUP:IsHelicopter() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.HELICOPTER - end - - return nil -end - ---- Returns if the DCS Group contains AirPlanes. --- @param #GROUP self --- @return #boolean true if DCS Group contains AirPlanes. -function GROUP:IsAirPlane() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.AIRPLANE - end - - return nil -end - ---- Returns if the DCS Group contains Ground troops. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ground troops. -function GROUP:IsGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.GROUND - end - - return nil -end - ---- Returns if the DCS Group contains Ships. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ships. -function GROUP:IsShip() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.SHIP - end - - return nil -end - ---- Returns if all units of the group are on the ground or landed. --- If all units of this group are on the ground, this function will return true, otherwise false. --- @param #GROUP self --- @return #boolean All units on the ground result. -function GROUP:AllOnGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local AllOnGroundResult = true - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - if UnitData:inAir() then - AllOnGroundResult = false - end - end - - self:T3( AllOnGroundResult ) - return AllOnGroundResult - end - - return nil -end - -end - -do -- AI methods - - --- Turns the AI On or Off for the GROUP. - -- @param #GROUP self - -- @param #boolean AIOnOff The value true turns the AI On, the value false turns the AI Off. - -- @return #GROUP The GROUP. - function GROUP:SetAIOnOff( AIOnOff ) - - local DCSGroup = self:GetDCSObject() -- Dcs.DCSGroup#Group - - if DCSGroup then - local DCSController = DCSGroup:getController() -- Dcs.DCSController#Controller - if DCSController then - DCSController:setOnOff( AIOnOff ) - return self - end - end - - return nil - end - - --- Turns the AI On for the GROUP. - -- @param #GROUP self - -- @return #GROUP The GROUP. - function GROUP:SetAIOn() - - return self:SetAIOnOff( true ) - end - - --- Turns the AI Off for the GROUP. - -- @param #GROUP self - -- @return #GROUP The GROUP. - function GROUP:SetAIOff() - - return self:SetAIOnOff( false ) - end - -end - - - ---- Returns the current maximum velocity of the group. --- Each unit within the group gets evaluated, and the maximum velocity (= the unit which is going the fastest) is returned. --- @param #GROUP self --- @return #number Maximum velocity found. -function GROUP:GetMaxVelocity() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupVelocityMax = 0 - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - - local UnitVelocityVec3 = UnitData:getVelocity() - local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z ) - - if UnitVelocity > GroupVelocityMax then - GroupVelocityMax = UnitVelocity - end - end - - return GroupVelocityMax - end - - return nil -end - ---- Returns the current minimum height of the group. --- Each unit within the group gets evaluated, and the minimum height (= the unit which is the lowest elevated) is returned. --- @param #GROUP self --- @return #number Minimum height found. -function GROUP:GetMinHeight() - self:F2() - -end - ---- Returns the current maximum height of the group. --- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned. --- @param #GROUP self --- @return #number Maximum height found. -function GROUP:GetMaxHeight() - self:F2() - -end - --- SPAWNING - ---- Respawn the @{GROUP} using a (tweaked) template of the Group. --- The template must be retrieved with the @{Group#GROUP.GetTemplate}() function. --- The template contains all the definitions as declared within the mission file. --- To understand templates, do the following: --- --- * unpack your .miz file into a directory using 7-zip. --- * browse in the directory created to the file **mission**. --- * open the file and search for the country group definitions. --- --- Your group template will contain the fields as described within the mission file. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the group is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will destroy the current alive group. --- * And it will respawn the group using your new template definition. --- @param Wrapper.Group#GROUP self --- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() -function GROUP:Respawn( Template ) - - local Vec3 = self:GetVec3() - Template.x = Vec3.x - Template.y = Vec3.z - --Template.x = nil - --Template.y = nil - - self:E( #Template.units ) - for UnitID, UnitData in pairs( self:GetUnits() ) do - local GroupUnit = UnitData -- Wrapper.Unit#UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - Template.units[UnitID].alt = GroupUnitVec3.y - Template.units[UnitID].x = GroupUnitVec3.x - Template.units[UnitID].y = GroupUnitVec3.z - Template.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) - end - end - - self:Destroy() - _DATABASE:Spawn( Template ) -end - ---- Returns the group template from the @{DATABASE} (_DATABASE object). --- @param #GROUP self --- @return #table -function GROUP:GetTemplate() - local GroupName = self:GetName() - self:E( GroupName ) - return _DATABASE:GetGroupTemplate( GroupName ) -end - ---- Sets the controlled status in a Template. --- @param #GROUP self --- @param #boolean Controlled true is controlled, false is uncontrolled. --- @return #table -function GROUP:SetTemplateControlled( Template, Controlled ) - Template.uncontrolled = not Controlled - return Template -end - ---- Sets the CountryID of the group in a Template. --- @param #GROUP self --- @param Dcs.DCScountry#country.id CountryID The country ID. --- @return #table -function GROUP:SetTemplateCountry( Template, CountryID ) - Template.CountryID = CountryID - return Template -end - ---- Sets the CoalitionID of the group in a Template. --- @param #GROUP self --- @param Dcs.DCSCoalitionWrapper.Object#coalition.side CoalitionID The coalition ID. --- @return #table -function GROUP:SetTemplateCoalition( Template, CoalitionID ) - Template.CoalitionID = CoalitionID - return Template -end - - - - ---- Return the mission template of the group. --- @param #GROUP self --- @return #table The MissionTemplate -function GROUP:GetTaskMission() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) -end - ---- Return the mission route of the group. --- @param #GROUP self --- @return #table The mission route defined by points. -function GROUP:GetTaskRoute() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) -end - ---- Return the route of a group by using the @{Database#DATABASE} class. --- @param #GROUP self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function GROUP:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Group - local GroupName = string.match( self:GetName(), ".*#" ) - if GroupName then - GroupName = GroupName:sub( 1, -2 ) - else - GroupName = self:GetName() - end - - self:T3( { GroupName } ) - - local Template = _DATABASE.Templates.Groups[GroupName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Group : " .. GroupName ) - end - - return nil -end - ---- Calculate the maxium A2G threat level of the Group. --- @param #GROUP self -function GROUP:CalculateThreatLevelA2G() - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( self:GetUnits() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - return MaxThreatLevelA2G -end - ---- Returns true if the first unit of the GROUP is in the air. --- @param Wrapper.Group#GROUP self --- @return #boolean true if in the first unit of the group is in the air. --- @return #nil The GROUP is not existing or not alive. -function GROUP:InAir() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnit = DCSGroup:getUnit(1) - if DCSUnit then - local GroupInAir = DCSGroup:getUnit(1):inAir() - self:T3( GroupInAir ) - return GroupInAir - end - end - - return nil -end - -function GROUP:OnReSpawn( ReSpawnFunction ) - - self.ReSpawnFunction = ReSpawnFunction -end - -do -- Event Handling - - --- Subscribe to a DCS Event. - -- @param #GROUP self - -- @param Core.Event#EVENTS Event - -- @param #function EventFunction (optional) The function to be called when the event occurs for the GROUP. - -- @return #GROUP - function GROUP:HandleEvent( Event, EventFunction ) - - self:EventDispatcher():OnEventForGroup( self:GetName(), EventFunction, self, Event ) - - return self - end - - --- UnSubscribe to a DCS event. - -- @param #GROUP self - -- @param Core.Event#EVENTS Event - -- @return #GROUP - function GROUP:UnHandleEvent( Event ) - - self:EventDispatcher():RemoveForGroup( self:GetName(), self, Event ) - - return self - end - -end - -do -- Players - - --- Get player names - -- @param #GROUP self - -- @return #table The group has players, an array of player names is returned. - -- @return #nil The group has no players - function GROUP:GetPlayerNames() - - local PlayerNames = nil - - local Units = self:GetUnits() - for UnitID, UnitData in pairs( Units ) do - local Unit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = Unit:GetPlayerName() - if PlayerName and PlayerName ~= "" then - PlayerNames = PlayerNames or {} - table.insert( PlayerNames, PlayerName ) - end - end - - self:F( PlayerNames ) - return PlayerNames - end - -end--- This module contains the UNIT class. --- --- 1) @{#UNIT} class, extends @{Controllable#CONTROLLABLE} --- =========================================================== --- The @{#UNIT} class is a wrapper class to handle the DCS Unit objects: --- --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Unit API set. --- * Handle local Unit Controller. --- * Manage the "state" of the DCS Unit. --- --- --- 1.1) UNIT reference methods --- ---------------------- --- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Unit objects are spawned (using the @{SPAWN} class). --- --- The UNIT class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that UNIT objects do not "contain" the DCS Unit object. --- The UNIT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the UNIT methods will return nil and log an exception in the DCS.log file. --- --- The UNIT class provides the following functions to retrieve quickly the relevant UNIT instance: --- --- * @{#UNIT.Find}(): Find a UNIT instance from the _DATABASE object using a DCS Unit object. --- * @{#UNIT.FindByName}(): Find a UNIT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these UNIT OBJECT REFERENCES! (make the UNIT object references nil). --- --- 1.2) DCS UNIT APIs --- ------------------ --- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. --- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, --- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{DCSWrapper.Unit#Unit.getName}() --- is implemented in the UNIT class as @{#UNIT.GetName}(). --- --- 1.3) Smoke, Flare Units --- ----------------------- --- The UNIT class provides methods to smoke or flare units easily. --- The @{#UNIT.SmokeBlue}(), @{#UNIT.SmokeGreen}(),@{#UNIT.SmokeOrange}(), @{#UNIT.SmokeRed}(), @{#UNIT.SmokeRed}() methods --- will smoke the unit in the corresponding color. Note that smoking a unit is done at the current position of the DCS Unit. --- When the DCS Unit moves for whatever reason, the smoking will still continue! --- The @{#UNIT.FlareGreen}(), @{#UNIT.FlareRed}(), @{#UNIT.FlareWhite}(), @{#UNIT.FlareYellow}() --- methods will fire off a flare in the air with the corresponding color. Note that a flare is a one-off shot and its effect is of very short duration. --- --- 1.4) Location Position, Point --- ----------------------------- --- The UNIT class provides methods to obtain the current point or position of the DCS Unit. --- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. --- --- 1.5) Test if alive --- ------------------ --- The @{#UNIT.IsAlive}(), @{#UNIT.IsActive}() methods determines if the DCS Unit is alive, meaning, it is existing and active. --- --- 1.6) Test for proximity --- ----------------------- --- The UNIT class contains methods to test the location or proximity against zones or other objects. --- --- ### 1.6.1) Zones --- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Zone#ZONE_BASE}. --- --- ### 1.6.2) Units --- Test if another DCS Unit is within a given radius of the current DCS Unit, use the @{#UNIT.OtherUnitInRadius}() method. --- --- @module Unit --- @author FlightControl - - - - - ---- The UNIT class --- @type UNIT --- @extends Wrapper.Controllable#CONTROLLABLE -UNIT = { - ClassName="UNIT", -} - - ---- Unit.SensorType --- @type Unit.SensorType --- @field OPTIC --- @field RADAR --- @field IRST --- @field RWR - - --- Registration. - ---- Create a new UNIT from DCSUnit. --- @param #UNIT self --- @param #string UnitName The name of the DCS unit. --- @return #UNIT -function UNIT:Register( UnitName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) - self.UnitName = UnitName - - self:SetEventPriority( 3 ) - return self -end - --- Reference methods. - ---- Finds a UNIT from the _DATABASE using a DCSUnit object. --- @param #UNIT self --- @param Dcs.DCSWrapper.Unit#Unit DCSUnit An existing DCS Unit object reference. --- @return #UNIT self -function UNIT:Find( DCSUnit ) - - local UnitName = DCSUnit:getName() - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. --- @param #UNIT self --- @param #string UnitName The Unit Name. --- @return #UNIT self -function UNIT:FindByName( UnitName ) - - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Return the name of the UNIT. --- @param #UNIT self --- @return #string The UNIT name. -function UNIT:Name() - - return self.UnitName -end - - ---- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit -function UNIT:GetDCSObject() - - local DCSUnit = Unit.getByName( self.UnitName ) - - if DCSUnit then - return DCSUnit - end - - return nil -end - ---- Respawn the @{Unit} using a (tweaked) template of the parent Group. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the unit is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will respawn the re-modelled group. --- --- @param #UNIT self --- @param Dcs.DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. --- @param #number Heading The heading of the unit respawn. -function UNIT:ReSpawn( SpawnVec3, Heading ) - - local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) - self:T( SpawnGroupTemplate ) - - local SpawnGroup = self:GetGroup() - - if SpawnGroup then - - local Vec3 = SpawnGroup:GetVec3() - SpawnGroupTemplate.x = SpawnVec3.x - SpawnGroupTemplate.y = SpawnVec3.z - - self:E( #SpawnGroupTemplate.units ) - for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do - local GroupUnit = UnitData -- #UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y - SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x - SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z - SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) - end - end - end - - for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do - self:T( UnitTemplateData.name ) - if UnitTemplateData.name == self:Name() then - self:T("Adjusting") - SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y - SpawnGroupTemplate.units[UnitTemplateID].x = SpawnVec3.x - SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z - SpawnGroupTemplate.units[UnitTemplateID].heading = Heading - self:E( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) - else - self:E( SpawnGroupTemplate.units[UnitTemplateID].name ) - local GroupUnit = UNIT:FindByName( SpawnGroupTemplate.units[UnitTemplateID].name ) -- #UNIT - if GroupUnit and GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - UnitTemplateData.alt = GroupUnitVec3.y - UnitTemplateData.x = GroupUnitVec3.x - UnitTemplateData.y = GroupUnitVec3.z - UnitTemplateData.heading = GroupUnitHeading - else - if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then - self:T("nilling") - SpawnGroupTemplate.units[UnitTemplateID].delete = true - end - end - end - end - - -- Remove obscolete units from the group structure - i = 1 - while i <= #SpawnGroupTemplate.units do - - local UnitTemplateData = SpawnGroupTemplate.units[i] - self:T( UnitTemplateData.name ) - - if UnitTemplateData.delete then - table.remove( SpawnGroupTemplate.units, i ) - else - i = i + 1 - end - end - - _DATABASE:Spawn( SpawnGroupTemplate ) -end - - - ---- Returns if the unit is activated. --- @param #UNIT self --- @return #boolean true if Unit is activated. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:IsActive() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local UnitIsActive = DCSUnit:isActive() - return UnitIsActive - end - - return nil -end - - - ---- Returns the Unit's callsign - the localized string. --- @param #UNIT self --- @return #string The Callsign of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetCallsign() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCallSign = DCSUnit:getCallsign() - return UnitCallSign - end - - self:E( self.ClassName .. " " .. self.UnitName .. " not found!" ) - return nil -end - - ---- Returns name of the player that control the unit or nil if the unit is controlled by A.I. --- @param #UNIT self --- @return #string Player Name --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPlayerName() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local PlayerName = DCSUnit:getPlayerName() - if PlayerName == nil then - PlayerName = "" - end - return PlayerName - end - - return nil -end - ---- Returns the unit's number in the group. --- The number is the same number the unit has in ME. --- It may not be changed during the mission. --- If any unit in the group is destroyed, the numbers of another units will not be changed. --- @param #UNIT self --- @return #number The Unit number. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetNumber() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitNumber = DCSUnit:getNumber() - return UnitNumber - end - - return nil -end - ---- Returns the unit's group if it exist and nil otherwise. --- @param Wrapper.Unit#UNIT self --- @return Wrapper.Group#GROUP The Group of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetGroup() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) - return UnitGroup - end - - return nil -end - - --- Need to add here functions to check if radar is on and which object etc. - ---- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. --- DCS Units spawned with the @{SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. --- The spawn sequence number and unit number are contained within the name after the '#' sign. --- @param #UNIT self --- @return #string The name of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPrefix() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) - self:T3( UnitPrefix ) - return UnitPrefix - end - - return nil -end - ---- Returns the Unit's ammunition. --- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit.Ammo --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetAmmo() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitAmmo = DCSUnit:getAmmo() - return UnitAmmo - end - - return nil -end - ---- Returns the unit sensors. --- @param #UNIT self --- @return Dcs.DCSWrapper.Unit#Unit.Sensors --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetSensors() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSensors = DCSUnit:getSensors() - return UnitSensors - end - - return nil -end - --- Need to add here a function per sensortype --- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) - ---- Returns if the unit has sensors of a certain type. --- @param #UNIT self --- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSensors( ... ) - self:F2( arg ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) - return HasSensors - end - - return nil -end - ---- Returns if the unit is SEADable. --- @param #UNIT self --- @return #boolean returns true if the unit is SEADable. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSEAD() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSEADAttributes = DCSUnit:getDesc().attributes - - local HasSEAD = false - if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or - UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then - HasSEAD = true - end - return HasSEAD - end - - return nil -end - ---- Returns two values: --- --- * First value indicates if at least one of the unit's radar(s) is on. --- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @param #UNIT self --- @return #boolean Indicates if at least one of the unit's radar(s) is on. --- @return Dcs.DCSWrapper.Object#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetRadar() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() - return UnitRadarOn, UnitRadarObject - end - - return nil, nil -end - ---- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. --- @param #UNIT self --- @return #number The relative amount of fuel (from 0.0 to 1.0). --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetFuel() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitFuel = DCSUnit:getFuel() - return UnitFuel - end - - return nil -end - ---- Returns the UNIT in a UNIT list of one element. --- @param #UNIT self --- @return #list The UNITs wrappers. -function UNIT:GetUnits() - self:F2( { self.UnitName } ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local DCSUnits = DCSUnit:getUnits() - local Units = {} - Units[1] = UNIT:Find( DCSUnit ) - self:T3( Units ) - return Units - end - - return nil -end - - ---- Returns the unit's health. Dead units has health <= 1.0. --- @param #UNIT self --- @return #number The Unit's health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife = DCSUnit:getLife() - return UnitLife - end - - return nil -end - ---- Returns the Unit's initial health. --- @param #UNIT self --- @return #number The Unit's initial health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife0() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife0 = DCSUnit:getLife0() - return UnitLife0 - end - - return nil -end - ---- Returns the category name of the #UNIT. --- @param #UNIT self --- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship -function UNIT:GetCategoryName() - self:F3( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - if DCSUnit then - local CategoryNames = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicopter", - [Unit.Category.GROUND_UNIT] = "Ground Unit", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - local UnitCategory = DCSUnit:getDesc().category - self:T3( UnitCategory ) - - return CategoryNames[UnitCategory] - end - - return nil -end - - ---- Returns the Unit's A2G threat level on a scale from 1 to 10 ... --- The following threat levels are foreseen: --- --- * Threat level 0: Unit is unarmed. --- * Threat level 1: Unit is infantry. --- * Threat level 2: Unit is an infantry vehicle. --- * Threat level 3: Unit is ground artillery. --- * Threat level 4: Unit is a tank. --- * Threat level 5: Unit is a modern tank or ifv with ATGM. --- * Threat level 6: Unit is a AAA. --- * Threat level 7: Unit is a SAM or manpad, IR guided. --- * Threat level 8: Unit is a Short Range SAM, radar guided. --- * Threat level 9: Unit is a Medium Range SAM, radar guided. --- * Threat level 10: Unit is a Long Range SAM, radar guided. --- @param #UNIT self -function UNIT:GetThreatLevel() - - local Attributes = self:GetDesc().attributes - self:T( Attributes ) - - local ThreatLevel = 0 - local ThreatText = "" - - if self:IsGround() then - - self:T( "Ground" ) - - local ThreatLevels = { - "Unarmed", - "Infantry", - "Old Tanks & APCs", - "Tanks & IFVs without ATGM", - "Tanks & IFV with ATGM", - "Modern Tanks", - "AAA", - "IR Guided SAMs", - "SR SAMs", - "MR SAMs", - "LR SAMs" - } - - - if Attributes["LR SAM"] then ThreatLevel = 10 - elseif Attributes["MR SAM"] then ThreatLevel = 9 - elseif Attributes["SR SAM"] and - not Attributes["IR Guided SAM"] then ThreatLevel = 8 - elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and - Attributes["IR Guided SAM"] then ThreatLevel = 7 - elseif Attributes["AAA"] then ThreatLevel = 6 - elseif Attributes["Modern Tanks"] then ThreatLevel = 5 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - Attributes["ATGM"] then ThreatLevel = 4 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - not Attributes["ATGM"] then ThreatLevel = 3 - elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then ThreatLevel = 2 - elseif Attributes["Infantry"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - if self:IsAir() then - - self:T( "Air" ) - - local ThreatLevels = { - "Unarmed", - "Tanker", - "AWACS", - "Transport Helicpter", - "UAV", - "Bomber", - "Strategic Bomber", - "Attack Helicopter", - "Interceptor", - "Multirole Fighter", - "Fighter" - } - - - if Attributes["Fighters"] then ThreatLevel = 10 - elseif Attributes["Multirole fighters"] then ThreatLevel = 9 - elseif Attributes["Battleplanes"] then ThreatLevel = 8 - elseif Attributes["Attack helicopters"] then ThreatLevel = 7 - elseif Attributes["Strategic bombers"] then ThreatLevel = 6 - elseif Attributes["Bombers"] then ThreatLevel = 5 - elseif Attributes["UAVs"] then ThreatLevel = 4 - elseif Attributes["Transport helicopters"] then ThreatLevel = 3 - elseif Attributes["AWACS"] then ThreatLevel = 2 - elseif Attributes["Tankers"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - if self:IsShip() then - - self:T( "Ship" ) - ---["Aircraft Carriers"] = {"Heavy armed ships",}, ---["Cruisers"] = {"Heavy armed ships",}, ---["Destroyers"] = {"Heavy armed ships",}, ---["Frigates"] = {"Heavy armed ships",}, ---["Corvettes"] = {"Heavy armed ships",}, ---["Heavy armed ships"] = {"Armed ships", "Armed Air Defence", "HeavyArmoredUnits",}, ---["Light armed ships"] = {"Armed ships","NonArmoredUnits"}, ---["Armed ships"] = {"Ships"}, ---["Unarmed ships"] = {"Ships","HeavyArmoredUnits",}, - - local ThreatLevels = { - "Unarmed ship", - "Light armed ships", - "Corvettes", - "", - "Frigates", - "", - "Cruiser", - "", - "Destroyer", - "", - "Aircraft Carrier" - } - - - if Attributes["Aircraft Carriers"] then ThreatLevel = 10 - elseif Attributes["Destroyers"] then ThreatLevel = 8 - elseif Attributes["Cruisers"] then ThreatLevel = 6 - elseif Attributes["Frigates"] then ThreatLevel = 4 - elseif Attributes["Corvettes"] then ThreatLevel = 2 - elseif Attributes["Light armed ships"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - self:T2( ThreatLevel ) - return ThreatLevel, ThreatText - -end - - --- Is functions - ---- Returns true if the unit is within a @{Zone}. --- @param #UNIT self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is within the @{Zone#ZONE_BASE} -function UNIT:IsInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = Zone:IsVec3InZone( self:GetVec3() ) - - self:T( { IsInZone } ) - return IsInZone - end - - return false -end - ---- Returns true if the unit is not within a @{Zone}. --- @param #UNIT self --- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is not within the @{Zone#ZONE_BASE} -function UNIT:IsNotInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = not Zone:IsVec3InZone( self:GetVec3() ) - - self:T( { IsInZone } ) - return IsInZone - else - return false - end -end - - ---- Returns true if there is an **other** DCS Unit within a radius of the current 2D point of the DCS Unit. --- @param #UNIT self --- @param #UNIT AwaitUnit The other UNIT wrapper object. --- @param Radius The radius in meters with the DCS Unit in the centre. --- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitVec3 = self:GetVec3() - local AwaitUnitVec3 = AwaitUnit:GetVec3() - - if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then - self:T3( "true" ) - return true - else - self:T3( "false" ) - return false - end - end - - return nil -end - - - ---- Signal a flare at the position of the UNIT. --- @param #UNIT self --- @param Utilities.Utils#FLARECOLOR FlareColor -function UNIT:Flare( FlareColor ) - self:F2() - trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) -end - ---- Signal a white flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareWhite() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) -end - ---- Signal a yellow flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareYellow() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) -end - ---- Signal a green flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareGreen() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) -end - ---- Signal a red flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareRed() - self:F2() - local Vec3 = self:GetVec3() - if Vec3 then - trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) - end -end - ---- Smoke the UNIT. --- @param #UNIT self -function UNIT:Smoke( SmokeColor, Range ) - self:F2() - if Range then - trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) - else - trigger.action.smoke( self:GetVec3(), SmokeColor ) - end - -end - ---- Smoke the UNIT Green. --- @param #UNIT self -function UNIT:SmokeGreen() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green ) -end - ---- Smoke the UNIT Red. --- @param #UNIT self -function UNIT:SmokeRed() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red ) -end - ---- Smoke the UNIT White. --- @param #UNIT self -function UNIT:SmokeWhite() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White ) -end - ---- Smoke the UNIT Orange. --- @param #UNIT self -function UNIT:SmokeOrange() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange ) -end - ---- Smoke the UNIT Blue. --- @param #UNIT self -function UNIT:SmokeBlue() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) -end - --- Is methods - ---- Returns if the unit is of an air category. --- If the unit is a helicopter or a plane, then this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Air category evaluation result. -function UNIT:IsAir() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) - - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the unit is of an ground category. --- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ground category evaluation result. -function UNIT:IsGround() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) - - local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) - - self:T3( IsGroundResult ) - return IsGroundResult - end - - return nil -end - ---- Returns if the unit is a friendly unit. --- @param #UNIT self --- @return #boolean IsFriendly evaluation result. -function UNIT:IsFriendly( FriendlyCoalition ) - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCoalition = DCSUnit:getCoalition() - self:T3( { UnitCoalition, FriendlyCoalition } ) - - local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) - - self:E( IsFriendlyResult ) - return IsFriendlyResult - end - - return nil -end - ---- Returns if the unit is of a ship category. --- If the unit is a ship, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ship category evaluation result. -function UNIT:IsShip() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - - local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) - - self:T3( IsShipResult ) - return IsShipResult - end - - return nil -end - ---- Returns true if the UNIT is in the air. --- @param Wrapper.Positionable#UNIT self --- @return #boolean true if in the air. --- @return #nil The UNIT is not existing or alive. -function UNIT:InAir() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitInAir = DCSUnit:inAir() - self:T3( UnitInAir ) - return UnitInAir - end - - return nil -end - -do -- Event Handling - - --- Subscribe to a DCS Event. - -- @param #UNIT self - -- @param Core.Event#EVENTS Event - -- @param #function EventFunction (optional) The function to be called when the event occurs for the unit. - -- @return #UNIT - function UNIT:HandleEvent( Event, EventFunction ) - - self:EventDispatcher():OnEventForUnit( self:GetName(), EventFunction, self, Event ) - - return self - end - - --- UnSubscribe to a DCS event. - -- @param #UNIT self - -- @param Core.Event#EVENTS Event - -- @return #UNIT - function UNIT:UnHandleEvent( Event ) - - self:EventDispatcher():RemoveForUnit( self:GetName(), self, Event ) - - return self - end - -end--- This module contains the CLIENT class. --- --- 1) @{Client#CLIENT} class, extends @{Unit#UNIT} --- =============================================== --- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. --- Note that clients are NOT the same as Units, they are NOT necessarily alive. --- The @{Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: --- --- * Wraps the DCS Unit objects with skill level set to Player or Client. --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Group API set. --- * When player joins Unit, execute alive init logic. --- * Handles messages to players. --- * Manage the "state" of the DCS Unit. --- --- Clients are being used by the @{MISSION} class to follow players and register their successes. --- --- 1.1) CLIENT reference methods --- ----------------------------- --- For each DCS Unit having skill level Player or Client, a CLIENT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The CLIENT class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that CLIENT objects do not "contain" the DCS Unit object. --- The CLIENT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the CLIENT methods will return nil and log an exception in the DCS.log file. --- --- The CLIENT class provides the following functions to retrieve quickly the relevant CLIENT instance: --- --- * @{#CLIENT.Find}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit object. --- * @{#CLIENT.FindByName}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these CLIENT OBJECT REFERENCES! (make the CLIENT object references nil). --- --- @module Client - ---- The CLIENT class --- @type CLIENT --- @extends Wrapper.Unit#UNIT -CLIENT = { - ONBOARDSIDE = { - NONE = 0, - LEFT = 1, - RIGHT = 2, - BACK = 3, - FRONT = 4 - }, - ClassName = "CLIENT", - ClientName = nil, - ClientAlive = false, - ClientTransport = false, - ClientBriefingShown = false, - _Menus = {}, - _Tasks = {}, - Messages = { - } -} - - ---- Finds a CLIENT from the _DATABASE using the relevant DCS Unit. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:Find( DCSUnit ) - local ClientName = DCSUnit:getName() - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( ClientName ) - return ClientFound - end - - error( "CLIENT not found for: " .. ClientName ) -end - - ---- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name. --- As an optional parameter, a briefing text can be given also. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @param #boolean Error A flag that indicates whether an error should be raised if the CLIENT cannot be found. By default an error will be raised. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:FindByName( ClientName, ClientBriefing, Error ) - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( { ClientName, ClientBriefing } ) - ClientFound:AddBriefing( ClientBriefing ) - ClientFound.MessageSwitch = true - - return ClientFound - end - - if not Error then - error( "CLIENT not found for: " .. ClientName ) - end -end - -function CLIENT:Register( ClientName ) - local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) - - self:F( ClientName ) - self.ClientName = ClientName - self.MessageSwitch = true - self.ClientAlive2 = false - - --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) - self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 ) - - self:E( self ) - return self -end - - ---- Transport defines that the Client is a Transport. Transports show cargo. --- @param #CLIENT self --- @return #CLIENT -function CLIENT:Transport() - self:F() - - self.ClientTransport = true - return self -end - ---- AddBriefing adds a briefing to a CLIENT when a player joins a mission. --- @param #CLIENT self --- @param #string ClientBriefing is the text defining the Mission briefing. --- @return #CLIENT self -function CLIENT:AddBriefing( ClientBriefing ) - self:F( ClientBriefing ) - self.ClientBriefing = ClientBriefing - self.ClientBriefingShown = false - - return self -end - ---- Show the briefing of a CLIENT. --- @param #CLIENT self --- @return #CLIENT self -function CLIENT:ShowBriefing() - self:F( { self.ClientName, self.ClientBriefingShown } ) - - if not self.ClientBriefingShown then - self.ClientBriefingShown = true - local Briefing = "" - if self.ClientBriefing then - Briefing = Briefing .. self.ClientBriefing - end - Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing." - self:Message( Briefing, 60, "Briefing" ) - end - - return self -end - ---- Show the mission briefing of a MISSION to the CLIENT. --- @param #CLIENT self --- @param #string MissionBriefing --- @return #CLIENT self -function CLIENT:ShowMissionBriefing( MissionBriefing ) - self:F( { self.ClientName } ) - - if MissionBriefing then - self:Message( MissionBriefing, 60, "Mission Briefing" ) - end - - return self -end - - - ---- Resets a CLIENT. --- @param #CLIENT self --- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. -function CLIENT:Reset( ClientName ) - self:F() - self._Menus = {} -end - --- Is Functions - ---- Checks if the CLIENT is a multi-seated UNIT. --- @param #CLIENT self --- @return #boolean true if multi-seated. -function CLIENT:IsMultiSeated() - self:F( self.ClientName ) - - local ClientMultiSeatedTypes = { - ["Mi-8MT"] = "Mi-8MT", - ["UH-1H"] = "UH-1H", - ["P-51B"] = "P-51B" - } - - if self:IsAlive() then - local ClientTypeName = self:GetClientGroupUnit():GetTypeName() - if ClientMultiSeatedTypes[ClientTypeName] then - return true - end - end - - return false -end - ---- Checks for a client alive event and calls a function on a continuous basis. --- @param #CLIENT self --- @param #function CallBackFunction Create a function that will be called when a player joins the slot. --- @return #CLIENT -function CLIENT:Alive( CallBackFunction, ... ) - self:F() - - self.ClientCallBack = CallBackFunction - self.ClientParameters = arg - - return self -end - ---- @param #CLIENT self -function CLIENT:_AliveCheckScheduler( SchedulerName ) - self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) - - if self:IsAlive() then - if self.ClientAlive2 == false then - self:ShowBriefing() - if self.ClientCallBack then - self:T("Calling Callback function") - self.ClientCallBack( self, unpack( self.ClientParameters ) ) - end - self.ClientAlive2 = true - end - else - if self.ClientAlive2 == true then - self.ClientAlive2 = false - end - end - - return true -end - ---- Return the DCSGroup of a Client. --- This function is modified to deal with a couple of bugs in DCS 1.5.3 --- @param #CLIENT self --- @return Dcs.DCSWrapper.Group#Group -function CLIENT:GetDCSGroup() - self:F3() - --- local ClientData = Group.getByName( self.ClientName ) --- if ClientData and ClientData:isExist() then --- self:T( self.ClientName .. " : group found!" ) --- return ClientData --- else --- return nil --- end - - local ClientUnit = Unit.getByName( self.ClientName ) - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - self:T3( { "CoalitionData:", CoalitionData } ) - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - - --self:E(self.ClientName) - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() and UnitData:getGroup():isExist() then - if ClientGroup:getID() == UnitData:getGroup():getID() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - self.ClientGroupID = ClientGroup:getID() - self.ClientGroupName = ClientGroup:getName() - return ClientGroup - end - else - -- Now we need to resolve the bugs in DCS 1.5 ... - -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) - self:T3( "Bug 1.5 logic" ) - local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate - self.ClientGroupID = ClientGroupTemplate.groupId - self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName - self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) - return ClientGroup - end - -- else - -- error( "Client " .. self.ClientName .. " not found!" ) - end - else - --self:E( { "Client not found!", self.ClientName } ) - end - end - end - end - - -- For non player clients - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - end - end - - self.ClientGroupID = nil - self.ClientGroupUnit = nil - - return nil -end - - --- TODO: Check Dcs.DCSTypes#Group.ID ---- Get the group ID of the client. --- @param #CLIENT self --- @return Dcs.DCSTypes#Group.ID -function CLIENT:GetClientGroupID() - - local ClientGroup = self:GetDCSGroup() - - --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() - return self.ClientGroupID -end - - ---- Get the name of the group of the client. --- @param #CLIENT self --- @return #string -function CLIENT:GetClientGroupName() - - local ClientGroup = self:GetDCSGroup() - - self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() - return self.ClientGroupName -end - ---- Returns the UNIT of the CLIENT. --- @param #CLIENT self --- @return Wrapper.Unit#UNIT -function CLIENT:GetClientGroupUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - self:T( self.ClientDCSUnit ) - if ClientDCSUnit and ClientDCSUnit:isExist() then - local ClientUnit = _DATABASE:FindUnit( self.ClientName ) - self:T2( ClientUnit ) - return ClientUnit - end -end - ---- Returns the DCSUnit of the CLIENT. --- @param #CLIENT self --- @return Dcs.DCSTypes#Unit -function CLIENT:GetClientGroupDCSUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - if ClientDCSUnit and ClientDCSUnit:isExist() then - self:T2( ClientDCSUnit ) - return ClientDCSUnit - end -end - - ---- Evaluates if the CLIENT is a transport. --- @param #CLIENT self --- @return #boolean true is a transport. -function CLIENT:IsTransport() - self:F() - return self.ClientTransport -end - ---- Shows the @{AI_Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{AI_Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. --- @param #CLIENT self -function CLIENT:ShowCargo() - self:F() - - local CargoMsg = "" - - for CargoName, Cargo in pairs( CARGOS ) do - if self == Cargo:IsLoadedInClient() then - CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" - end - end - - if CargoMsg == "" then - CargoMsg = "empty" - end - - self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 ) - -end - --- TODO (1) I urgently need to revise this. ---- A local function called by the DCS World Menu system to switch off messages. -function CLIENT.SwitchMessages( PrmTable ) - PrmTable[1].MessageSwitch = PrmTable[2] -end - ---- The main message driver for the CLIENT. --- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. --- @param #CLIENT self --- @param #string Message is the text describing the message. --- @param #number MessageDuration is the duration in seconds that the Message should be displayed. --- @param #string MessageCategory is the category of the message (the title). --- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. --- @param #string MessageID is the identifier of the message when displayed with intervals. -function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) - self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) - - if self.MessageSwitch == true then - if MessageCategory == nil then - MessageCategory = "Messages" - end - if MessageID ~= nil then - if self.Messages[MessageID] == nil then - self.Messages[MessageID] = {} - self.Messages[MessageID].MessageId = MessageID - self.Messages[MessageID].MessageTime = timer.getTime() - self.Messages[MessageID].MessageDuration = MessageDuration - if MessageInterval == nil then - self.Messages[MessageID].MessageInterval = 600 - else - self.Messages[MessageID].MessageInterval = MessageInterval - end - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - else - if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then - MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - else - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - end - end - else - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - end - end -end ---- This module contains the STATIC class. --- --- 1) @{Static#STATIC} class, extends @{Positionable#POSITIONABLE} --- =============================================================== --- Statics are **Static Units** defined within the Mission Editor. --- Note that Statics are almost the same as Units, but they don't have a controller. --- The @{Static#STATIC} class is a wrapper class to handle the DCS Static objects: --- --- * Wraps the DCS Static objects. --- * Support all DCS Static APIs. --- * Enhance with Static specific APIs not in the DCS API set. --- --- 1.1) STATIC reference methods --- ----------------------------- --- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the Static Name. --- --- Another thing to know is that STATIC objects do not "contain" the DCS Static object. --- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. --- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. --- --- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: --- --- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). --- --- @module Static --- @author FlightControl - - - - - - ---- The STATIC class --- @type STATIC --- @extends Wrapper.Positionable#POSITIONABLE -STATIC = { - ClassName = "STATIC", -} - - ---- Finds a STATIC from the _DATABASE using the relevant Static Name. --- As an optional parameter, a briefing text can be given also. --- @param #STATIC self --- @param #string StaticName Name of the DCS **Static** as defined within the Mission Editor. --- @param #boolean RaiseError Raise an error if not found. --- @return #STATIC -function STATIC:FindByName( StaticName, RaiseError ) - local StaticFound = _DATABASE:FindStatic( StaticName ) - - self.StaticName = StaticName - - if StaticFound then - StaticFound:F3( { StaticName } ) - - return StaticFound - end - - if RaiseError == nil or RaiseError == true then - error( "STATIC not found for: " .. StaticName ) - end - - return nil -end - -function STATIC:Register( StaticName ) - local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) - self.StaticName = StaticName - return self -end - - -function STATIC:GetDCSObject() - local DCSStatic = StaticObject.getByName( self.StaticName ) - - if DCSStatic then - return DCSStatic - end - - return nil -end - -function STATIC:GetThreatLevel() - - return 1, "Static" -end--- This module contains the AIRBASE classes. --- --- === --- --- 1) @{Airbase#AIRBASE} class, extends @{Positionable#POSITIONABLE} --- ================================================================= --- The @{AIRBASE} class is a wrapper class to handle the DCS Airbase objects: --- --- * Support all DCS Airbase APIs. --- * Enhance with Airbase specific APIs not in the DCS Airbase API set. --- --- --- 1.1) AIRBASE reference methods --- ------------------------------ --- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Airbase or the DCS AirbaseName. --- --- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. --- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. --- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. --- --- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: --- --- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. --- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). --- --- 1.2) DCS AIRBASE APIs --- --------------------- --- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. --- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, --- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSWrapper.Airbase#Airbase.getName}() --- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). --- --- More functions will be added --- ---------------------------- --- During the MOOSE development, more functions will be added. --- --- @module Airbase --- @author FlightControl - - - - - ---- The AIRBASE class --- @type AIRBASE --- @extends Wrapper.Positionable#POSITIONABLE -AIRBASE = { - ClassName="AIRBASE", - CategoryName = { - [Airbase.Category.AIRDROME] = "Airdrome", - [Airbase.Category.HELIPAD] = "Helipad", - [Airbase.Category.SHIP] = "Ship", - }, - } - --- Registration. - ---- Create a new AIRBASE from DCSAirbase. --- @param #AIRBASE self --- @param #string AirbaseName The name of the airbase. --- @return Wrapper.Airbase#AIRBASE -function AIRBASE:Register( AirbaseName ) - - local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) - self.AirbaseName = AirbaseName - return self -end - --- Reference methods. - ---- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. --- @param #AIRBASE self --- @param Dcs.DCSWrapper.Airbase#Airbase DCSAirbase An existing DCS Airbase object reference. --- @return Wrapper.Airbase#AIRBASE self -function AIRBASE:Find( DCSAirbase ) - - local AirbaseName = DCSAirbase:getName() - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - ---- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. --- @param #AIRBASE self --- @param #string AirbaseName The Airbase Name. --- @return Wrapper.Airbase#AIRBASE self -function AIRBASE:FindByName( AirbaseName ) - - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - -function AIRBASE:GetDCSObject() - local DCSAirbase = Airbase.getByName( self.AirbaseName ) - - if DCSAirbase then - return DCSAirbase - end - - return nil -end - - - ---- This module contains the SCENERY class. --- --- 1) @{Scenery#SCENERY} class, extends @{Positionable#POSITIONABLE} --- =============================================================== --- Scenery objects are defined on the map. --- The @{Scenery#SCENERY} class is a wrapper class to handle the DCS Scenery objects: --- --- * Wraps the DCS Scenery objects. --- * Support all DCS Scenery APIs. --- * Enhance with Scenery specific APIs not in the DCS API set. --- --- @module Scenery --- @author FlightControl - - - ---- The SCENERY class --- @type SCENERY --- @extends Wrapper.Positionable#POSITIONABLE -SCENERY = { - ClassName = "SCENERY", -} - - -function SCENERY:Register( SceneryName, SceneryObject ) - local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) ) - self.SceneryName = SceneryName - self.SceneryObject = SceneryObject - return self -end - -function SCENERY:GetDCSObject() - return self.SceneryObject -end - -function SCENERY:GetThreatLevel() - - return 0, "Scenery" -end ---- Single-Player:**Yes** / Multi-Player:**Yes** / Core:**Yes** -- **Administer the scoring of player achievements, --- and create a CSV file logging the scoring events for use at team or squadron websites.** --- --- ![Banner Image](..\Presentations\SCORING\Dia1.JPG) --- --- === --- --- # 1) @{Scoring#SCORING} class, extends @{Base#BASE} --- --- The @{#SCORING} class administers the scoring of player achievements, --- and creates a CSV file logging the scoring events and results for use at team or squadron websites. --- --- SCORING automatically calculates the threat level of the objects hit and destroyed by players, --- which can be @{Unit}, @{Static) and @{Scenery} objects. --- --- Positive score points are granted when enemy or neutral targets are destroyed. --- Negative score points or penalties are given when a friendly target is hit or destroyed. --- This brings a lot of dynamism in the scoring, where players need to take care to inflict damage on the right target. --- By default, penalties weight heavier in the scoring, to ensure that players don't commit fratricide. --- The total score of the player is calculated by **adding the scores minus the penalties**. --- --- ![Banner Image](..\Presentations\SCORING\Dia4.JPG) --- --- The score value is calculated based on the **threat level of the player** and the **threat level of the target**. --- A calculated score takes the threat level of the target divided by a balanced threat level of the player unit. --- As such, if the threat level of the target is high, and the player threat level is low, a higher score will be given than --- if the threat level of the player would be high too. --- --- ![Banner Image](..\Presentations\SCORING\Dia5.JPG) --- --- When multiple players hit the same target, and finally succeed in destroying the target, then each player who contributed to the target --- destruction, will receive a score. This is important for targets that require significant damage before it can be destroyed, like --- ships or heavy planes. --- --- ![Banner Image](..\Presentations\SCORING\Dia13.JPG) --- --- Optionally, the score values can be **scaled** by a **scale**. Specific scales can be set for positive cores or negative penalties. --- The default range of the scores granted is a value between 0 and 10. The default range of penalties given is a value between 0 and 30. --- --- ![Banner Image](..\Presentations\SCORING\Dia7.JPG) --- --- **Additional scores** can be granted to **specific objects**, when the player(s) destroy these objects. --- --- ![Banner Image](..\Presentations\SCORING\Dia9.JPG) --- --- Various @{Zone}s can be defined for which scores are also granted when objects in that @{Zone} are destroyed. --- This is **specifically useful** to designate **scenery targets on the map** that will generate points when destroyed. --- --- With a small change in MissionScripting.lua, the scoring results can also be logged in a **CSV file**. --- These CSV files can be used to: --- --- * Upload scoring to a database or a BI tool to publish the scoring results to the player community. --- * Upload scoring in an (online) Excel like tool, using pivot tables and pivot charts to show mission results. --- * Share scoring amoung players after the mission to discuss mission results. --- --- Scores can be **reported**. **Menu options** are automatically added to **each player group** when a player joins a client slot or a CA unit. --- Use the radio menu F10 to consult the scores while running the mission. --- Scores can be reported for your user, or an overall score can be reported of all players currently active in the mission. --- --- ## 1.1) Set the destroy score or penalty scale --- --- Score scales can be set for scores granted when enemies or friendlies are destroyed. --- Use the method @{#SCORING.SetScaleDestroyScore}() to set the scale of enemy destroys (positive destroys). --- Use the method @{#SCORING.SetScaleDestroyPenalty}() to set the scale of friendly destroys (negative destroys). --- --- local Scoring = SCORING:New( "Scoring File" ) --- Scoring:SetScaleDestroyScore( 10 ) --- Scoring:SetScaleDestroyPenalty( 40 ) --- --- The above sets the scale for valid scores to 10. So scores will be given in a scale from 0 to 10. --- The penalties will be given in a scale from 0 to 40. --- --- ## 1.2) Define special targets that will give extra scores. --- --- Special targets can be set that will give extra scores to the players when these are destroyed. --- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Unit}s. --- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Static}s. --- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Group}s. --- --- local Scoring = SCORING:New( "Scoring File" ) --- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) --- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 ) --- --- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed. --- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over. --- For example, this can be done as follows: --- --- Scoring:RemoveUnitScore( UNIT:FindByName( "Unit #001" ) ) --- --- --- --- ## 1.3) Define destruction zones that will give extra scores. --- --- Define zones of destruction. Any object destroyed within the zone of the given category will give extra points. --- Use the method @{#SCORING.AddZoneScore}() to add a @{Zone} for additional scoring. --- Use the method @{#SCORING.RemoveZoneScore}() to remove a @{Zone} for additional scoring. --- There are interesting variations that can be achieved with this functionality. For example, if the @{Zone} is a @{Zone#ZONE_UNIT}, --- then the zone is a moving zone, and anything destroyed within that @{Zone} will generate points. --- The other implementation could be to designate a scenery target (a building) in the mission editor surrounded by a @{Zone}, --- just large enough around that building. --- --- ## 1.4) Add extra Goal scores upon an event or a condition. --- --- A mission has goals and achievements. The scoring system provides an API to set additional scores when a goal or achievement event happens. --- Use the method @{#SCORING.AddGoalScore}() to add a score for a Player at any time in your mission. --- --- ## 1.5) Configure fratricide level. --- --- When a player commits too much damage to friendlies, his penalty score will reach a certain level. --- Use the method @{#SCORING.SetFratricide}() to define the level when a player gets kicked. --- By default, the fratricide level is the default penalty mutiplier * 2 for the penalty score. --- --- ## 1.6) Penalty score when a player changes the coalition. --- --- When a player changes the coalition, he can receive a penalty score. --- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. --- By default, the penalty for changing coalition is the default penalty scale. --- --- ## 1.8) Define output CSV files. --- --- The CSV file is given the name of the string given in the @{#SCORING.New}{} constructor, followed by the .csv extension. --- The file is incrementally saved in the **\\Saved Games\\DCS\\Logs** folder, and has a time stamp indicating each mission run. --- See the following example: --- --- local ScoringFirstMission = SCORING:New( "FirstMission" ) --- local ScoringSecondMission = SCORING:New( "SecondMission" ) --- --- The above documents that 2 Scoring objects are created, ScoringFirstMission and ScoringSecondMission. --- --- ## 1.9) Configure messages. --- --- When players hit or destroy targets, messages are sent. --- Various methods exist to configure: --- --- * Which messages are sent upon the event. --- * Which audience receives the message. --- --- ### 1.9.1) Configure the messages sent upon the event. --- --- Use the following methods to configure when to send messages. By default, all messages are sent. --- --- * @{#SCORING.SetMessagesHit}(): Configure to send messages after a target has been hit. --- * @{#SCORING.SetMessagesDestroy}(): Configure to send messages after a target has been destroyed. --- * @{#SCORING.SetMessagesAddon}(): Configure to send messages for additional score, after a target has been destroyed. --- * @{#SCORING.SetMessagesZone}(): Configure to send messages for additional score, after a target has been destroyed within a given zone. --- --- ### 1.9.2) Configure the audience of the messages. --- --- Use the following methods to configure the audience of the messages. By default, the messages are sent to all players in the mission. --- --- * @{#SCORING.SetMessagesToAll}(): Configure to send messages to all players. --- * @{#SCORING.SetMessagesToCoalition}(): Configure to send messages to only those players within the same coalition as the player. --- --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-02-26: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **Wingthor (TAW)**: Testing & Advice. --- * **Dutch-Baron (TAW)**: Testing & Advice. --- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing and Advice. --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module Scoring - - ---- The Scoring class --- @type SCORING --- @field Players A collection of the current players that have joined the game. --- @extends Core.Base#BASE -SCORING = { - ClassName = "SCORING", - ClassID = 0, - Players = {}, -} - -local _SCORINGCoalition = - { - [1] = "Red", - [2] = "Blue", - } - -local _SCORINGCategory = - { - [Unit.Category.AIRPLANE] = "Plane", - [Unit.Category.HELICOPTER] = "Helicopter", - [Unit.Category.GROUND_UNIT] = "Vehicle", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - ---- Creates a new SCORING object to administer the scoring achieved by players. --- @param #SCORING self --- @param #string GameName The name of the game. This name is also logged in the CSV score file. --- @return #SCORING self --- @usage --- -- Define a new scoring object for the mission Gori Valley. --- ScoringObject = SCORING:New( "Gori Valley" ) -function SCORING:New( GameName ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- #SCORING - - if GameName then - self.GameName = GameName - else - error( "A game name must be given to register the scoring results" ) - end - - - -- Additional Object scores - self.ScoringObjects = {} - - -- Additional Zone scores. - self.ScoringZones = {} - - -- Configure Messages - self:SetMessagesToAll() - self:SetMessagesHit( true ) - self:SetMessagesDestroy( true ) - self:SetMessagesScore( true ) - self:SetMessagesZone( true ) - - -- Scales - self:SetScaleDestroyScore( 10 ) - self:SetScaleDestroyPenalty( 30 ) - - -- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked). - self:SetFratricide( self.ScaleDestroyPenalty * 3 ) - - -- Default penalty when a player changes coalition. - self:SetCoalitionChangePenalty( self.ScaleDestroyPenalty ) - - -- Event handlers - self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Hit, self._EventOnHit ) - self:HandleEvent( EVENTS.PlayerEnterUnit ) - self:HandleEvent( EVENTS.PlayerLeaveUnit ) - - -- Create the CSV file. - self:OpenCSV( GameName ) - - return self - -end - ---- Set the scale for scoring valid destroys (enemy destroys). --- A default calculated score is a value between 1 and 10. --- The scale magnifies the scores given to the players. --- @param #SCORING self --- @param #number Scale The scale of the score given. -function SCORING:SetScaleDestroyScore( Scale ) - - self.ScaleDestroyScore = Scale - - return self -end - ---- Set the scale for scoring penalty destroys (friendly destroys). --- A default calculated penalty is a value between 1 and 10. --- The scale magnifies the scores given to the players. --- @param #SCORING self --- @param #number Scale The scale of the score given. --- @return #SCORING -function SCORING:SetScaleDestroyPenalty( Scale ) - - self.ScaleDestroyPenalty = Scale - - return self -end - ---- Add a @{Unit} for additional scoring when the @{Unit} is destroyed. --- Note that if there was already a @{Unit} declared within the scoring with the same name, --- then the old @{Unit} will be replaced with the new @{Unit}. --- @param #SCORING self --- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given. --- @param #number Score The Score value. --- @return #SCORING -function SCORING:AddUnitScore( ScoreUnit, Score ) - - local UnitName = ScoreUnit:GetName() - - self.ScoringObjects[UnitName] = Score - - return self -end - ---- Removes a @{Unit} for additional scoring when the @{Unit} is destroyed. --- @param #SCORING self --- @param Wrapper.Unit#UNIT ScoreUnit The @{Unit} for which the Score needs to be given. --- @return #SCORING -function SCORING:RemoveUnitScore( ScoreUnit ) - - local UnitName = ScoreUnit:GetName() - - self.ScoringObjects[UnitName] = nil - - return self -end - ---- Add a @{Static} for additional scoring when the @{Static} is destroyed. --- Note that if there was already a @{Static} declared within the scoring with the same name, --- then the old @{Static} will be replaced with the new @{Static}. --- @param #SCORING self --- @param Wrapper.Static#UNIT ScoreStatic The @{Static} for which the Score needs to be given. --- @param #number Score The Score value. --- @return #SCORING -function SCORING:AddStaticScore( ScoreStatic, Score ) - - local StaticName = ScoreStatic:GetName() - - self.ScoringObjects[StaticName] = Score - - return self -end - ---- Removes a @{Static} for additional scoring when the @{Static} is destroyed. --- @param #SCORING self --- @param Wrapper.Static#UNIT ScoreStatic The @{Static} for which the Score needs to be given. --- @return #SCORING -function SCORING:RemoveStaticScore( ScoreStatic ) - - local StaticName = ScoreStatic:GetName() - - self.ScoringObjects[StaticName] = nil - - return self -end - - ---- Specify a special additional score for a @{Group}. --- @param #SCORING self --- @param Wrapper.Group#GROUP ScoreGroup The @{Group} for which each @{Unit} a Score is given. --- @param #number Score The Score value. --- @return #SCORING -function SCORING:AddScoreGroup( ScoreGroup, Score ) - - local ScoreUnits = ScoreGroup:GetUnits() - - for ScoreUnitID, ScoreUnit in pairs( ScoreUnits ) do - local UnitName = ScoreUnit:GetName() - self.ScoringObjects[UnitName] = Score - end - - return self -end - ---- Add a @{Zone} to define additional scoring when any object is destroyed in that zone. --- Note that if a @{Zone} with the same name is already within the scoring added, the @{Zone} (type) and Score will be replaced! --- This allows for a dynamic destruction zone evolution within your mission. --- @param #SCORING self --- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. --- Note that a zone can be a polygon or a moving zone. --- @param #number Score The Score value. --- @return #SCORING -function SCORING:AddZoneScore( ScoreZone, Score ) - - local ZoneName = ScoreZone:GetName() - - self.ScoringZones[ZoneName] = {} - self.ScoringZones[ZoneName].ScoreZone = ScoreZone - self.ScoringZones[ZoneName].Score = Score - - return self -end - ---- Remove a @{Zone} for additional scoring. --- The scoring will search if any @{Zone} is added with the given name, and will remove that zone from the scoring. --- This allows for a dynamic destruction zone evolution within your mission. --- @param #SCORING self --- @param Core.Zone#ZONE_BASE ScoreZone The @{Zone} which defines the destruction score perimeters. --- Note that a zone can be a polygon or a moving zone. --- @return #SCORING -function SCORING:RemoveZoneScore( ScoreZone ) - - local ZoneName = ScoreZone:GetName() - - self.ScoringZones[ZoneName] = nil - - return self -end - - ---- Configure to send messages after a target has been hit. --- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. --- @return #SCORING -function SCORING:SetMessagesHit( OnOff ) - - self.MessagesHit = OnOff - return self -end - ---- If to send messages after a target has been hit. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesHit() - - return self.MessagesHit -end - ---- Configure to send messages after a target has been destroyed. --- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. --- @return #SCORING -function SCORING:SetMessagesDestroy( OnOff ) - - self.MessagesDestroy = OnOff - return self -end - ---- If to send messages after a target has been destroyed. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesDestroy() - - return self.MessagesDestroy -end - ---- Configure to send messages after a target has been destroyed and receives additional scores. --- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. --- @return #SCORING -function SCORING:SetMessagesScore( OnOff ) - - self.MessagesScore = OnOff - return self -end - ---- If to send messages after a target has been destroyed and receives additional scores. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesScore() - - return self.MessagesScore -end - ---- Configure to send messages after a target has been hit in a zone, and additional score is received. --- @param #SCORING self --- @param #boolean OnOff If true is given, the messages are sent. --- @return #SCORING -function SCORING:SetMessagesZone( OnOff ) - - self.MessagesZone = OnOff - return self -end - ---- If to send messages after a target has been hit in a zone, and additional score is received. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesZone() - - return self.MessagesZone -end - ---- Configure to send messages to all players. --- @param #SCORING self --- @return #SCORING -function SCORING:SetMessagesToAll() - - self.MessagesAudience = 1 - return self -end - ---- If to send messages to all players. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesToAll() - - return self.MessagesAudience == 1 -end - ---- Configure to send messages to only those players within the same coalition as the player. --- @param #SCORING self --- @return #SCORING -function SCORING:SetMessagesToCoalition() - - self.MessagesAudience = 2 - return self -end - ---- If to send messages to only those players within the same coalition as the player. --- @param #SCORING self --- @return #boolean -function SCORING:IfMessagesToCoalition() - - return self.MessagesAudience == 2 -end - - ---- When a player commits too much damage to friendlies, his penalty score will reach a certain level. --- Use this method to define the level when a player gets kicked. --- By default, the fratricide level is the default penalty mutiplier * 2 for the penalty score. --- @param #SCORING self --- @param #number Fratricide The amount of maximum penalty that may be inflicted by a friendly player before he gets kicked. --- @return #SCORING -function SCORING:SetFratricide( Fratricide ) - - self.Fratricide = Fratricide - return self -end - - ---- When a player changes the coalition, he can receive a penalty score. --- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition. --- By default, the penalty for changing coalition is the default penalty scale. --- @param #SCORING self --- @param #number CoalitionChangePenalty The amount of penalty that is given. --- @return #SCORING -function SCORING:SetCoalitionChangePenalty( CoalitionChangePenalty ) - - self.CoalitionChangePenalty = CoalitionChangePenalty - return self -end - - ---- Add a new player entering a Unit. --- @param #SCORING self --- @param Wrapper.Unit#UNIT UnitData -function SCORING:_AddPlayerFromUnit( UnitData ) - self:F( UnitData ) - - if UnitData:IsAlive() then - local UnitName = UnitData:GetName() - local PlayerName = UnitData:GetPlayerName() - local UnitDesc = UnitData:GetDesc() - local UnitCategory = UnitDesc.category - local UnitCoalition = UnitData:GetCoalition() - local UnitTypeName = UnitData:GetTypeName() - - self:T( { PlayerName, UnitName, UnitCategory, UnitCoalition, UnitTypeName } ) - - if self.Players[PlayerName] == nil then -- I believe this is the place where a Player gets a life in a mission when he enters a unit ... - self.Players[PlayerName] = {} - self.Players[PlayerName].Hit = {} - self.Players[PlayerName].Destroy = {} - self.Players[PlayerName].Goals = {} - self.Players[PlayerName].Mission = {} - - -- for CategoryID, CategoryName in pairs( SCORINGCategory ) do - -- self.Players[PlayerName].Hit[CategoryID] = {} - -- self.Players[PlayerName].Destroy[CategoryID] = {} - -- end - self.Players[PlayerName].HitPlayers = {} - self.Players[PlayerName].Score = 0 - self.Players[PlayerName].Penalty = 0 - self.Players[PlayerName].PenaltyCoalition = 0 - self.Players[PlayerName].PenaltyWarning = 0 - end - - if not self.Players[PlayerName].UnitCoalition then - self.Players[PlayerName].UnitCoalition = UnitCoalition - else - if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then - self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50 - self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1 - MESSAGE:New( "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] .. - "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.", - 2 - ):ToAll() - self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType, - UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() ) - end - end - self.Players[PlayerName].UnitName = UnitName - self.Players[PlayerName].UnitCoalition = UnitCoalition - self.Players[PlayerName].UnitCategory = UnitCategory - self.Players[PlayerName].UnitType = UnitTypeName - self.Players[PlayerName].UNIT = UnitData - - if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then - if self.Players[PlayerName].PenaltyWarning < 1 then - MESSAGE:New( "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty, - 30 - ):ToAll() - self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1 - end - end - - if self.Players[PlayerName].Penalty > self.Fratricide then - UnitData:Destroy() - MESSAGE:New( "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!", - 10 - ):ToAll() - end - - end -end - - ---- Add a goal score for a player. --- The method takes the PlayerUnit for which the Goal score needs to be set. --- The GoalTag is a string or identifier that is taken into the CSV file scoring log to identify the goal. --- A free text can be given that is shown to the players. --- The Score can be both positive and negative. --- @param #SCORING self --- @param Wrapper.Unit#UNIT PlayerUnit The @{Unit} of the Player. Other Properties for the scoring are taken from this PlayerUnit, like coalition, type etc. --- @param #string GoalTag The string or identifier that is used in the CSV file to identify the goal (sort or group later in Excel). --- @param #string Text A free text that is shown to the players. --- @param #number Score The score can be both positive or negative ( Penalty ). -function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score ) - - local PlayerName = PlayerUnit:GetPlayerName() - - self:E( { PlayerUnit.UnitName, PlayerName, GoalTag, Text, Score } ) - - -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then - local PlayerData = self.Players[PlayerName] - - PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 } - PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score - PlayerData.Score = PlayerData.Score + Score - - MESSAGE:New( Text, 30 ):ToAll() - - self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, PlayerUnit:GetName() ) - end -end - - ---- Registers Scores the players completing a Mission Task. --- @param #SCORING self --- @param Tasking.Mission#MISSION Mission --- @param Wrapper.Unit#UNIT PlayerUnit --- @param #string Text --- @param #number Score -function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) - - local PlayerName = PlayerUnit:GetPlayerName() - local MissionName = Mission:GetName() - - self:E( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) - - -- PlayerName can be nil, if the Unit with the player crashed or due to another reason. - if PlayerName then - local PlayerData = self.Players[PlayerName] - - if not PlayerData.Mission[MissionName] then - PlayerData.Mission[MissionName] = {} - PlayerData.Mission[MissionName].ScoreTask = 0 - PlayerData.Mission[MissionName].ScoreMission = 0 - end - - self:T( PlayerName ) - self:T( PlayerData.Mission[MissionName] ) - - PlayerData.Score = self.Players[PlayerName].Score + Score - PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score - - MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. - Score .. " task score!", - 30 ):ToAll() - - self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() ) - end -end - - ---- Registers Mission Scores for possible multiple players that contributed in the Mission. --- @param #SCORING self --- @param Tasking.Mission#MISSION Mission --- @param Wrapper.Unit#UNIT PlayerUnit --- @param #string Text --- @param #number Score -function SCORING:_AddMissionScore( Mission, Text, Score ) - - local MissionName = Mission:GetName() - - self:E( { Mission, Text, Score } ) - self:E( self.Players ) - - for PlayerName, PlayerData in pairs( self.Players ) do - - self:E( PlayerData ) - if PlayerData.Mission[MissionName] then - - PlayerData.Score = PlayerData.Score + Score - PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score - - MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. - Score .. " mission score!", - 60 ):ToAll() - - self:ScoreCSV( PlayerName, "", "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score ) - end - end -end - - ---- Handles the OnPlayerEnterUnit event for the scoring. --- @param #SCORING self --- @param Core.Event#EVENTDATA Event -function SCORING:OnEventPlayerEnterUnit( Event ) - if Event.IniUnit then - self:_AddPlayerFromUnit( Event.IniUnit ) - local Menu = MENU_GROUP:New( Event.IniGroup, 'Scoring' ) - local ReportGroupSummary = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Summary report players in group', Menu, SCORING.ReportScoreGroupSummary, self, Event.IniGroup ) - local ReportGroupDetailed = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Detailed report players in group', Menu, SCORING.ReportScoreGroupDetailed, self, Event.IniGroup ) - local ReportToAllSummary = MENU_GROUP_COMMAND:New( Event.IniGroup, 'Summary report all players', Menu, SCORING.ReportScoreAllSummary, self, Event.IniGroup ) - self:SetState( Event.IniUnit, "ScoringMenu", Menu ) - end -end - ---- Handles the OnPlayerLeaveUnit event for the scoring. --- @param #SCORING self --- @param Core.Event#EVENTDATA Event -function SCORING:OnEventPlayerLeaveUnit( Event ) - if Event.IniUnit then - local Menu = self:GetState( Event.IniUnit, "ScoringMenu" ) -- Core.Menu#MENU_GROUP - if Menu then - Menu:Remove() - end - end -end - - ---- Handles the OnHit event for the scoring. --- @param #SCORING self --- @param Core.Event#EVENTDATA Event -function SCORING:_EventOnHit( Event ) - self:F( { Event } ) - - local InitUnit = nil - local InitUNIT = nil - local InitUnitName = "" - local InitGroup = nil - local InitGroupName = "" - local InitPlayerName = nil - - local InitCoalition = nil - local InitCategory = nil - local InitType = nil - local InitUnitCoalition = nil - local InitUnitCategory = nil - local InitUnitType = nil - - local TargetUnit = nil - local TargetUNIT = nil - local TargetUnitName = "" - local TargetGroup = nil - local TargetGroupName = "" - local TargetPlayerName = nil - - local TargetCoalition = nil - local TargetCategory = nil - local TargetType = nil - local TargetUnitCoalition = nil - local TargetUnitCategory = nil - local TargetUnitType = nil - - if Event.IniDCSUnit then - - InitUnit = Event.IniDCSUnit - InitUNIT = Event.IniUnit - InitUnitName = Event.IniDCSUnitName - InitGroup = Event.IniDCSGroup - InitGroupName = Event.IniDCSGroupName - InitPlayerName = Event.IniPlayerName - - InitCoalition = Event.IniCoalition - --TODO: Workaround Client DCS Bug - --InitCategory = InitUnit:getCategory() - --InitCategory = InitUnit:getDesc().category - InitCategory = Event.IniCategory - InitType = Event.IniTypeName - - InitUnitCoalition = _SCORINGCoalition[InitCoalition] - InitUnitCategory = _SCORINGCategory[InitCategory] - InitUnitType = InitType - - self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } ) - end - - - if Event.TgtDCSUnit then - - TargetUnit = Event.TgtDCSUnit - TargetUNIT = Event.TgtUnit - TargetUnitName = Event.TgtDCSUnitName - TargetGroup = Event.TgtDCSGroup - TargetGroupName = Event.TgtDCSGroupName - TargetPlayerName = Event.TgtPlayerName - - TargetCoalition = Event.TgtCoalition - --TODO: Workaround Client DCS Bug - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category - TargetCategory = Event.TgtCategory - TargetType = Event.TgtTypeName - - TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] - TargetUnitCategory = _SCORINGCategory[TargetCategory] - TargetUnitType = TargetType - - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType, TargetUnitCoalition, TargetUnitCategory, TargetUnitType } ) - end - - if InitPlayerName ~= nil then -- It is a player that is hitting something - self:_AddPlayerFromUnit( InitUNIT ) - if self.Players[InitPlayerName] then -- This should normally not happen, but i'll test it anyway. - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - self:_AddPlayerFromUnit( TargetUNIT ) - end - - self:T( "Hitting Something" ) - - -- What is he hitting? - if TargetCategory then - - -- A target got hit, score it. - -- Player contains the score data from self.Players[InitPlayerName] - local Player = self.Players[InitPlayerName] - - -- Ensure there is a hit table per TargetCategory and TargetUnitName. - Player.Hit[TargetCategory] = Player.Hit[TargetCategory] or {} - Player.Hit[TargetCategory][TargetUnitName] = Player.Hit[TargetCategory][TargetUnitName] or {} - - -- PlayerHit contains the score counters and data per unit that was hit. - local PlayerHit = Player.Hit[TargetCategory][TargetUnitName] - - PlayerHit.Score = PlayerHit.Score or 0 - PlayerHit.Penalty = PlayerHit.Penalty or 0 - PlayerHit.ScoreHit = PlayerHit.ScoreHit or 0 - PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 - PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 - PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT - - -- Only grant hit scores if there was more than one second between the last hit. - if timer.getTime() - PlayerHit.TimeStamp > 1 then - PlayerHit.TimeStamp = timer.getTime() - - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - - -- Ensure there is a Player to Player hit reference table. - Player.HitPlayers[TargetPlayerName] = true - end - - local Score = 0 - - if InitCoalition then -- A coalition object was hit. - if InitCoalition == TargetCoalition then - Player.Penalty = Player.Penalty + 10 - PlayerHit.Penalty = PlayerHit.Penalty + 10 - PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 - - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. - "Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - else - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit a friendly target " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. - "Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - end - self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -25, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - else - Player.Score = Player.Score + 1 - PlayerHit.Score = PlayerHit.Score + 1 - PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. - "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - else - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit an enemy target " .. - TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " .. - "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - end - self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - else -- A scenery object was hit. - MESSAGE - :New( "Player '" .. InitPlayerName .. "' hit a scenery object.", - 2 - ) - :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) - self:ScoreCSV( InitPlayerName, "", "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) - end - end - end - end - elseif InitPlayerName == nil then -- It is an AI hitting a player??? - - end -end - ---- Track DEAD or CRASH events for the scoring. --- @param #SCORING self --- @param Core.Event#EVENTDATA Event -function SCORING:_EventOnDeadOrCrash( Event ) - self:F( { Event } ) - - local TargetUnit = nil - local TargetGroup = nil - local TargetUnitName = "" - local TargetGroupName = "" - local TargetPlayerName = "" - local TargetCoalition = nil - local TargetCategory = nil - local TargetType = nil - local TargetUnitCoalition = nil - local TargetUnitCategory = nil - local TargetUnitType = nil - - if Event.IniDCSUnit then - - TargetUnit = Event.IniUnit - TargetUnitName = Event.IniDCSUnitName - TargetGroup = Event.IniDCSGroup - TargetGroupName = Event.IniDCSGroupName - TargetPlayerName = Event.IniPlayerName - - TargetCoalition = Event.IniCoalition - --TargetCategory = TargetUnit:getCategory() - --TargetCategory = TargetUnit:getDesc().category -- Workaround - TargetCategory = Event.IniCategory - TargetType = Event.IniTypeName - - TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] - TargetUnitCategory = _SCORINGCategory[TargetCategory] - TargetUnitType = TargetType - - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) - end - - -- Player contains the score and reference data for the player. - for PlayerName, Player in pairs( self.Players ) do - if Player then -- This should normally not happen, but i'll test it anyway. - self:T( "Something got destroyed" ) - - -- Some variables - local InitUnitName = Player.UnitName - local InitUnitType = Player.UnitType - local InitCoalition = Player.UnitCoalition - local InitCategory = Player.UnitCategory - local InitUnitCoalition = _SCORINGCoalition[InitCoalition] - local InitUnitCategory = _SCORINGCategory[InitCategory] - - self:T( { InitUnitName, InitUnitType, InitUnitCoalition, InitCoalition, InitUnitCategory, InitCategory } ) - - -- What is the player destroying? - if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] then -- Was there a hit for this unit for this player before registered??? - - Player.Destroy[TargetCategory] = Player.Destroy[TargetCategory] or {} - Player.Destroy[TargetCategory][TargetType] = Player.Destroy[TargetCategory][TargetType] or {} - - -- PlayerDestroy contains the destroy score data per category and target type of the player. - local TargetDestroy = Player.Destroy[TargetCategory][TargetType] - TargetDestroy.Score = TargetDestroy.Score or 0 - TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy or 0 - TargetDestroy.Penalty = TargetDestroy.Penalty or 0 - TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy or 0 - - if TargetCoalition then - if InitCoalition == TargetCoalition then - local ThreatLevelTarget, ThreatTypeTarget = TargetUnit:GetThreatLevel() - local ThreatLevelPlayer = Player.UNIT:GetThreatLevel() / 10 + 1 - local ThreatPenalty = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyPenalty / 10 ) - self:E( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } ) - - Player.Penalty = Player.Penalty + ThreatPenalty - TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty - TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1 - - if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player - MESSAGE - :New( "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. - TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.PenaltyDestroy .. " times. " .. - "Penalty: -" .. TargetDestroy.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) - else - MESSAGE - :New( "Player '" .. PlayerName .. "' destroyed a friendly target " .. - TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.PenaltyDestroy .. " times. " .. - "Penalty: -" .. TargetDestroy.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) - end - - self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_PENALTY", 1, ThreatPenalty, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - else - - local ThreatLevelTarget, ThreatTypeTarget = TargetUnit:GetThreatLevel() - local ThreatLevelPlayer = Player.UNIT:GetThreatLevel() / 10 + 1 - local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyScore / 10 ) - - self:E( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } ) - - Player.Score = Player.Score + ThreatScore - TargetDestroy.Score = TargetDestroy.Score + ThreatScore - TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1 - if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player - MESSAGE - :New( "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. - TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.ScoreDestroy .. " times. " .. - "Score: " .. TargetDestroy.Score .. ". Score Total:" .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) - else - MESSAGE - :New( "Player '" .. PlayerName .. "' destroyed an enemy " .. - TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. TargetDestroy.ScoreDestroy .. " times. " .. - "Score: " .. TargetDestroy.Score .. ". Total:" .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) - end - self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, ThreatScore, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - - local UnitName = TargetUnit:GetName() - local Score = self.ScoringObjects[UnitName] - if Score then - Player.Score = Player.Score + Score - TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE - :New( "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() ) - self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - - -- Check if there are Zones where the destruction happened. - for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do - self:E( { ScoringZone = ScoreZoneData } ) - local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE - local Score = ScoreZoneData.Score - if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then - Player.Score = Player.Score + Score - TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE - :New( "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. - "Total: " .. Player.Score - Player.Penalty, - 15 ) - :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) - self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - end - - end - else - -- Check if there are Zones where the destruction happened. - for ZoneName, ScoreZoneData in pairs( self.ScoringZones ) do - self:E( { ScoringZone = ScoreZoneData } ) - local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE - local Score = ScoreZoneData.Score - if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then - Player.Score = Player.Score + Score - TargetDestroy.Score = TargetDestroy.Score + Score - MESSAGE - :New( "Scenery destroyed in zone '" .. ScoreZone:GetName() .. "'." .. - "Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " .. - "Total: " .. Player.Score - Player.Penalty, - 15 - ) - :ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() ) - :ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() ) - self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType ) - end - end - end - end - end - end -end - - ---- Produce detailed report of player hit scores. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerHits( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageHits = "" - for CategoryID, CategoryName in pairs( _SCORINGCategory ) do - self:T( CategoryName ) - if PlayerData.Hit[CategoryID] then - self:T( "Hit scores exist for player " .. PlayerName ) - local Score = 0 - local ScoreHit = 0 - local Penalty = 0 - local PenaltyHit = 0 - for UnitName, UnitData in pairs( PlayerData.Hit[CategoryID] ) do - Score = Score + UnitData.Score - ScoreHit = ScoreHit + UnitData.ScoreHit - Penalty = Penalty + UnitData.Penalty - PenaltyHit = UnitData.PenaltyHit - end - local ScoreMessageHit = string.format( "%s:%d ", CategoryName, Score - Penalty ) - self:T( ScoreMessageHit ) - ScoreMessageHits = ScoreMessageHits .. ScoreMessageHit - PlayerScore = PlayerScore + Score - PlayerPenalty = PlayerPenalty + Penalty - else - --ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) - end - end - if ScoreMessageHits ~= "" then - ScoreMessage = "Hits: " .. ScoreMessageHits - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - - ---- Produce detailed report of player destroy scores. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerDestroys( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageDestroys = "" - for CategoryID, CategoryName in pairs( _SCORINGCategory ) do - if PlayerData.Destroy[CategoryID] then - self:T( "Destroy scores exist for player " .. PlayerName ) - local Score = 0 - local ScoreDestroy = 0 - local Penalty = 0 - local PenaltyDestroy = 0 - - for UnitName, UnitData in pairs( PlayerData.Destroy[CategoryID] ) do - self:E( { UnitData = UnitData } ) - if UnitData ~= {} then - Score = Score + UnitData.Score - ScoreDestroy = ScoreDestroy + UnitData.ScoreDestroy - Penalty = Penalty + UnitData.Penalty - PenaltyDestroy = PenaltyDestroy + UnitData.PenaltyDestroy - end - end - - local ScoreMessageDestroy = string.format( " %s:%d ", CategoryName, Score - Penalty ) - self:T( ScoreMessageDestroy ) - ScoreMessageDestroys = ScoreMessageDestroys .. ScoreMessageDestroy - - PlayerScore = PlayerScore + Score - PlayerPenalty = PlayerPenalty + Penalty - else - --ScoreMessageDestroys = ScoreMessageDestroys .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) - end - end - if ScoreMessageDestroys ~= "" then - ScoreMessage = "Destroys: " .. ScoreMessageDestroys - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - ---- Produce detailed report of player penalty scores because of changing the coalition. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerCoalitionChanges( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageCoalitionChangePenalties = "" - if PlayerData.PenaltyCoalition ~= 0 then - ScoreMessageCoalitionChangePenalties = ScoreMessageCoalitionChangePenalties .. string.format( " -%d (%d changed)", PlayerData.Penalty, PlayerData.PenaltyCoalition ) - PlayerPenalty = PlayerPenalty + PlayerData.Penalty - end - if ScoreMessageCoalitionChangePenalties ~= "" then - ScoreMessage = ScoreMessage .. "Coalition Penalties: " .. ScoreMessageCoalitionChangePenalties - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - ---- Produce detailed report of player goal scores. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerGoals( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageGoal = "" - local ScoreGoal = 0 - local ScoreTask = 0 - for GoalName, GoalData in pairs( PlayerData.Goals ) do - ScoreGoal = ScoreGoal + GoalData.Score - ScoreMessageGoal = ScoreMessageGoal .. "'" .. GoalName .. "':" .. GoalData.Score .. "; " - end - PlayerScore = PlayerScore + ScoreGoal - - if ScoreMessageGoal ~= "" then - ScoreMessage = "Goals: " .. ScoreMessageGoal - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - ---- Produce detailed report of player penalty scores because of changing the coalition. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @return #string The report. -function SCORING:ReportDetailedPlayerMissions( PlayerName ) - - local ScoreMessage = "" - local PlayerScore = 0 - local PlayerPenalty = 0 - - local PlayerData = self.Players[PlayerName] - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local ScoreMessageMission = "" - local ScoreMission = 0 - local ScoreTask = 0 - for MissionName, MissionData in pairs( PlayerData.Mission ) do - ScoreMission = ScoreMission + MissionData.ScoreMission - ScoreTask = ScoreTask + MissionData.ScoreTask - ScoreMessageMission = ScoreMessageMission .. "'" .. MissionName .. "'; " - end - PlayerScore = PlayerScore + ScoreMission + ScoreTask - - if ScoreMessageMission ~= "" then - ScoreMessage = "Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ")" - end - end - - return ScoreMessage, PlayerScore, PlayerPenalty -end - - ---- Report Group Score Summary --- @param #SCORING self --- @param Wrapper.Group#GROUP PlayerGroup The player group. -function SCORING:ReportScoreGroupSummary( PlayerGroup ) - - local PlayerMessage = "" - - self:T( "Report Score Group Summary" ) - - local PlayerUnits = PlayerGroup:GetUnits() - for UnitID, PlayerUnit in pairs( PlayerUnits ) do - local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT - local PlayerName = PlayerUnit:GetPlayerName() - - if PlayerName then - - local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits - self:E( { ReportHits, ScoreHits, PenaltyHits } ) - - local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) - ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys - self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } ) - - local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName ) - ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges - self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } ) - - local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName ) - ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals - self:E( { ReportGoals, ScoreGoals, PenaltyGoals } ) - - local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) - ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions - self:E( { ReportMissions, ScoreMissions, PenaltyMissions } ) - - local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions - local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty - ) - MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup ) - end - end - -end - ---- Report Group Score Detailed --- @param #SCORING self --- @param Wrapper.Group#GROUP PlayerGroup The player group. -function SCORING:ReportScoreGroupDetailed( PlayerGroup ) - - local PlayerMessage = "" - - self:T( "Report Score Group Detailed" ) - - local PlayerUnits = PlayerGroup:GetUnits() - for UnitID, PlayerUnit in pairs( PlayerUnits ) do - local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT - local PlayerName = PlayerUnit:GetPlayerName() - - if PlayerName then - - local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits - self:E( { ReportHits, ScoreHits, PenaltyHits } ) - - local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) - ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys - self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } ) - - local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName ) - ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges - self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } ) - - local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName ) - ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals - self:E( { ReportGoals, ScoreGoals, PenaltyGoals } ) - - local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) - ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions - self:E( { ReportMissions, ScoreMissions, PenaltyMissions } ) - - local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions - local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty, - ReportHits, - ReportDestroys, - ReportCoalitionChanges, - ReportGoals, - ReportMissions - ) - MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup ) - end - end - -end - ---- Report all players score --- @param #SCORING self --- @param Wrapper.Group#GROUP PlayerGroup The player group. -function SCORING:ReportScoreAllSummary( PlayerGroup ) - - local PlayerMessage = "" - - self:T( "Report Score All Players" ) - - for PlayerName, PlayerData in pairs( self.Players ) do - - if PlayerName then - - local ReportHits, ScoreHits, PenaltyHits = self:ReportDetailedPlayerHits( PlayerName ) - ReportHits = ReportHits ~= "" and "\n- " .. ReportHits or ReportHits - self:E( { ReportHits, ScoreHits, PenaltyHits } ) - - local ReportDestroys, ScoreDestroys, PenaltyDestroys = self:ReportDetailedPlayerDestroys( PlayerName ) - ReportDestroys = ReportDestroys ~= "" and "\n- " .. ReportDestroys or ReportDestroys - self:E( { ReportDestroys, ScoreDestroys, PenaltyDestroys } ) - - local ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges = self:ReportDetailedPlayerCoalitionChanges( PlayerName ) - ReportCoalitionChanges = ReportCoalitionChanges ~= "" and "\n- " .. ReportCoalitionChanges or ReportCoalitionChanges - self:E( { ReportCoalitionChanges, ScoreCoalitionChanges, PenaltyCoalitionChanges } ) - - local ReportGoals, ScoreGoals, PenaltyGoals = self:ReportDetailedPlayerGoals( PlayerName ) - ReportGoals = ReportGoals ~= "" and "\n- " .. ReportGoals or ReportGoals - self:E( { ReportGoals, ScoreGoals, PenaltyGoals } ) - - local ReportMissions, ScoreMissions, PenaltyMissions = self:ReportDetailedPlayerMissions( PlayerName ) - ReportMissions = ReportMissions ~= "" and "\n- " .. ReportMissions or ReportMissions - self:E( { ReportMissions, ScoreMissions, PenaltyMissions } ) - - local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions - local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions - - PlayerMessage = - string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )", - PlayerName, - PlayerScore - PlayerPenalty, - PlayerScore, - PlayerPenalty - ) - MESSAGE:New( PlayerMessage, 30, "Player '" .. PlayerName .. "'" ):ToGroup( PlayerGroup ) - end - end - -end - - -function SCORING:SecondsToClock(sSeconds) - local nSeconds = sSeconds - if nSeconds == 0 then - --return nil; - return "00:00:00"; - else - nHours = string.format("%02.f", math.floor(nSeconds/3600)); - nMins = string.format("%02.f", math.floor(nSeconds/60 - (nHours*60))); - nSecs = string.format("%02.f", math.floor(nSeconds - nHours*3600 - nMins *60)); - return nHours..":"..nMins..":"..nSecs - end -end - ---- Opens a score CSV file to log the scores. --- @param #SCORING self --- @param #string ScoringCSV --- @return #SCORING self --- @usage --- -- Open a new CSV file to log the scores of the game Gori Valley. Let the name of the CSV file begin with "Player Scores". --- ScoringObject = SCORING:New( "Gori Valley" ) --- ScoringObject:OpenCSV( "Player Scores" ) -function SCORING:OpenCSV( ScoringCSV ) - self:F( ScoringCSV ) - - if lfs and io and os then - if ScoringCSV then - self.ScoringCSV = ScoringCSV - local fdir = lfs.writedir() .. [[Logs\]] .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv" - - self.CSVFile, self.err = io.open( fdir, "w+" ) - if not self.CSVFile then - error( "Error: Cannot open CSV file in " .. lfs.writedir() ) - end - - self.CSVFile:write( '"GameName","RunTime","Time","PlayerName","TargetPlayerName","ScoreType","PlayerUnitCoaltion","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n' ) - - self.RunTime = os.date("%y-%m-%d_%H-%M-%S") - else - error( "A string containing the CSV file name must be given." ) - end - else - self:E( "The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used..." ) - end - return self -end - - ---- Registers a score for a player. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @param #string TargetPlayerName The name of the target player. --- @param #string ScoreType The type of the score. --- @param #string ScoreTimes The amount of scores achieved. --- @param #string ScoreAmount The score given. --- @param #string PlayerUnitName The unit name of the player. --- @param #string PlayerUnitCoalition The coalition of the player unit. --- @param #string PlayerUnitCategory The category of the player unit. --- @param #string PlayerUnitType The type of the player unit. --- @param #string TargetUnitName The name of the target unit. --- @param #string TargetUnitCoalition The coalition of the target unit. --- @param #string TargetUnitCategory The category of the target unit. --- @param #string TargetUnitType The type of the target unit. --- @return #SCORING self -function SCORING:ScoreCSV( PlayerName, TargetPlayerName, ScoreType, ScoreTimes, ScoreAmount, PlayerUnitName, PlayerUnitCoalition, PlayerUnitCategory, PlayerUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - --write statistic information to file - local ScoreTime = self:SecondsToClock( timer.getTime() ) - PlayerName = PlayerName:gsub( '"', '_' ) - - TargetPlayerName = TargetPlayerName or "" - TargetPlayerName = TargetPlayerName:gsub( '"', '_' ) - - if PlayerUnitName and PlayerUnitName ~= '' then - local PlayerUnit = Unit.getByName( PlayerUnitName ) - - if PlayerUnit then - if not PlayerUnitCategory then - --PlayerUnitCategory = SCORINGCategory[PlayerUnit:getCategory()] - PlayerUnitCategory = _SCORINGCategory[PlayerUnit:getDesc().category] - end - - if not PlayerUnitCoalition then - PlayerUnitCoalition = _SCORINGCoalition[PlayerUnit:getCoalition()] - end - - if not PlayerUnitType then - PlayerUnitType = PlayerUnit:getTypeName() - end - else - PlayerUnitName = '' - PlayerUnitCategory = '' - PlayerUnitCoalition = '' - PlayerUnitType = '' - end - else - PlayerUnitName = '' - PlayerUnitCategory = '' - PlayerUnitCoalition = '' - PlayerUnitType = '' - end - - TargetUnitCoalition = TargetUnitCoalition or "" - TargetUnitCategory = TargetUnitCategory or "" - TargetUnitType = TargetUnitType or "" - TargetUnitName = TargetUnitName or "" - - if lfs and io and os then - self.CSVFile:write( - '"' .. self.GameName .. '"' .. ',' .. - '"' .. self.RunTime .. '"' .. ',' .. - '' .. ScoreTime .. '' .. ',' .. - '"' .. PlayerName .. '"' .. ',' .. - '"' .. TargetPlayerName .. '"' .. ',' .. - '"' .. ScoreType .. '"' .. ',' .. - '"' .. PlayerUnitCoalition .. '"' .. ',' .. - '"' .. PlayerUnitCategory .. '"' .. ',' .. - '"' .. PlayerUnitType .. '"' .. ',' .. - '"' .. PlayerUnitName .. '"' .. ',' .. - '"' .. TargetUnitCoalition .. '"' .. ',' .. - '"' .. TargetUnitCategory .. '"' .. ',' .. - '"' .. TargetUnitType .. '"' .. ',' .. - '"' .. TargetUnitName .. '"' .. ',' .. - '' .. ScoreTimes .. '' .. ',' .. - '' .. ScoreAmount - ) - - self.CSVFile:write( "\n" ) - end -end - - -function SCORING:CloseCSV() - if lfs and io and os then - self.CSVFile:close() - end -end - ---- The CLEANUP class keeps an area clean of crashing or colliding airplanes. It also prevents airplanes from firing within this area. --- @module CleanUp --- @author Flightcontrol - - - - - - - ---- The CLEANUP class. --- @type CLEANUP --- @extends Core.Base#BASE -CLEANUP = { - ClassName = "CLEANUP", - ZoneNames = {}, - TimeInterval = 300, - CleanUpList = {}, -} - ---- Creates the main object which is handling the cleaning of the debris within the given Zone Names. --- @param #CLEANUP self --- @param #table ZoneNames Is a table of zone names where the debris should be cleaned. Also a single string can be passed with one zone name. --- @param #number TimeInterval The interval in seconds when the clean activity takes place. The default is 300 seconds, thus every 5 minutes. --- @return #CLEANUP --- @usage --- -- Clean these Zones. --- CleanUpAirports = CLEANUP:New( { 'CLEAN Tbilisi', 'CLEAN Kutaisi' }, 150 ) --- or --- CleanUpTbilisi = CLEANUP:New( 'CLEAN Tbilisi', 150 ) --- CleanUpKutaisi = CLEANUP:New( 'CLEAN Kutaisi', 600 ) -function CLEANUP:New( ZoneNames, TimeInterval ) - - local self = BASE:Inherit( self, BASE:New() ) -- #CLEANUP - self:F( { ZoneNames, TimeInterval } ) - - if type( ZoneNames ) == 'table' then - self.ZoneNames = ZoneNames - else - self.ZoneNames = { ZoneNames } - end - if TimeInterval then - self.TimeInterval = TimeInterval - end - - self:HandleEvent( EVENTS.Birth ) - - self.CleanUpScheduler = SCHEDULER:New( self, self._CleanUpScheduler, {}, 1, TimeInterval ) - - return self -end - - ---- Destroys a group from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param Dcs.DCSWrapper.Group#Group GroupObject The object to be destroyed. --- @param #string CleanUpGroupName The groupname... -function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) - self:F( { GroupObject, CleanUpGroupName } ) - - if GroupObject then -- and GroupObject:isExist() then - trigger.action.deactivateGroup(GroupObject) - self:T( { "GroupObject Destroyed", GroupObject } ) - end -end - ---- Destroys a @{DCSWrapper.Unit#Unit} from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param Dcs.DCSWrapper.Unit#Unit CleanUpUnit The object to be destroyed. --- @param #string CleanUpUnitName The Unit name ... -function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) - self:F( { CleanUpUnit, CleanUpUnitName } ) - - if CleanUpUnit then - local CleanUpGroup = Unit.getGroup(CleanUpUnit) - -- TODO Client bug in 1.5.3 - if CleanUpGroup and CleanUpGroup:isExist() then - local CleanUpGroupUnits = CleanUpGroup:getUnits() - if #CleanUpGroupUnits == 1 then - local CleanUpGroupName = CleanUpGroup:getName() - --self:CreateEventCrash( timer.getTime(), CleanUpUnit ) - CleanUpGroup:destroy() - self:T( { "Destroyed Group:", CleanUpGroupName } ) - else - CleanUpUnit:destroy() - self:T( { "Destroyed Unit:", CleanUpUnitName } ) - end - self.CleanUpList[CleanUpUnitName] = nil -- Cleaning from the list - CleanUpUnit = nil - end - end -end - --- TODO check Dcs.DCSTypes#Weapon ---- Destroys a missile from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param Dcs.DCSTypes#Weapon MissileObject -function CLEANUP:_DestroyMissile( MissileObject ) - self:F( { MissileObject } ) - - if MissileObject and MissileObject:isExist() then - MissileObject:destroy() - self:T( "MissileObject Destroyed") - end -end - ---- @param #CLEANUP self --- @param Core.Event#EVENTDATA EventData -function CLEANUP:_OnEventBirth( EventData ) - self:F( { EventData } ) - - self.CleanUpList[EventData.IniDCSUnitName] = {} - self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnit = EventData.IniDCSUnit - self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroup = EventData.IniDCSGroup - self.CleanUpList[EventData.IniDCSUnitName].CleanUpGroupName = EventData.IniDCSGroupName - self.CleanUpList[EventData.IniDCSUnitName].CleanUpUnitName = EventData.IniDCSUnitName - - EventData.IniUnit:HandleEvent( EVENTS.EngineShutdown , self._EventAddForCleanUp ) - EventData.IniUnit:HandleEvent( EVENTS.EngineStartup, self._EventAddForCleanUp ) - EventData.IniUnit:HandleEvent( EVENTS.Hit, self._EventAddForCleanUp ) - EventData.IniUnit:HandleEvent( EVENTS.PilotDead, self._EventCrash ) - EventData.IniUnit:HandleEvent( EVENTS.Dead, self._EventCrash ) - EventData.IniUnit:HandleEvent( EVENTS.Crash, self._EventCrash ) - EventData.IniUnit:HandleEvent( EVENTS.Shot, self._EventShot ) - -end - ---- Detects if a crash event occurs. --- Crashed units go into a CleanUpList for removal. --- @param #CLEANUP self --- @param Dcs.DCSTypes#Event event -function CLEANUP:_EventCrash( Event ) - self:F( { Event } ) - - --TODO: This stuff is not working due to a DCS bug. Burning units cannot be destroyed. - -- self:T("before getGroup") - -- local _grp = Unit.getGroup(event.initiator)-- Identify the group that fired - -- self:T("after getGroup") - -- _grp:destroy() - -- self:T("after deactivateGroup") - -- event.initiator:destroy() - - self.CleanUpList[Event.IniDCSUnitName] = {} - self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit = Event.IniDCSUnit - self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup = Event.IniDCSGroup - self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName = Event.IniDCSGroupName - self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName = Event.IniDCSUnitName - -end - ---- Detects if a unit shoots a missile. --- If this occurs within one of the zones, then the weapon used must be destroyed. --- @param #CLEANUP self --- @param Dcs.DCSTypes#Event event -function CLEANUP:_EventShot( Event ) - self:F( { Event } ) - - -- Test if the missile was fired within one of the CLEANUP.ZoneNames. - local CurrentLandingZoneID = 0 - CurrentLandingZoneID = routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) - if ( CurrentLandingZoneID ) then - -- Okay, the missile was fired within the CLEANUP.ZoneNames, destroy the fired weapon. - --_SEADmissile:destroy() - SCHEDULER:New( self, CLEANUP._DestroyMissile, { Event.Weapon }, 0.1 ) - end -end - - ---- Detects if the Unit has an S_EVENT_HIT within the given ZoneNames. If this is the case, destroy the unit. --- @param #CLEANUP self --- @param Dcs.DCSTypes#Event event -function CLEANUP:_EventHitCleanUp( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - if routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) ~= nil then - self:T( { "Life: ", Event.IniDCSUnitName, ' = ', Event.IniDCSUnit:getLife(), "/", Event.IniDCSUnit:getLife0() } ) - if Event.IniDCSUnit:getLife() < Event.IniDCSUnit:getLife0() then - self:T( "CleanUp: Destroy: " .. Event.IniDCSUnitName ) - SCHEDULER:New( self, CLEANUP._DestroyUnit, { Event.IniDCSUnit }, 0.1 ) - end - end - end - - if Event.TgtDCSUnit then - if routines.IsUnitInZones( Event.TgtDCSUnit, self.ZoneNames ) ~= nil then - self:T( { "Life: ", Event.TgtDCSUnitName, ' = ', Event.TgtDCSUnit:getLife(), "/", Event.TgtDCSUnit:getLife0() } ) - if Event.TgtDCSUnit:getLife() < Event.TgtDCSUnit:getLife0() then - self:T( "CleanUp: Destroy: " .. Event.TgtDCSUnitName ) - SCHEDULER:New( self, CLEANUP._DestroyUnit, { Event.TgtDCSUnit }, 0.1 ) - end - end - end -end - ---- Add the @{DCSWrapper.Unit#Unit} to the CleanUpList for CleanUp. -function CLEANUP:_AddForCleanUp( CleanUpUnit, CleanUpUnitName ) - self:F( { CleanUpUnit, CleanUpUnitName } ) - - self.CleanUpList[CleanUpUnitName] = {} - self.CleanUpList[CleanUpUnitName].CleanUpUnit = CleanUpUnit - self.CleanUpList[CleanUpUnitName].CleanUpUnitName = CleanUpUnitName - self.CleanUpList[CleanUpUnitName].CleanUpGroup = Unit.getGroup(CleanUpUnit) - self.CleanUpList[CleanUpUnitName].CleanUpGroupName = Unit.getGroup(CleanUpUnit):getName() - self.CleanUpList[CleanUpUnitName].CleanUpTime = timer.getTime() - self.CleanUpList[CleanUpUnitName].CleanUpMoved = false - - self:T( { "CleanUp: Add to CleanUpList: ", Unit.getGroup(CleanUpUnit):getName(), CleanUpUnitName } ) - -end - ---- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given ZoneNames. If this is the case, add the Group to the CLEANUP List. --- @param #CLEANUP self --- @param Dcs.DCSTypes#Event event -function CLEANUP:_EventAddForCleanUp( Event ) - - if Event.IniDCSUnit then - if self.CleanUpList[Event.IniDCSUnitName] == nil then - if routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) ~= nil then - self:_AddForCleanUp( Event.IniDCSUnit, Event.IniDCSUnitName ) - end - end - end - - if Event.TgtDCSUnit then - if self.CleanUpList[Event.TgtDCSUnitName] == nil then - if routines.IsUnitInZones( Event.TgtDCSUnit, self.ZoneNames ) ~= nil then - self:_AddForCleanUp( Event.TgtDCSUnit, Event.TgtDCSUnitName ) - end - end - end - -end - -local CleanUpSurfaceTypeText = { - "LAND", - "SHALLOW_WATER", - "WATER", - "ROAD", - "RUNWAY" - } - ---- At the defined time interval, CleanUp the Groups within the CleanUpList. --- @param #CLEANUP self -function CLEANUP:_CleanUpScheduler() - self:F( { "CleanUp Scheduler" } ) - - local CleanUpCount = 0 - for CleanUpUnitName, UnitData in pairs( self.CleanUpList ) do - CleanUpCount = CleanUpCount + 1 - - self:T( { CleanUpUnitName, UnitData } ) - local CleanUpUnit = Unit.getByName(UnitData.CleanUpUnitName) - local CleanUpGroupName = UnitData.CleanUpGroupName - local CleanUpUnitName = UnitData.CleanUpUnitName - if CleanUpUnit then - self:T( { "CleanUp Scheduler", "Checking:", CleanUpUnitName } ) - if _DATABASE:GetStatusGroup( CleanUpGroupName ) ~= "ReSpawn" then - local CleanUpUnitVec3 = CleanUpUnit:getPoint() - --self:T( CleanUpUnitVec3 ) - local CleanUpUnitVec2 = {} - CleanUpUnitVec2.x = CleanUpUnitVec3.x - CleanUpUnitVec2.y = CleanUpUnitVec3.z - --self:T( CleanUpUnitVec2 ) - local CleanUpSurfaceType = land.getSurfaceType(CleanUpUnitVec2) - --self:T( CleanUpSurfaceType ) - - if CleanUpUnit and CleanUpUnit:getLife() <= CleanUpUnit:getLife0() * 0.95 then - if CleanUpSurfaceType == land.SurfaceType.RUNWAY then - if CleanUpUnit:inAir() then - local CleanUpLandHeight = land.getHeight(CleanUpUnitVec2) - local CleanUpUnitHeight = CleanUpUnitVec3.y - CleanUpLandHeight - self:T( { "CleanUp Scheduler", "Height = " .. CleanUpUnitHeight } ) - if CleanUpUnitHeight < 30 then - self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because below safe height and damaged." } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - else - self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because on runway and damaged." } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - end - end - -- Clean Units which are waiting for a very long time in the CleanUpZone. - if CleanUpUnit then - local CleanUpUnitVelocity = CleanUpUnit:getVelocity() - local CleanUpUnitVelocityTotal = math.abs(CleanUpUnitVelocity.x) + math.abs(CleanUpUnitVelocity.y) + math.abs(CleanUpUnitVelocity.z) - if CleanUpUnitVelocityTotal < 1 then - if UnitData.CleanUpMoved then - if UnitData.CleanUpTime + 180 <= timer.getTime() then - self:T( { "CleanUp Scheduler", "Destroy due to not moving anymore " .. CleanUpUnitName } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - end - else - UnitData.CleanUpTime = timer.getTime() - UnitData.CleanUpMoved = true - end - end - - else - -- Do nothing ... - self.CleanUpList[CleanUpUnitName] = nil -- Not anymore in the DCSRTE - end - else - self:T( "CleanUp: Group " .. CleanUpUnitName .. " cannot be found in DCS RTE, removing ..." ) - self.CleanUpList[CleanUpUnitName] = nil -- Not anymore in the DCSRTE - end - end - self:T(CleanUpCount) - - return true -end - ---- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**All** -- --- **Spawn groups of units dynamically in your missions.** --- --- ![Banner Image](..\Presentations\SPAWN\SPAWN.JPG) --- --- === --- --- # 1) @{#SPAWN} class, extends @{Base#BASE} --- --- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned. --- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object. --- A reference to this Spawn Template needs to be provided when constructing the SPAWN object, by indicating the name of the group within the mission editor in the constructor methods. --- --- Within the SPAWN object, there is an internal index that keeps track of which group from the internal group list was spawned. --- When new groups get spawned by using the SPAWN methods (see below), it will be validated whether the Limits (@{#SPAWN.Limit}) of the SPAWN object are not reached. --- When all is valid, a new group will be created by the spawning methods, and the internal index will be increased with 1. --- --- Regarding the name of new spawned groups, a _SpawnPrefix_ will be assigned for each new group created. --- If you want to have the Spawn Template name to be used as the _SpawnPrefix_ name, use the @{#SPAWN.New} constructor. --- However, when the @{#SPAWN.NewWithAlias} constructor was used, the Alias name will define the _SpawnPrefix_ name. --- Groups will follow the following naming structure when spawned at run-time: --- --- 1. Spawned groups will have the name _SpawnPrefix_#ggg, where ggg is a counter from 0 to 999. --- 2. Spawned units will have the name _SpawnPrefix_#ggg-uu, where uu is a counter from 0 to 99 for each new spawned unit belonging to the group. --- --- Some additional notes that need to be remembered: --- --- * Templates are actually groups defined within the mission editor, with the flag "Late Activation" set. As such, these groups are never used within the mission, but are used by the @{#SPAWN} module. --- * It is important to defined BEFORE you spawn new groups, a proper initialization of the SPAWN instance is done with the options you want to use. --- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn Template(s), or the SPAWN module logic won't work anymore. --- --- ## 1.1) SPAWN construction methods --- --- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods: --- --- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). --- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition), and gives each spawned @{Group} an different name. --- --- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned. --- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons. --- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient. --- --- ## 1.2) SPAWN initialization methods --- --- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: --- --- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!! --- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height. --- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled. --- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- * @{#SPAWN.InitRepeat}(): Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. --- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. --- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius. --- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. --- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Group} object. --- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Group} object. --- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Group} object. --- --- ## 1.3) SPAWN spawning methods --- --- Groups can be spawned at different times and methods: --- --- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index. --- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index. --- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart}() and @{#SPAWN.SpawnScheduleStop}() to start and stop the schedule respectively. --- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). --- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). --- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}. --- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}. --- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. --- --- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object. --- You can use the @{GROUP} object to do further actions with the DCSGroup. --- --- ## 1.4) Retrieve alive GROUPs spawned by the SPAWN object --- --- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution. --- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS. --- SPAWN provides methods to iterate through that internal GROUP object reference table: --- --- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. --- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. --- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. --- --- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. --- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive... --- --- ## 1.5) SPAWN object cleaning --- --- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive. --- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, --- and it may occur that no new groups are or can be spawned as limits are reached. --- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group. --- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. --- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... --- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. --- This models AI that has succesfully returned to their airbase, to restart their combat activities. --- Check the @{#SPAWN.InitCleanUp}() for further info. --- --- ## 1.6) Catch the @{Group} spawn event in a callback function! --- --- When using the SpawnScheduled method, new @{Group}s are created following the schedule timing parameters. --- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. --- To SPAWN class supports this functionality through the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method, which takes a function as a parameter that you can define locally. --- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter. --- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object. --- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-14: SPAWN:**InitKeepUnitNames()** added. --- 2017-03-14: SPAWN:**InitRandomizePosition( RandomizePosition, OuterRadious, InnerRadius )** added. --- --- 2017-02-04: SPAWN:InitUnControlled( **UnControlled** ) replaces SPAWN:InitUnControlled(). --- --- 2017-01-24: SPAWN:**InitAIOnOff( AIOnOff )** added. --- --- 2017-01-24: SPAWN:**InitAIOn()** added. --- --- 2017-01-24: SPAWN:**InitAIOff()** added. --- --- 2016-08-15: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ). --- --- 2016-08-15: SPAWN:**InitRandomizeZones( SpawnZones )** added. --- --- 2016-08-14: SPAWN:**OnSpawnGroup**( SpawnCallBackFunction, ... ) replaces SPAWN:_SpawnFunction_( SpawnCallBackFunction, ... ). --- --- 2016-08-14: SPAWN.SpawnInZone( Zone, __RandomizeGroup__, SpawnIndex ) replaces SpawnInZone( Zone, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ). --- --- 2016-08-14: SPAWN.SpawnFromVec3( Vec3, SpawnIndex ) replaces SpawnFromVec3( Vec3, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): --- --- 2016-08-14: SPAWN.SpawnFromVec2( Vec2, SpawnIndex ) replaces SpawnFromVec2( Vec2, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): --- --- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromUnit( SpawnUnit, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): --- --- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromStatic( SpawnStatic, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): --- --- 2016-08-14: SPAWN.**InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )** added: --- --- 2016-08-14: SPAWN.**Init**Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) replaces SPAWN._Limit_( SpawnMaxUnitsAlive, SpawnMaxGroups ): --- --- 2016-08-14: SPAWN.**Init**Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) replaces SPAWN._Array_( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ). --- --- 2016-08-14: SPAWN.**Init**RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) replaces SPAWN._RandomizeRoute_( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ). --- --- 2016-08-14: SPAWN.**Init**RandomizeTemplate( SpawnTemplatePrefixTable ) replaces SPAWN._RandomizeTemplate_( SpawnTemplatePrefixTable ). --- --- 2016-08-14: SPAWN.**Init**UnControlled() replaces SPAWN._UnControlled_(). --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization. --- * [**Entropy**](https://forums.eagle.ru/member.php?u=111471), **Afinegan**: Came up with the requirement for AIOnOff(). --- --- ### Authors: --- --- * **FlightControl**: Design & Programming --- --- @module Spawn - - - ---- SPAWN Class --- @type SPAWN --- @extends Core.Base#BASE --- @field ClassName --- @field #string SpawnTemplatePrefix --- @field #string SpawnAliasPrefix --- @field #number AliveUnits --- @field #number MaxAliveUnits --- @field #number SpawnIndex --- @field #number MaxAliveGroups --- @field #SPAWN.SpawnZoneTable SpawnZoneTable -SPAWN = { - ClassName = "SPAWN", - SpawnTemplatePrefix = nil, - SpawnAliasPrefix = nil, -} - - ---- @type SPAWN.SpawnZoneTable --- @list SpawnZone - - ---- Creates the main object to spawn a @{Group} defined in the DCS ME. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ) --- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME. -function SPAWN:New( SpawnTemplatePrefix ) - local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN - self:F( { SpawnTemplatePrefix } ) - - local TemplateGroup = Group.getByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. - self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end - - return self -end - ---- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. --- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) --- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME. -function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) - - local TemplateGroup = Group.getByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnAliasPrefix = SpawnAliasPrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnInitLimit = false -- By default, no InitLimit - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - self.AIOnOff = true -- The AI is on by default when spawning a group. - self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end - - return self -end - - ---- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned. --- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. --- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used... --- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. --- @param #SPAWN self --- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. --- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group. --- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area. --- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time. --- @return #SPAWN self --- @usage --- -- NATO helicopters engaging in the battle field. --- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. --- -- There will be maximum 24 groups spawned during the whole mission lifetime. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) -function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) - self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) - - self.SpawnInitLimit = true - self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned. - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_InitializeSpawnGroups( SpawnGroupID ) - end - - return self -end - ---- Keeps the unit names as defined within the mission editor, --- but note that anything after a # mark is ignored, --- and any spaces before and after the resulting name are removed. --- IMPORTANT! This method MUST be the first used after :New !!! --- @param #SPAWN self --- @return #SPAWN self -function SPAWN:InitKeepUnitNames() - self:F( ) - - self.SpawnInitKeepUnitNames = true - - return self -end - - ---- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups. --- @param #SPAWN self --- @param #number SpawnStartPoint is the waypoint where the randomization begins. --- Note that the StartPoint = 0 equaling the point where the group is spawned. --- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards. --- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route. --- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ... --- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) -function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) - self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) - - self.SpawnRandomizeRoute = true - self.SpawnRandomizeRouteStartPoint = SpawnStartPoint - self.SpawnRandomizeRouteEndPoint = SpawnEndPoint - self.SpawnRandomizeRouteRadius = SpawnRadius - self.SpawnRandomizeRouteHeight = SpawnHeight - - for GroupID = 1, self.SpawnMaxGroups do - self:_RandomizeRoute( GroupID ) - end - - return self -end - ---- Randomizes the position of @{Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. --- @param #SPAWN self --- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Group}s position between a given outer and inner radius. --- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. --- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. --- @return #SPAWN -function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius ) - self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } ) - - self.SpawnRandomizePosition = RandomizePosition or false - self.SpawnRandomizePositionOuterRadius = OuterRadius or 0 - self.SpawnRandomizePositionInnerRadius = InnerRadius or 0 - - for GroupID = 1, self.SpawnMaxGroups do - self:_RandomizeRoute( GroupID ) - end - - return self -end - - ---- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. --- @param #SPAWN self --- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. --- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. --- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) -function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) - self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) - - self.SpawnRandomizeUnits = RandomizeUnits or false - self.SpawnOuterRadius = OuterRadius or 0 - self.SpawnInnerRadius = InnerRadius or 0 - - for GroupID = 1, self.SpawnMaxGroups do - self:_RandomizeRoute( GroupID ) - end - - return self -end - ---- This method is rather complicated to understand. But I'll try to explain. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, --- but they will all follow the same Template route and have the same prefix name. --- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. --- @param #SPAWN self --- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned. --- @return #SPAWN --- @usage --- -- NATO Tank Platoons invading Gori. --- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes. --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', --- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', --- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) -function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) - self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) - - self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable - self.SpawnRandomizeTemplate = true - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeTemplate( SpawnGroupID ) - end - - return self -end - ---TODO: Add example. ---- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. --- @param #SPAWN self --- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. --- @return #SPAWN --- @usage --- -- NATO Tank Platoons invading Gori. --- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type. -function SPAWN:InitRandomizeZones( SpawnZoneTable ) - self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) - - self.SpawnZoneTable = SpawnZoneTable - self.SpawnRandomizeZones = true - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeZones( SpawnGroupID ) - end - - return self -end - - - - - ---- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. --- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... --- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. --- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ... --- @param #SPAWN self --- @return #SPAWN self --- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() -function SPAWN:InitRepeat() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) - - self.Repeat = true - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self -end - ---- Respawn group after landing. --- @param #SPAWN self --- @return #SPAWN self -function SPAWN:InitRepeatOnLanding() - self:F( { self.SpawnTemplatePrefix } ) - - self:InitRepeat() - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self -end - - ---- Respawn after landing when its engines have shut down. --- @param #SPAWN self --- @return #SPAWN self -function SPAWN:InitRepeatOnEngineShutDown() - self:F( { self.SpawnTemplatePrefix } ) - - self:InitRepeat() - self.RepeatOnEngineShutDown = true - self.RepeatOnLanding = false - - return self -end - - ---- CleanUp groups when they are still alive, but inactive. --- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds. --- @param #SPAWN self --- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds. --- @return #SPAWN self --- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. -function SPAWN:InitCleanUp( SpawnCleanUpInterval ) - self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) - - self.SpawnCleanUpInterval = SpawnCleanUpInterval - self.SpawnCleanUpTimeStamps = {} - - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup } ) - - --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) - self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) - return self -end - - - ---- Makes the groups visible before start (like a batallion). --- The method will take the position of the group as the first position in the array. --- @param #SPAWN self --- @param #number SpawnAngle The angle in degrees how the groups and each unit of the group will be positioned. --- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis. --- @param #number SpawnDeltaX The space between each Group on the X-axis. --- @param #number SpawnDeltaY The space between each Group on the Y-axis. --- @return #SPAWN self --- @usage --- -- Define an array of Groups. --- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 ) -function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) - self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) - - self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. - - local SpawnX = 0 - local SpawnY = 0 - local SpawnXIndex = 0 - local SpawnYIndex = 0 - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) - - self.SpawnGroups[SpawnGroupID].Visible = true - self.SpawnGroups[SpawnGroupID].Spawned = false - - SpawnXIndex = SpawnXIndex + 1 - if SpawnWidth and SpawnWidth ~= 0 then - if SpawnXIndex >= SpawnWidth then - SpawnXIndex = 0 - SpawnYIndex = SpawnYIndex + 1 - end - end - - local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x - local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y - - self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - - self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true - self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true - - self.SpawnGroups[SpawnGroupID].Visible = true - - _EVENTDISPATCHER:OnBirthForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self ) - - if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnLand, self ) - end - if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnEngineShutDown, self ) - end - - self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate ) - - SpawnX = SpawnXIndex * SpawnDeltaX - SpawnY = SpawnYIndex * SpawnDeltaY - end - - return self -end - -do -- AI methods - --- Turns the AI On or Off for the @{Group} when spawning. - -- @param #SPAWN self - -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off. - -- @return #SPAWN The SPAWN object - function SPAWN:InitAIOnOff( AIOnOff ) - - self.AIOnOff = AIOnOff - return self - end - - --- Turns the AI On for the @{Group} when spawning. - -- @param #SPAWN self - -- @return #SPAWN The SPAWN object - function SPAWN:InitAIOn() - - return self:InitAIOnOff( true ) - end - - --- Turns the AI Off for the @{Group} when spawning. - -- @param #SPAWN self - -- @return #SPAWN The SPAWN object - function SPAWN:InitAIOff() - - return self:InitAIOnOff( false ) - end - -end -- AI methods - ---- Will spawn a group based on the internal index. --- Note: Uses @{DATABASE} module defined in MOOSE. --- @param #SPAWN self --- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:Spawn() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) - - return self:SpawnWithIndex( self.SpawnIndex + 1 ) -end - ---- Will re-spawn a group based on a given index. --- Note: Uses @{DATABASE} module defined in MOOSE. --- @param #SPAWN self --- @param #string SpawnIndex The index of the group to be spawned. --- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:ReSpawn( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end - --- TODO: This logic makes DCS crash and i don't know why (yet). - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil - if SpawnGroup then - local SpawnDCSGroup = SpawnGroup:GetDCSObject() - if SpawnDCSGroup then - SpawnGroup:Destroy() - end - end - - local SpawnGroup = self:SpawnWithIndex( SpawnIndex ) - if SpawnGroup and WayPoints then - -- If there were WayPoints set, then Re-Execute those WayPoints! - SpawnGroup:WayPointInitialize( WayPoints ) - SpawnGroup:WayPointExecute( 1, 5 ) - end - - if SpawnGroup.ReSpawnFunction then - SpawnGroup:ReSpawnFunction() - end - - return SpawnGroup -end - ---- Will spawn a group with a specified index number. --- Uses @{DATABASE} global object defined in MOOSE. --- @param #SPAWN self --- @param #string SpawnIndex The index of the group to be spawned. --- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:SpawnWithIndex( SpawnIndex ) - self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) - - if self:_GetSpawnIndex( SpawnIndex ) then - - if self.SpawnGroups[self.SpawnIndex].Visible then - self.SpawnGroups[self.SpawnIndex].Group:Activate() - else - - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - self:T( SpawnTemplate.name ) - - if SpawnTemplate then - - local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) - self:T( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) - - -- If RandomizePosition, then Randomize the formation in the zone band, keeping the template. - if self.SpawnRandomizePosition then - local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnRandomizePositionOuterRadius, self.SpawnRandomizePositionInnerRadius ) - local CurrentX = SpawnTemplate.units[1].x - local CurrentY = SpawnTemplate.units[1].y - SpawnTemplate.x = RandomVec2.x - SpawnTemplate.y = RandomVec2.y - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].x = SpawnTemplate.units[UnitID].x + ( RandomVec2.x - CurrentX ) - SpawnTemplate.units[UnitID].y = SpawnTemplate.units[UnitID].y + ( RandomVec2.y - CurrentY ) - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - end - - -- If RandomizeUnits, then Randomize the formation at the start point. - if self.SpawnRandomizeUnits then - for UnitID = 1, #SpawnTemplate.units do - local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) - SpawnTemplate.units[UnitID].x = RandomVec2.x - SpawnTemplate.units[UnitID].y = RandomVec2.y - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - end - - if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then - if SpawnTemplate.route.points[1].type == "TakeOffParking" then - SpawnTemplate.uncontrolled = self.SpawnUnControlled - end - end - end - - _EVENTDISPATCHER:OnBirthForTemplate( SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) - - if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( SpawnTemplate, self._OnLand, self ) - end - if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( SpawnTemplate, self._OnEngineShutDown, self ) - end - self:T3( SpawnTemplate.name ) - - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) - - local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP - - --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there! - if SpawnGroup then - - SpawnGroup:SetAIOnOff( self.AIOnOff ) - end - - -- If there is a SpawnFunction hook defined, call it. - if self.SpawnFunctionHook then - self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) ) - end - -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. - --if self.Repeat then - -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) - --end - end - - self.SpawnGroups[self.SpawnIndex].Spawned = true - return self.SpawnGroups[self.SpawnIndex].Group - else - --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) - end - - return nil -end - ---- Spawns new groups at varying time intervals. --- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions. --- @param #SPAWN self --- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. --- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. --- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval. --- @return #SPAWN self --- @usage --- -- NATO helicopters engaging in the battle field. --- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%. --- -- The time variation in this case will be between 450 seconds and 750 seconds. --- -- This is calculated as follows: --- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450 --- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750 --- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 ) -function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) - self:F( { SpawnTime, SpawnTimeVariation } ) - - if SpawnTime ~= nil and SpawnTimeVariation ~= nil then - self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, 1, SpawnTime, SpawnTimeVariation ) - end - - return self -end - ---- Will re-start the spawning scheduler. --- Note: This method is only required to be called when the schedule was stopped. -function SPAWN:SpawnScheduleStart() - self:F( { self.SpawnTemplatePrefix } ) - - self.SpawnScheduler:Start() -end - ---- Will stop the scheduled spawning scheduler. -function SPAWN:SpawnScheduleStop() - self:F( { self.SpawnTemplatePrefix } ) - - self.SpawnScheduler:Stop() -end - - ---- Allows to place a CallFunction hook when a new group spawns. --- The provided method will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned. --- @param #SPAWN self --- @param #function SpawnCallBackFunction The function to be called when a group spawns. --- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. --- @return #SPAWN --- @usage --- -- Declare SpawnObject and call a function when a new Group is spawned. --- local SpawnObject = SPAWN --- :New( "SpawnObject" ) --- :InitLimit( 2, 10 ) --- :OnSpawnGroup( --- function( SpawnGroup ) --- SpawnGroup:E( "I am spawned" ) --- end --- ) --- :SpawnScheduled( 300, 0.3 ) -function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) - self:F( "OnSpawnGroup" ) - - self.SpawnFunctionHook = SpawnCallBackFunction - self.SpawnFunctionArguments = {} - if arg then - self.SpawnFunctionArguments = arg - end - - return self -end - - ---- Will spawn a group from a Vec3 in 3D space. --- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. --- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) - - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2(PointVec3) - - if SpawnIndex then - else - SpawnIndex = self.SpawnIndex + 1 - end - - if self:_GetSpawnIndex( SpawnIndex ) then - - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - - if SpawnTemplate then - - self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) - - -- Translate the position of the Group Template to the Vec3. - for UnitID = 1, #SpawnTemplate.units do - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - local UnitTemplate = SpawnTemplate.units[UnitID] - local SX = UnitTemplate.x - local SY = UnitTemplate.y - local BX = SpawnTemplate.route.points[1].x - local BY = SpawnTemplate.route.points[1].y - local TX = Vec3.x + ( SX - BX ) - local TY = Vec3.z + ( SY - BY ) - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY - SpawnTemplate.units[UnitID].alt = Vec3.y - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - - SpawnTemplate.route.points[1].x = Vec3.x - SpawnTemplate.route.points[1].y = Vec3.z - SpawnTemplate.route.points[1].alt = Vec3.y - - SpawnTemplate.x = Vec3.x - SpawnTemplate.y = Vec3.z - - return self:SpawnWithIndex( self.SpawnIndex ) - end - end - - return nil -end - ---- Will spawn a group from a Vec2 in 3D space. --- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. --- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec2( Vec2, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } ) - - local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex ) -end - - ---- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. --- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } ) - - if HostUnit and HostUnit:IsAlive() then -- and HostUnit:getUnit(1):inAir() == false then - return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex ) - end - - return nil -end - ---- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings). --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } ) - - if HostStatic and HostStatic:IsAlive() then - return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex ) - end - - return nil -end - ---- Will spawn a Group within a given @{Zone}. --- The @{Zone} can be of any type derived from @{Zone#ZONE_BASE}. --- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route. --- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates. --- @param #SPAWN self --- @param Core.Zone#ZONE Zone The zone where the group is to be spawned. --- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone. --- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil when nothing was spawned. -function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } ) - - if Zone then - if RandomizeGroup then - return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex ) - else - return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex ) - end - end - - return nil -end - ---- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode... --- This will be similar to the uncontrolled flag setting in the ME. --- You can use UnControlled mode to simulate planes startup and ready for take-off but aren't moving (yet). --- ReSpawn the plane in Controlled mode, and the plane will move... --- @param #SPAWN self --- @param #boolean UnControlled true if UnControlled, false if Controlled. --- @return #SPAWN self -function SPAWN:InitUnControlled( UnControlled ) - self:F2( { self.SpawnTemplatePrefix, UnControlled } ) - - self.SpawnUnControlled = UnControlled - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self.SpawnGroups[SpawnGroupID].UnControlled = UnControlled - end - - return self -end - - - ---- Will return the SpawnGroupName either with with a specific count number or without any count. --- @param #SPAWN self --- @param #number SpawnIndex Is the number of the Group that is to be spawned. --- @return #string SpawnGroupName -function SPAWN:SpawnGroupName( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - local SpawnPrefix = self.SpawnTemplatePrefix - if self.SpawnAliasPrefix then - SpawnPrefix = self.SpawnAliasPrefix - end - - if SpawnIndex then - local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex ) - self:T( SpawnName ) - return SpawnName - else - self:T( SpawnPrefix ) - return SpawnPrefix - end - -end - ---- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found. --- @param #SPAWN self --- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found. --- @return #nil, #nil When no group is found, #nil is returned. --- @usage --- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end -function SPAWN:GetFirstAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - for SpawnIndex = 1, self.SpawnCount do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - return SpawnGroup, SpawnIndex - end - end - - return nil, nil -end - - ---- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found. --- @param #SPAWN self --- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index. --- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found. --- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned. --- @usage --- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end -function SPAWN:GetNextAliveGroup( SpawnIndexStart ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) - - SpawnIndexStart = SpawnIndexStart + 1 - for SpawnIndex = SpawnIndexStart, self.SpawnCount do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - return SpawnGroup, SpawnIndex - end - end - - return nil, nil -end - ---- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found. --- @param #SPAWN self --- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found. --- @return #nil, #nil When no alive @{Group} object is found, #nil is returned. --- @usage --- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. --- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() --- if GroupPlane then -- GroupPlane can be nil!!! --- -- Do actions with the GroupPlane object. --- end -function SPAWN:GetLastAliveGroup() - self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } ) - - self.SpawnIndex = self:_GetLastIndex() - for SpawnIndex = self.SpawnIndex, 1, -1 do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - self.SpawnIndex = SpawnIndex - return SpawnGroup - end - end - - self.SpawnIndex = nil - return nil -end - - - ---- Get the group from an index. --- Returns the group from the SpawnGroups list. --- If no index is given, it will return the first group in the list. --- @param #SPAWN self --- @param #number SpawnIndex The index of the group to return. --- @return Wrapper.Group#GROUP self -function SPAWN:GetGroupFromIndex( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end - - if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then - local SpawnGroup = self.SpawnGroups[SpawnIndex].Group - return SpawnGroup - else - return nil - end -end - ---- Get the group index from a DCSUnit. --- The method will search for a #-mark, and will return the index behind the #-mark of the DCSUnit. --- It will return nil of no prefix was found. --- @param #SPAWN self --- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return #string The prefix --- @return #nil Nothing found -function SPAWN:_GetGroupIndexFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local SpawnUnitName = ( DCSUnit and DCSUnit:getName() ) or nil - if SpawnUnitName then - local IndexString = string.match( SpawnUnitName, "#.*-" ):sub( 2, -2 ) - if IndexString then - local Index = tonumber( IndexString ) - return Index - end - end - - return nil -end - ---- Return the prefix of a SpawnUnit. --- The method will search for a #-mark, and will return the text before the #-mark. --- It will return nil of no prefix was found. --- @param #SPAWN self --- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched. --- @return #string The prefix --- @return #nil Nothing found -function SPAWN:_GetPrefixFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local DCSGroup = DCSUnit:getGroup() - local DCSUnitName = ( DCSGroup and DCSGroup:getName() ) or nil - if DCSUnitName then - local SpawnPrefix = string.match( DCSUnitName, ".*#" ) - if SpawnPrefix then - SpawnPrefix = SpawnPrefix:sub( 1, -2 ) - end - return SpawnPrefix - end - - return nil -end - ---- Return the group within the SpawnGroups collection with input a DCSUnit. --- @param #SPAWN self --- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return Wrapper.Group#GROUP The Group --- @return #nil Nothing found -function SPAWN:_GetGroupFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local SpawnPrefix = self:_GetPrefixFromDCSUnit( DCSUnit ) - - if self.SpawnTemplatePrefix == SpawnPrefix or ( self.SpawnAliasPrefix and self.SpawnAliasPrefix == SpawnPrefix ) then - local SpawnGroupIndex = self:_GetGroupIndexFromDCSUnit( DCSUnit ) - local SpawnGroup = self.SpawnGroups[SpawnGroupIndex].Group - self:T( SpawnGroup ) - return SpawnGroup - end - - return nil -end - - ---- Get the index from a given group. --- The function will search the name of the group for a #, and will return the number behind the #-mark. -function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) - - local IndexString = string.match( SpawnGroup:GetName(), "#.*$" ):sub( 2 ) - local Index = tonumber( IndexString ) - - self:T3( IndexString, Index ) - return Index - -end - ---- Return the last maximum index that can be used. -function SPAWN:_GetLastIndex() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - return self.SpawnMaxGroups -end - ---- Initalize the SpawnGroups collection. --- @param #SPAWN self -function SPAWN:_InitializeSpawnGroups( SpawnIndex ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) - - if not self.SpawnGroups[SpawnIndex] then - self.SpawnGroups[SpawnIndex] = {} - self.SpawnGroups[SpawnIndex].Visible = false - self.SpawnGroups[SpawnIndex].Spawned = false - self.SpawnGroups[SpawnIndex].UnControlled = false - self.SpawnGroups[SpawnIndex].SpawnTime = 0 - - self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix - self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) - end - - self:_RandomizeTemplate( SpawnIndex ) - self:_RandomizeRoute( SpawnIndex ) - --self:_TranslateRotate( SpawnIndex ) - - return self.SpawnGroups[SpawnIndex] -end - - - ---- Gets the CategoryID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCategoryID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCategory() - else - return nil - end -end - ---- Gets the CoalitionID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCoalitionID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCoalition() - else - return nil - end -end - ---- Gets the CountryID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCountryID( SpawnPrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } ) - - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - local TemplateUnits = TemplateGroup:getUnits() - return TemplateUnits[1]:getCountry() - else - return nil - end -end - ---- Gets the Group Template from the ME environment definition. --- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix --- @return @SPAWN self -function SPAWN:_GetTemplate( SpawnTemplatePrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) - - local SpawnTemplate = nil - - SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) - - if SpawnTemplate == nil then - error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) - end - - --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) - - self:T3( { SpawnTemplate } ) - return SpawnTemplate -end - ---- Prepares the new Group Template. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix --- @param #number SpawnIndex --- @return #SPAWN self -function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) - SpawnTemplate.name = self:SpawnGroupName( SpawnIndex ) - - SpawnTemplate.groupId = nil - --SpawnTemplate.lateActivation = false - SpawnTemplate.lateActivation = false - - if SpawnTemplate.CategoryID == Group.Category.GROUND then - self:T3( "For ground units, visible needs to be false..." ) - SpawnTemplate.visible = false - end - - if self.SpawnInitKeepUnitNames == false then - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) - SpawnTemplate.units[UnitID].unitId = nil - end - else - for UnitID = 1, #SpawnTemplate.units do - local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) - self:T( { UnitPrefix, Rest } ) - - SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) - SpawnTemplate.units[UnitID].unitId = nil - end - end - - self:T3( { "Template:", SpawnTemplate } ) - return SpawnTemplate - -end - ---- Private method randomizing the routes. --- @param #SPAWN self --- @param #number SpawnIndex The index of the group to be spawned. --- @return #SPAWN -function SPAWN:_RandomizeRoute( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) - - if self.SpawnRandomizeRoute then - local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate - local RouteCount = #SpawnTemplate.route.points - - for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do - - SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) - SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) - - -- Manage randomization of altitude for airborne units ... - if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then - if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then - SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight ) - end - else - SpawnTemplate.route.points[t].alt = nil - end - - self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) - end - end - - self:_RandomizeZones( SpawnIndex ) - - return self -end - ---- Private method that randomizes the template of the group. --- @param #SPAWN self --- @param #number SpawnIndex --- @return #SPAWN self -function SPAWN:_RandomizeTemplate( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) - - if self.SpawnRandomizeTemplate then - self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ] - self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x - self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y - self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time - local OldX = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].x - local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y - for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt - end - end - - self:_RandomizeRoute( SpawnIndex ) - - return self -end - ---- Private method that randomizes the @{Zone}s where the Group will be spawned. --- @param #SPAWN self --- @param #number SpawnIndex --- @return #SPAWN self -function SPAWN:_RandomizeZones( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) - - if self.SpawnRandomizeZones then - local SpawnZone = nil -- Core.Zone#ZONE_BASE - while not SpawnZone do - self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) - local ZoneID = math.random( #self.SpawnZoneTable ) - self:T( ZoneID ) - SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe() - end - - self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) - - local SpawnVec2 = SpawnZone:GetRandomVec2() - - self:T( { SpawnVec2 = SpawnVec2 } ) - - local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate - - self:T( { Route = SpawnTemplate.route } ) - - for UnitID = 1, #SpawnTemplate.units do - local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) - local SX = UnitTemplate.x - local SY = UnitTemplate.y - local BX = SpawnTemplate.route.points[1].x - local BY = SpawnTemplate.route.points[1].y - local TX = SpawnVec2.x + ( SX - BX ) - local TY = SpawnVec2.y + ( SY - BY ) - UnitTemplate.x = TX - UnitTemplate.y = TY - -- TODO: Manage altitude based on landheight... - --SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) - end - SpawnTemplate.x = SpawnVec2.x - SpawnTemplate.y = SpawnVec2.y - SpawnTemplate.route.points[1].x = SpawnVec2.x - SpawnTemplate.route.points[1].y = SpawnVec2.y - end - - return self - -end - -function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) - - -- Translate - local TranslatedX = SpawnX - local TranslatedY = SpawnY - - -- Rotate - -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations - -- x' = x \cos \theta - y \sin \theta\ - -- y' = x \sin \theta + y \cos \theta\ - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - - -- Assign - self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX - self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY - - - local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units ) - for u = 1, SpawnUnitCount do - - -- Translate - local TranslatedX = SpawnX - local TranslatedY = SpawnY - 10 * ( u - 1 ) - - -- Rotate - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - - -- Assign - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle ) - end - - return self -end - ---- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces. -function SPAWN:_GetSpawnIndex( SpawnIndex ) - self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) - - if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then - if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then - if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then - self.SpawnCount = self.SpawnCount + 1 - SpawnIndex = self.SpawnCount - end - self.SpawnIndex = SpawnIndex - if not self.SpawnGroups[self.SpawnIndex] then - self:_InitializeSpawnGroups( self.SpawnIndex ) - end - else - return nil - end - else - return nil - end - - return self.SpawnIndex -end - - --- TODO Need to delete this... _DATABASE does this now ... - ---- @param #SPAWN self --- @param Core.Event#EVENTDATA Event -function SPAWN:_OnBirth( Event ) - - if timer.getTime0() < timer.getAbsTime() then - if Event.IniDCSUnit then - local EventPrefix = self:_GetPrefixFromDCSUnit( Event.IniDCSUnit ) - self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits + 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end - end - end - -end - ---- Obscolete --- @todo Need to delete this... _DATABASE does this now ... - ---- @param #SPAWN self --- @param Core.Event#EVENTDATA Event -function SPAWN:_OnDeadOrCrash( Event ) - self:F( self.SpawnTemplatePrefix, Event ) - - if Event.IniDCSUnit then - local EventPrefix = self:_GetPrefixFromDCSUnit( Event.IniDCSUnit ) - self:T( { "Dead event: " .. EventPrefix, self.SpawnTemplatePrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits - 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end - end -end - ---- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne... --- This is needed to ensure that Re-SPAWNing only is done for landed AIR Groups. --- @todo Need to test for AIR Groups only... -function SPAWN:_OnTakeOff( event ) - self:F( self.SpawnTemplatePrefix, event ) - - if event.initiator and event.initiator:getName() then - local SpawnGroup = self:_GetGroupFromDCSUnit( event.initiator ) - if SpawnGroup then - self:T( { "TakeOff event: " .. event.initiator:getName(), event } ) - self:T( "self.Landed = false" ) - self.Landed = false - end - end -end - ---- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed. --- This is needed to ensure that Re-SPAWNing is only done for landed AIR Groups. --- @todo Need to test for AIR Groups only... -function SPAWN:_OnLand( event ) - self:F( self.SpawnTemplatePrefix, event ) - - local SpawnUnit = event.initiator - if SpawnUnit and SpawnUnit:isExist() and Object.getCategory(SpawnUnit) == Object.Category.UNIT then - local SpawnGroup = self:_GetGroupFromDCSUnit( SpawnUnit ) - if SpawnGroup then - self:T( { "Landed event:" .. SpawnUnit:getName(), event } ) - self.Landed = true - self:T( "self.Landed = true" ) - if self.Landed and self.RepeatOnLanding then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - self:ReSpawn( SpawnGroupIndex ) - end - end - end -end - ---- Will detect AIR Units shutting down their engines ... --- When the event takes place, and the method @{RepeatOnEngineShutDown} was called, the spawned Group will Re-SPAWN. --- But only when the Unit was registered to have landed. --- @param #SPAWN self --- @see _OnTakeOff --- @see _OnLand --- @todo Need to test for AIR Groups only... -function SPAWN:_OnEngineShutDown( event ) - self:F( self.SpawnTemplatePrefix, event ) - - local SpawnUnit = event.initiator - if SpawnUnit and SpawnUnit:isExist() and Object.getCategory(SpawnUnit) == Object.Category.UNIT then - local SpawnGroup = self:_GetGroupFromDCSUnit( SpawnUnit ) - if SpawnGroup then - self:T( { "EngineShutDown event: " .. SpawnUnit:getName(), event } ) - if self.Landed and self.RepeatOnEngineShutDown then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - self:ReSpawn( SpawnGroupIndex ) - end - end - end -end - ---- This function is called automatically by the Spawning scheduler. --- It is the internal worker method SPAWNing new Groups on the defined time intervals. -function SPAWN:_Scheduler() - self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } ) - - -- Validate if there are still groups left in the batch... - self:Spawn() - - return true -end - ---- Schedules the CleanUp of Groups --- @param #SPAWN self --- @return #boolean True = Continue Scheduler -function SPAWN:_SpawnCleanUpScheduler() - self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - - while SpawnGroup do - - local SpawnUnits = SpawnGroup:GetUnits() - - for UnitID, UnitData in pairs( SpawnUnits ) do - - local SpawnUnit = UnitData -- Wrapper.Unit#UNIT - local SpawnUnitName = SpawnUnit:GetName() - - - self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} - local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] - self:T( { SpawnUnitName, Stamp } ) - - if Stamp.Vec2 then - if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then - local NewVec2 = SpawnUnit:GetVec2() - if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving, and is on the ground, assign it with a timestamp... - if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) - self:ReSpawn( SpawnCursor ) - Stamp.Vec2 = nil - Stamp.Time = nil - end - else - Stamp.Time = timer.getTime() - Stamp.Vec2 = SpawnUnit:GetVec2() - end - else - Stamp.Vec2 = nil - Stamp.Time = nil - end - else - if SpawnUnit:InAir() == false then - Stamp.Vec2 = SpawnUnit:GetVec2() - if SpawnUnit:GetVelocityKMH() < 1 then - Stamp.Time = timer.getTime() - end - else - Stamp.Time = nil - Stamp.Vec2 = nil - end - end - end - - SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - - end - - return true -- Repeat - -end ---- Limit the simultaneous movement of Groups within a running Mission. --- This module is defined to improve the performance in missions, and to bring additional realism for GROUND vehicles. --- Performance: If in a DCSRTE there are a lot of moving GROUND units, then in a multi player mission, this WILL create lag if --- the main DCS execution core of your CPU is fully utilized. So, this class will limit the amount of simultaneous moving GROUND units --- on defined intervals (currently every minute). --- @module Movement - ---- the MOVEMENT class --- @type MOVEMENT --- @extends Core.Base#BASE -MOVEMENT = { - ClassName = "MOVEMENT", -} - ---- Creates the main object which is handling the GROUND forces movement. --- @param table{string,...}|string MovePrefixes is a table of the Prefixes (names) of the GROUND Groups that need to be controlled by the MOVEMENT Object. --- @param number MoveMaximum is a number that defines the maximum amount of GROUND Units to be moving during one minute. --- @return MOVEMENT --- @usage --- -- Limit the amount of simultaneous moving units on the ground to prevent lag. --- Movement_US_Platoons = MOVEMENT:New( { 'US Tank Platoon Left', 'US Tank Platoon Middle', 'US Tank Platoon Right', 'US CH-47D Troops' }, 15 ) - -function MOVEMENT:New( MovePrefixes, MoveMaximum ) - local self = BASE:Inherit( self, BASE:New() ) -- #MOVEMENT - self:F( { MovePrefixes, MoveMaximum } ) - - if type( MovePrefixes ) == 'table' then - self.MovePrefixes = MovePrefixes - else - self.MovePrefixes = { MovePrefixes } - end - self.MoveCount = 0 -- The internal counter of the amount of Moveing the has happened since MoveStart. - self.MoveMaximum = MoveMaximum -- Contains the Maximum amount of units that are allowed to move... - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.MoveUnits = {} -- Reflects if the Moving for this MovePrefixes is going to be scheduled or not. - - self:HandleEvent( EVENTS.Birth ) - --- self:AddEvent( world.event.S_EVENT_BIRTH, self.OnBirth ) --- --- self:EnableEvents() - - self:ScheduleStart() - - return self -end - ---- Call this function to start the MOVEMENT scheduling. -function MOVEMENT:ScheduleStart() - self:F() - --self.MoveFunction = routines.scheduleFunction( self._Scheduler, { self }, timer.getTime() + 1, 120 ) - self.MoveFunction = SCHEDULER:New( self, self._Scheduler, {}, 1, 120 ) -end - ---- Call this function to stop the MOVEMENT scheduling. --- @todo need to implement it ... Forgot. -function MOVEMENT:ScheduleStop() - self:F() - -end - ---- Captures the birth events when new Units were spawned. --- @todo This method should become obsolete. The new @{DATABASE} class will handle the collection administration. --- @param #MOVEMENT self --- @param Core.Event#EVENTDATA self -function MOVEMENT:OnEventBirth( EventData ) - self:F( { EventData } ) - - if 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 - if EventData.IniDCSUnit then - self:T( "Birth object : " .. EventData.IniDCSUnitName ) - if EventData.IniDCSGroup and EventData.IniDCSGroup:isExist() then - for MovePrefixID, MovePrefix in pairs( self.MovePrefixes ) do - if string.find( EventData.IniDCSUnitName, MovePrefix, 1, true ) then - self.AliveUnits = self.AliveUnits + 1 - self.MoveUnits[EventData.IniDCSUnitName] = EventData.IniDCSGroupName - self:T( self.AliveUnits ) - end - end - end - end - - EventData.IniUnit:HandleEvent( EVENTS.DEAD, self.OnDeadOrCrash ) - end - -end - ---- Captures the Dead or Crash events when Units crash or are destroyed. --- @todo This method should become obsolete. The new @{DATABASE} class will handle the collection administration. -function MOVEMENT:OnDeadOrCrash( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - self:T( "Dead object : " .. Event.IniDCSUnitName ) - for MovePrefixID, MovePrefix in pairs( self.MovePrefixes ) do - if string.find( Event.IniDCSUnitName, MovePrefix, 1, true ) then - self.AliveUnits = self.AliveUnits - 1 - self.MoveUnits[Event.IniDCSUnitName] = nil - self:T( self.AliveUnits ) - end - end - end -end - ---- This function is called automatically by the MOVEMENT scheduler. A new function is scheduled when MoveScheduled is true. -function MOVEMENT:_Scheduler() - self:F( { self.MovePrefixes, self.MoveMaximum, self.AliveUnits, self.MovementGroups } ) - - if self.AliveUnits > 0 then - local MoveProbability = ( self.MoveMaximum * 100 ) / self.AliveUnits - self:T( 'Move Probability = ' .. MoveProbability ) - - for MovementUnitName, MovementGroupName in pairs( self.MoveUnits ) do - local MovementGroup = Group.getByName( MovementGroupName ) - if MovementGroup and MovementGroup:isExist() then - local MoveOrStop = math.random( 1, 100 ) - self:T( 'MoveOrStop = ' .. MoveOrStop ) - if MoveOrStop <= MoveProbability then - self:T( 'Group continues moving = ' .. MovementGroupName ) - trigger.action.groupContinueMoving( MovementGroup ) - else - self:T( 'Group stops moving = ' .. MovementGroupName ) - trigger.action.groupStopMoving( MovementGroup ) - end - else - self.MoveUnits[MovementUnitName] = nil - end - end - end - return true -end ---- Provides defensive behaviour to a set of SAM sites within a running Mission. --- @module Sead --- @author to be searched on the forum --- @author (co) Flightcontrol (Modified and enriched with functionality) - ---- The SEAD class --- @type SEAD --- @extends Core.Base#BASE -SEAD = { - ClassName = "SEAD", - TargetSkill = { - Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } , - Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } , - High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } , - Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } } - }, - SEADGroupPrefixes = {} -} - ---- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. --- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... --- Chances are big that the missile will miss. --- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken. --- @return SEAD --- @usage --- -- CCCP SEAD Defenses --- -- Defends the Russian SA installations from SEAD attacks. --- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) -function SEAD:New( SEADGroupPrefixes ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( SEADGroupPrefixes ) - if type( SEADGroupPrefixes ) == 'table' then - for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do - self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix - end - else - self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes - end - - self:HandleEvent( EVENTS.Shot ) - - return self -end - ---- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @see SEAD --- @param #SEAD --- @param Core.Event#EVENTDATA EventData -function SEAD:OnEventShot( EventData ) - self:F( { EventData } ) - - local SEADUnit = EventData.IniDCSUnit - local SEADUnitName = EventData.IniDCSUnitName - local SEADWeapon = EventData.Weapon -- Identify the weapon fired - local SEADWeaponName = EventData.WeaponName -- return weapon type - -- Start of the 2nd loop - self:T( "Missile Launched = " .. SEADWeaponName ) - if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD - local _evade = math.random (1,100) -- random number for chance of evading action - local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetMimname = Unit.getName(_targetMim) - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimgroupName = _targetMimgroup:getName() - local _targetMimcont= _targetMimgroup:getController() - local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) - local SEADGroupFound = false - for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do - if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then - SEADGroupFound = true - self:T( 'Group Found' ) - break - end - end - if SEADGroupFound == true then - if _targetskill == "Random" then -- when skill is random, choose a skill - local Skills = { "Average", "Good", "High", "Excellent" } - _targetskill = Skills[ math.random(1,4) ] - end - self:T( _targetskill ) - if self.TargetSkill[_targetskill] then - if (_evade > self.TargetSkill[_targetskill].Evade) then - self:T( string.format("Evading, target skill " ..string.format(_targetskill)) ) - local _targetMim = Weapon.getTarget(SEADWeapon) - local _targetMimname = Unit.getName(_targetMim) - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimcont= _targetMimgroup:getController() - routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - local SuppressedGroups1 = {} -- unit suppressed radar off for a random time - local function SuppressionEnd1(id) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - SuppressedGroups1[id.groupName] = nil - end - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2]) - if SuppressedGroups1[id.groupName] == nil then - SuppressedGroups1[id.groupName] = { - SuppressionEndTime1 = timer.getTime() + delay1, - SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function - } - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20) - end - - local SuppressedGroups = {} - local function SuppressionEnd(id) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - SuppressedGroups[id.groupName] = nil - end - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - if SuppressedGroups[id.groupName] == nil then - SuppressedGroups[id.groupName] = { - SuppressionEndTime = timer.getTime() + delay, - SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function - } - timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20) - end - end - end - end - end -end ---- Taking the lead of AI escorting your flight. --- --- @{#ESCORT} class --- ================ --- The @{#ESCORT} class allows you to interact with escorting AI on your flight and take the lead. --- Each escorting group can be commanded with a whole set of radio commands (radio menu in your flight, and then F10). --- --- The radio commands will vary according the category of the group. The richest set of commands are with Helicopters and AirPlanes. --- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. --- --- RADIO MENUs that can be created: --- ================================ --- Find a summary below of the current available commands: --- --- Navigation ...: --- --------------- --- Escort group navigation functions: --- --- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you. --- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. --- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. --- --- Hold position ...: --- ------------------ --- Escort group navigation functions: --- --- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. --- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. --- --- Report targets ...: --- ------------------- --- Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below). --- --- * **"Report now":** Will report the current detected targets. --- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list. --- * **"Report targets off":** Will stop detecting targets. --- --- Scan targets ...: --- ----------------- --- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task. --- --- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. --- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. --- --- Attack targets ...: --- ------------------- --- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. --- --- Request assistance from ...: --- ---------------------------- --- This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**. --- This menu item allows to request attack support from other escorts supporting the current client group. --- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. --- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. --- --- ROE ...: --- -------- --- Sets the Rules of Engagement (ROE) of the escort group when in flight. --- --- * **"Hold Fire":** The escort group will hold fire. --- * **"Return Fire":** The escort group will return fire. --- * **"Open Fire":** The escort group will open fire on designated targets. --- * **"Weapon Free":** The escort group will engage with any target. --- --- Evasion ...: --- ------------ --- Will define the evasion techniques that the escort group will perform during flight or combat. --- --- * **"Fight until death":** The escort group will have no reaction to threats. --- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed. --- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing. --- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres. --- --- Resume Mission ...: --- ------------------- --- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. --- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. --- --- ESCORT construction methods. --- ============================ --- Create a new SPAWN object with the @{#ESCORT.New} method: --- --- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text. --- --- ESCORT initialization methods. --- ============================== --- The following menus are created within the RADIO MENU (F10) of an active unit hosted by a player: --- --- * @{#ESCORT.MenuFollowAt}: Creates a menu to make the escort follow the client. --- * @{#ESCORT.MenuHoldAtEscortPosition}: Creates a menu to hold the escort at its current position. --- * @{#ESCORT.MenuHoldAtLeaderPosition}: Creates a menu to hold the escort at the client position. --- * @{#ESCORT.MenuScanForTargets}: Creates a menu so that the escort scans targets. --- * @{#ESCORT.MenuFlare}: Creates a menu to disperse flares. --- * @{#ESCORT.MenuSmoke}: Creates a menu to disparse smoke. --- * @{#ESCORT.MenuReportTargets}: Creates a menu so that the escort reports targets. --- * @{#ESCORT.MenuReportPosition}: Creates a menu so that the escort reports its current position from bullseye. --- * @{#ESCORT.MenuAssistedAttack: Creates a menu so that the escort supportes assisted attack from other escorts with the client. --- * @{#ESCORT.MenuROE: Creates a menu structure to set the rules of engagement of the escort. --- * @{#ESCORT.MenuEvasion: Creates a menu structure to set the evasion techniques when the escort is under threat. --- * @{#ESCORT.MenuResumeMission}: Creates a menu structure so that the escort can resume from a waypoint. --- --- --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortClient = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = ESCORT:New( EscortClient, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- --- --- --- @module Escort --- @author FlightControl - ---- ESCORT class --- @type ESCORT --- @extends Core.Base#BASE --- @field Wrapper.Client#CLIENT EscortClient --- @field Wrapper.Group#GROUP EscortGroup --- @field #string EscortName --- @field #ESCORT.MODE EscortMode The mode the escort is in. --- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. --- @field #number FollowDistance The current follow distance. --- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field Dcs.DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. --- @field Dcs.DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. --- @field Core.Menu#MENU_CLIENT EscortMenuResumeMission --- @field Functional.Detection#DETECTION_BASE Detection -ESCORT = { - ClassName = "ESCORT", - EscortName = nil, -- The Escort Name - EscortClient = nil, - EscortGroup = nil, - EscortMode = 1, - MODE = { - FOLLOW = 1, - MISSION = 2, - }, - Targets = {}, -- The identified targets - FollowScheduler = nil, - ReportTargets = true, - OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, - OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, - SmokeDirectionVector = false, - TaskPoints = {} -} - ---- ESCORT.Mode class --- @type ESCORT.MODE --- @field #number FOLLOW --- @field #number MISSION - ---- MENUPARAM type --- @type MENUPARAM --- @field #ESCORT ParamSelf --- @field #Distance ParamDistance --- @field #function ParamFunction --- @field #string ParamMessage - ---- ESCORT class constructor for an AI group --- @param #ESCORT self --- @param Wrapper.Client#CLIENT EscortClient The client escorted by the EscortGroup. --- @param Wrapper.Group#GROUP EscortGroup The group AI escorting the EscortClient. --- @param #string EscortName Name of the escort. --- @param #string EscortBriefing A text showing the ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. --- @return #ESCORT self --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortClient = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = ESCORT:New( EscortClient, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) -function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) - - local self = BASE:Inherit( self, BASE:New() ) -- #ESCORT - self:F( { EscortClient, EscortGroup, EscortName } ) - - self.EscortClient = EscortClient -- Wrapper.Client#CLIENT - self.EscortGroup = EscortGroup -- Wrapper.Group#GROUP - self.EscortName = EscortName - self.EscortBriefing = EscortBriefing - - self.EscortSetGroup = SET_GROUP:New() - self.EscortSetGroup:AddObject( self.EscortGroup ) - self.EscortSetGroup:Flush() - self.Detection = DETECTION_UNITS:New( self.EscortSetGroup, 15000 ) - - self.EscortGroup.Detection = self.Detection - - -- Set EscortGroup known at EscortClient. - if not self.EscortClient._EscortGroups then - self.EscortClient._EscortGroups = {} - end - - if not self.EscortClient._EscortGroups[EscortGroup:GetName()] then - self.EscortClient._EscortGroups[EscortGroup:GetName()] = {} - self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortGroup = self.EscortGroup - self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortName = self.EscortName - self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection = self.EscortGroup.Detection - end - - self.EscortMenu = MENU_CLIENT:New( self.EscortClient, self.EscortName ) - - self.EscortGroup:WayPointInitialize(1) - - self.EscortGroup:OptionROTVertical() - self.EscortGroup:OptionROEOpenFire() - - if not EscortBriefing then - EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") reporting! " .. - "We're escorting your flight. " .. - "Use the Radio Menu and F10 and use the options under + " .. EscortName .. "\n", - 60, EscortClient - ) - else - EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") " .. EscortBriefing, - 60, EscortClient - ) - end - - self.FollowDistance = 100 - self.CT1 = 0 - self.GT1 = 0 - - self.FollowScheduler, self.FollowSchedule = SCHEDULER:New( self, self._FollowScheduler, {}, 1, .5, .01 ) - self.FollowScheduler:Stop( self.FollowSchedule ) - - self.EscortMode = ESCORT.MODE.MISSION - - - return self -end - ---- Set a Detection method for the EscortClient to be reported upon. --- Detection methods are based on the derived classes from DETECTION_BASE. --- @param #ESCORT self --- @param Function.Detection#DETECTION_BASE Detection -function ESCORT:SetDetection( Detection ) - - self.Detection = Detection - self.EscortGroup.Detection = self.Detection - self.EscortClient._EscortGroups[self.EscortGroup:GetName()].Detection = self.EscortGroup.Detection - - Detection:__Start( 1 ) - -end - ---- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This allows to visualize where the escort is flying to. --- @param #ESCORT self --- @param #boolean SmokeDirection If true, then the direction vector will be smoked. -function ESCORT:TestSmokeDirectionVector( SmokeDirection ) - self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false -end - - ---- Defines the default menus --- @param #ESCORT self --- @return #ESCORT -function ESCORT:Menus() - self:F() - - self:MenuFollowAt( 100 ) - self:MenuFollowAt( 200 ) - self:MenuFollowAt( 300 ) - self:MenuFollowAt( 400 ) - - self:MenuScanForTargets( 100, 60 ) - - self:MenuHoldAtEscortPosition( 30 ) - self:MenuHoldAtLeaderPosition( 30 ) - - self:MenuFlare() - self:MenuSmoke() - - self:MenuReportTargets( 60 ) - self:MenuAssistedAttack() - self:MenuROE() - self:MenuEvasion() - self:MenuResumeMission() - - - return self -end - - - ---- Defines a menu slot to let the escort Join and Follow you at a certain distance. --- This menu will appear under **Navigation**. --- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. --- @return #ESCORT -function ESCORT:MenuFollowAt( Distance ) - self:F(Distance) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - if not self.EscortMenuJoinUpAndFollow then - self.EscortMenuJoinUpAndFollow = {} - end - - self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1] = MENU_CLIENT_COMMAND:New( self.EscortClient, "Join-Up and Follow at " .. Distance, self.EscortMenuReportNavigation, ESCORT._JoinUpAndFollow, self, Distance ) - - self.EscortMode = ESCORT.MODE.FOLLOW - end - - return self -end - ---- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Hold position**. --- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT --- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. -function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - - if not self.EscortMenuHold then - self.EscortMenuHold = MENU_CLIENT:New( self.EscortClient, "Hold position", self.EscortMenu ) - end - - if not Height then - Height = 30 - end - - if not Seconds then - Seconds = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "Hold at %d meter", Height ) - else - MenuText = string.format( "Hold at %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuHoldPosition then - self.EscortMenuHoldPosition = {} - end - - self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuHold, - ESCORT._HoldPosition, - self, - self.EscortGroup, - Height, - Seconds - ) - end - - return self -end - - ---- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Navigation**. --- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT --- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. -function ESCORT:MenuHoldAtLeaderPosition( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - - if not self.EscortMenuHold then - self.EscortMenuHold = MENU_CLIENT:New( self.EscortClient, "Hold position", self.EscortMenu ) - end - - if not Height then - Height = 30 - end - - if not Seconds then - Seconds = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "Rejoin and hold at %d meter", Height ) - else - MenuText = string.format( "Rejoin and hold at %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuHoldAtLeaderPosition then - self.EscortMenuHoldAtLeaderPosition = {} - end - - self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuHold, - ESCORT._HoldPosition, - { ParamSelf = self, - ParamOrbitGroup = self.EscortClient, - ParamHeight = Height, - ParamSeconds = Seconds - } - ) - end - - return self -end - ---- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. --- This menu will appear under **Scan targets**. --- @param #ESCORT self --- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuScan then - self.EscortMenuScan = MENU_CLIENT:New( self.EscortClient, "Scan for targets", self.EscortMenu ) - end - - if not Height then - Height = 100 - end - - if not Seconds then - Seconds = 30 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "At %d meter", Height ) - else - MenuText = string.format( "At %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuScanForTargets then - self.EscortMenuScanForTargets = {} - end - - self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuScan, - ESCORT._ScanTargets, - self, - 30 - ) - end - - return self -end - - - ---- Defines a menu slot to let the escort disperse a flare in a certain color. --- This menu will appear under **Navigation**. --- The flare will be fired from the first unit in the group. --- @param #ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuFlare( MenuTextFormat ) - self:F() - - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Flare" - else - MenuText = MenuTextFormat - end - - if not self.EscortMenuFlare then - self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, self ) - self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Green, "Released a green flare!" ) - self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Red, "Released a red flare!" ) - self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.White, "Released a white flare!" ) - self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, self, FLARECOLOR.Yellow, "Released a yellow flare!" ) - end - - return self -end - ---- Defines a menu slot to let the escort disperse a smoke in a certain color. --- This menu will appear under **Navigation**. --- Note that smoke menu options will only be displayed for ships and ground units. Not for air units. --- The smoke will be fired from the first unit in the group. --- @param #ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuSmoke( MenuTextFormat ) - self:F() - - if not self.EscortGroup:IsAir() then - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Smoke" - else - MenuText = MenuTextFormat - end - - if not self.EscortMenuSmoke then - self.EscortMenuSmoke = MENU_CLIENT:New( self.EscortClient, "Smoke", self.EscortMenuReportNavigation, ESCORT._Smoke, self ) - self.EscortMenuSmokeGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Green, "Releasing green smoke!" ) - self.EscortMenuSmokeRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Red, "Releasing red smoke!" ) - self.EscortMenuSmokeWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.White, "Releasing white smoke!" ) - self.EscortMenuSmokeOrange = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release orange smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Orange, "Releasing orange smoke!" ) - self.EscortMenuSmokeBlue = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release blue smoke", self.EscortMenuSmoke, ESCORT._Smoke, self, SMOKECOLOR.Blue, "Releasing blue smoke!" ) - end - end - - return self -end - ---- Defines a menu slot to let the escort report their current detected targets with a specified time interval in seconds. --- This menu will appear under **Report targets**. --- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. --- @param #ESCORT self --- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. --- @return #ESCORT -function ESCORT:MenuReportTargets( Seconds ) - self:F( { Seconds } ) - - if not self.EscortMenuReportNearbyTargets then - self.EscortMenuReportNearbyTargets = MENU_CLIENT:New( self.EscortClient, "Report targets", self.EscortMenu ) - end - - if not Seconds then - Seconds = 30 - end - - -- Report Targets - self.EscortMenuReportNearbyTargetsNow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets now!", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargetsNow, self ) - self.EscortMenuReportNearbyTargetsOn = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets on", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, self, true ) - self.EscortMenuReportNearbyTargetsOff = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets off", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, self, false ) - - -- Attack Targets - self.EscortMenuAttackNearbyTargets = MENU_CLIENT:New( self.EscortClient, "Attack targets", self.EscortMenu ) - - - self.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, Seconds ) - - return self -end - ---- Defines a menu slot to let the escort attack its detected targets using assisted attack from another escort joined also with the client. --- This menu will appear under **Request assistance from**. --- Note that this method needs to be preceded with the method MenuReportTargets. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuAssistedAttack() - self:F() - - -- Request assistance from other escorts. - -- This is very useful to let f.e. an escorting ship attack a target detected by an escorting plane... - self.EscortMenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, "Request assistance from", self.EscortMenu ) - - return self -end - ---- Defines a menu to let the escort set its rules of engagement. --- All rules of engagement will appear under the menu **ROE**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuROE( MenuTextFormat ) - self:F( MenuTextFormat ) - - if not self.EscortMenuROE then - -- Rules of Engagement - self.EscortMenuROE = MENU_CLIENT:New( self.EscortClient, "ROE", self.EscortMenu ) - if self.EscortGroup:OptionROEHoldFirePossible() then - self.EscortMenuROEHoldFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Hold Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEHoldFire(), "Holding weapons!" ) - end - if self.EscortGroup:OptionROEReturnFirePossible() then - self.EscortMenuROEReturnFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Return Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEReturnFire(), "Returning fire!" ) - end - if self.EscortGroup:OptionROEOpenFirePossible() then - self.EscortMenuROEOpenFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Open Fire", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEOpenFire(), "Opening fire on designated targets!!" ) - end - if self.EscortGroup:OptionROEWeaponFreePossible() then - self.EscortMenuROEWeaponFree = MENU_CLIENT_COMMAND:New( self.EscortClient, "Weapon Free", self.EscortMenuROE, ESCORT._ROE, self, self.EscortGroup:OptionROEWeaponFree(), "Opening fire on targets of opportunity!" ) - end - end - - return self -end - - ---- Defines a menu to let the escort set its evasion when under threat. --- All rules of engagement will appear under the menu **Evasion**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuEvasion( MenuTextFormat ) - self:F( MenuTextFormat ) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuEvasion then - -- Reaction to Threats - self.EscortMenuEvasion = MENU_CLIENT:New( self.EscortClient, "Evasion", self.EscortMenu ) - if self.EscortGroup:OptionROTNoReactionPossible() then - self.EscortMenuEvasionNoReaction = MENU_CLIENT_COMMAND:New( self.EscortClient, "Fight until death", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTNoReaction(), "Fighting until death!" ) - end - if self.EscortGroup:OptionROTPassiveDefensePossible() then - self.EscortMenuEvasionPassiveDefense = MENU_CLIENT_COMMAND:New( self.EscortClient, "Use flares, chaff and jammers", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTPassiveDefense(), "Defending using jammers, chaff and flares!" ) - end - if self.EscortGroup:OptionROTEvadeFirePossible() then - self.EscortMenuEvasionEvadeFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Evade enemy fire", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTEvadeFire(), "Evading on enemy fire!" ) - end - if self.EscortGroup:OptionROTVerticalPossible() then - self.EscortMenuOptionEvasionVertical = MENU_CLIENT_COMMAND:New( self.EscortClient, "Go below radar and evade fire", self.EscortMenuEvasion, ESCORT._ROT, self, self.EscortGroup:OptionROTVertical(), "Evading on enemy fire with vertical manoeuvres!" ) - end - end - end - - return self -end - ---- Defines a menu to let the escort resume its mission from a waypoint on its route. --- All rules of engagement will appear under the menu **Resume mission from**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuResumeMission() - self:F() - - if not self.EscortMenuResumeMission then - -- Mission Resume Menu Root - self.EscortMenuResumeMission = MENU_CLIENT:New( self.EscortClient, "Resume mission from", self.EscortMenu ) - end - - return self -end - - ---- @param #MENUPARAM MenuParam -function ESCORT:_HoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT - - self.FollowScheduler:Stop( self.FollowSchedule ) - - local PointFrom = {} - local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() - PointFrom = {} - PointFrom.x = GroupVec3.x - PointFrom.y = GroupVec3.z - PointFrom.speed = 250 - PointFrom.type = AI.Task.WaypointType.TURNING_POINT - PointFrom.alt = GroupVec3.y - PointFrom.alt_type = AI.Task.AltitudeType.BARO - - local OrbitPoint = OrbitUnit:GetVec2() - local PointTo = {} - PointTo.x = OrbitPoint.x - PointTo.y = OrbitPoint.y - PointTo.speed = 250 - PointTo.type = AI.Task.WaypointType.TURNING_POINT - PointTo.alt = OrbitHeight - PointTo.alt_type = AI.Task.AltitudeType.BARO - PointTo.task = EscortGroup:TaskOrbitCircleAtVec2( OrbitPoint, OrbitHeight, 0 ) - - local Points = { PointFrom, PointTo } - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTPassiveDefense() - - EscortGroup:SetTask( EscortGroup:TaskRoute( Points ) ) - EscortGroup:MessageToClient( "Orbiting at location.", 10, EscortClient ) - -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_JoinUpAndFollow( Distance ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.Distance = Distance - - self:JoinUpAndFollow( EscortGroup, EscortClient, self.Distance ) -end - ---- JoinsUp and Follows a CLIENT. --- @param Functional.Escort#ESCORT self --- @param Wrapper.Group#GROUP EscortGroup --- @param Wrapper.Client#CLIENT EscortClient --- @param Dcs.DCSTypes#Distance Distance -function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) - self:F( { EscortGroup, EscortClient, Distance } ) - - self.FollowScheduler:Stop( self.FollowSchedule ) - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTPassiveDefense() - - self.EscortMode = ESCORT.MODE.FOLLOW - - self.CT1 = 0 - self.GT1 = 0 - self.FollowScheduler:Start( self.FollowSchedule ) - - EscortGroup:MessageToClient( "Rejoining and Following at " .. Distance .. "!", 30, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_Flare( Color, Message ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - EscortGroup:GetUnit(1):Flare( Color ) - EscortGroup:MessageToClient( Message, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_Smoke( Color, Message ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - EscortGroup:GetUnit(1):Smoke( Color ) - EscortGroup:MessageToClient( Message, 10, EscortClient ) -end - - ---- @param #MENUPARAM MenuParam -function ESCORT:_ReportNearbyTargetsNow() - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self:_ReportTargetsScheduler() - -end - -function ESCORT:_SwitchReportNearbyTargets( ReportTargets ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.ReportTargets = ReportTargets - - if self.ReportTargets then - if not self.ReportTargetsScheduler then - self.ReportTargetsScheduler:Schedule( self, self._ReportTargetsScheduler, {}, 1, 30 ) - end - else - routines.removeFunction( self.ReportTargetsScheduler ) - self.ReportTargetsScheduler = nil - end -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_ScanTargets( ScanDuration ) - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - local EscortClient = self.EscortClient - - self.FollowScheduler:Stop( self.FollowSchedule ) - - if EscortGroup:IsHelicopter() then - EscortGroup:PushTask( - EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 200, 20 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ), 1 ) - elseif EscortGroup:IsAirPlane() then - EscortGroup:PushTask( - EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 1000, 500 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ), 1 ) - end - - EscortGroup:MessageToClient( "Scanning targets for " .. ScanDuration .. " seconds.", ScanDuration, EscortClient ) - - if self.EscortMode == ESCORT.MODE.FOLLOW then - self.FollowScheduler:Start( self.FollowSchedule ) - end - -end - ---- @param Wrapper.Group#GROUP EscortGroup -function _Resume( EscortGroup ) - env.info( '_Resume' ) - - local Escort = EscortGroup:GetState( EscortGroup, "Escort" ) - env.info( "EscortMode = " .. Escort.EscortMode ) - if Escort.EscortMode == ESCORT.MODE.FOLLOW then - Escort:JoinUpAndFollow( EscortGroup, Escort.EscortClient, Escort.Distance ) - end - -end - ---- @param #ESCORT self --- @param #number DetectedItemID -function ESCORT:_AttackTarget( DetectedItemID ) - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - self:E( EscortGroup ) - - local EscortClient = self.EscortClient - - self.FollowScheduler:Stop( self.FollowSchedule ) - - if EscortGroup:IsAir() then - EscortGroup:OptionROEOpenFire() - EscortGroup:OptionROTPassiveDefense() - EscortGroup:SetState( EscortGroup, "Escort", self ) - - local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroup:TaskAttackUnit( DetectedUnit ) - end - end, Tasks - ) - - Tasks[#Tasks+1] = EscortGroup:TaskFunction( 1, 2, "_Resume", { "''" } ) - - EscortGroup:SetTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - else - - local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) - end - end, Tasks - ) - - EscortGroup:SetTask( - EscortGroup:TaskCombo( - Tasks - ), 1 - ) - - end - - EscortGroup:MessageToClient( "Engaging Designated Unit!", 10, EscortClient ) - -end - ---- --- @param #number DetectedItemID -function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItemID ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.FollowScheduler:Stop( self.FollowSchedule ) - - if EscortGroupAttack:IsAir() then - EscortGroupAttack:OptionROEOpenFire() - EscortGroupAttack:OptionROTVertical() - - local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroupAttack:TaskAttackUnit( DetectedUnit ) - end - end, Tasks - ) - - Tasks[#Tasks+1] = EscortGroupAttack:TaskOrbitCircle( 500, 350 ) - - EscortGroupAttack:SetTask( - EscortGroupAttack:TaskCombo( - Tasks - ), 1 - ) - - else - local DetectedSet = self.Detection:GetDetectedSet( DetectedItemID ) - - local Tasks = {} - - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit, Tasks ) - if DetectedUnit:IsAlive() then - Tasks[#Tasks+1] = EscortGroupAttack:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) - end - end, Tasks - ) - - EscortGroupAttack:SetTask( - EscortGroupAttack:TaskCombo( - Tasks - ), 1 - ) - - end - - EscortGroupAttack:MessageToClient( "Assisting with the destroying the enemy unit!", 10, EscortClient ) - -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_ROE( EscortROEFunction, EscortROEMessage ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - pcall( function() EscortROEFunction() end ) - EscortGroup:MessageToClient( EscortROEMessage, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_ROT( EscortROTFunction, EscortROTMessage ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - pcall( function() EscortROTFunction() end ) - EscortGroup:MessageToClient( EscortROTMessage, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT:_ResumeMission( WayPoint ) - - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.FollowScheduler:Stop( self.FollowSchedule ) - - local WayPoints = EscortGroup:GetTaskRoute() - self:T( WayPoint, WayPoints ) - - for WayPointIgnore = 1, WayPoint do - table.remove( WayPoints, 1 ) - end - - SCHEDULER:New( EscortGroup, EscortGroup.SetTask, { EscortGroup:TaskRoute( WayPoints ) }, 1 ) - - EscortGroup:MessageToClient( "Resuming mission from waypoint " .. WayPoint .. ".", 10, EscortClient ) -end - ---- Registers the waypoints --- @param #ESCORT self --- @return #table -function ESCORT:RegisterRoute() - self:F() - - local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - - local TaskPoints = EscortGroup:GetTaskRoute() - - self:T( TaskPoints ) - - return TaskPoints -end - ---- @param Functional.Escort#ESCORT self -function ESCORT:_FollowScheduler() - self:F( { self.FollowDistance } ) - - self:T( {self.EscortClient.UnitName, self.EscortGroup.GroupName } ) - if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - - local ClientUnit = self.EscortClient:GetClientGroupUnit() - local GroupUnit = self.EscortGroup:GetUnit( 1 ) - local FollowDistance = self.FollowDistance - - self:T( {ClientUnit.UnitName, GroupUnit.UnitName } ) - - if self.CT1 == 0 and self.GT1 == 0 then - self.CV1 = ClientUnit:GetVec3() - self:T( { "self.CV1", self.CV1 } ) - self.CT1 = timer.getTime() - self.GV1 = GroupUnit:GetVec3() - self.GT1 = timer.getTime() - else - local CT1 = self.CT1 - local CT2 = timer.getTime() - local CV1 = self.CV1 - local CV2 = ClientUnit:GetVec3() - self.CT1 = CT2 - self.CV1 = CV2 - - local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 - local CT = CT2 - CT1 - - local CS = ( 3600 / CT ) * ( CD / 1000 ) - - self:T2( { "Client:", CS, CD, CT, CV2, CV1, CT2, CT1 } ) - - local GT1 = self.GT1 - local GT2 = timer.getTime() - local GV1 = self.GV1 - local GV2 = GroupUnit:GetVec3() - self.GT1 = GT2 - self.GV1 = GV2 - - local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 - local GT = GT2 - GT1 - - local GS = ( 3600 / GT ) * ( GD / 1000 ) - - self:T2( { "Group:", GS, GD, GT, GV2, GV1, GT2, GT1 } ) - - -- Calculate the group direction vector - local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } - - -- Calculate GH2, GH2 with the same height as CV2. - local GH2 = { x = GV2.x, y = CV2.y, z = GV2.z } - - -- Calculate the angle of GV to the orthonormal plane - local alpha = math.atan2( GV.z, GV.x ) - - -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. - -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) - local CVI = { x = CV2.x + FollowDistance * math.cos(alpha), - y = GH2.y, - z = CV2.z + FollowDistance * math.sin(alpha), - } - - -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. - local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } - - -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. - -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. - -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... - local DVu = { x = DV.x / FollowDistance, y = DV.y / FollowDistance, z = DV.z / FollowDistance } - - -- Now we can calculate the group destination vector GDV. - local GDV = { x = DVu.x * CS * 8 + CVI.x, y = CVI.y, z = DVu.z * CS * 8 + CVI.z } - - if self.SmokeDirectionVector == true then - trigger.action.smoke( GDV, trigger.smokeColor.Red ) - end - - self:T2( { "CV2:", CV2 } ) - self:T2( { "CVI:", CVI } ) - self:T2( { "GDV:", GDV } ) - - -- Measure distance between client and group - local CatchUpDistance = ( ( GDV.x - GV2.x )^2 + ( GDV.y - GV2.y )^2 + ( GDV.z - GV2.z )^2 ) ^ 0.5 - - -- The calculation of the Speed would simulate that the group would take 30 seconds to overcome - -- the requested Distance). - local Time = 10 - local CatchUpSpeed = ( CatchUpDistance - ( CS * 8.4 ) ) / Time - - local Speed = CS + CatchUpSpeed - if Speed < 0 then - Speed = 0 - end - - self:T( { "Client Speed, Escort Speed, Speed, FollowDistance, Time:", CS, GS, Speed, FollowDistance, Time } ) - - -- Now route the escort to the desired point with the desired speed. - self.EscortGroup:RouteToVec3( GDV, Speed / 3.6 ) -- DCS models speed in Mps (Miles per second) - end - - return true - end - - return false -end - - ---- Report Targets Scheduler. --- @param #ESCORT self -function ESCORT:_ReportTargetsScheduler() - self:F( self.EscortGroup:GetName() ) - - if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - - if true then - - local EscortGroupName = self.EscortGroup:GetName() - - self.EscortMenuAttackNearbyTargets:RemoveSubMenus() - - if self.EscortMenuTargetAssistance then - self.EscortMenuTargetAssistance:RemoveSubMenus() - end - - local DetectedItems = self.Detection:GetDetectedItems() - self:E( DetectedItems ) - - local DetectedTargets = false - - local DetectedMsgs = {} - - for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do - - local ClientEscortTargets = EscortGroupData.Detection - - for DetectedItemID, DetectedItem in ipairs( DetectedItems ) do - self:E( { DetectedItemID, DetectedItem } ) - -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. - - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItemID ) - - if ClientEscortGroupName == EscortGroupName then - - DetectedMsgs[#DetectedMsgs+1] = DetectedItemReportSummary - - MENU_CLIENT_COMMAND:New( self.EscortClient, - DetectedItemReportSummary, - self.EscortMenuAttackNearbyTargets, - ESCORT._AttackTarget, - self, - DetectedItemID - ) - else - if self.EscortMenuTargetAssistance then - - self:T( DetectedItemReportSummary ) - local MenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance ) - MENU_CLIENT_COMMAND:New( self.EscortClient, - DetectedItemReportSummary, - MenuTargetAssistance, - ESCORT._AssistTarget, - self, - EscortGroupData.EscortGroup, - DetectedItemID - ) - end - end - - DetectedTargets = true - - end - end - self:E( DetectedMsgs ) - if DetectedTargets then - self.EscortGroup:MessageToClient( "Detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), 20, self.EscortClient ) - else - self.EscortGroup:MessageToClient( "No targets detected.", 10, self.EscortClient ) - end - - return true - else --- local EscortGroupName = self.EscortGroup:GetName() --- local EscortTargets = self.EscortGroup:GetDetectedTargets() --- --- local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets --- --- local EscortTargetMessages = "" --- for EscortTargetID, EscortTarget in pairs( EscortTargets ) do --- local EscortObject = EscortTarget.object --- self:T( EscortObject ) --- if EscortObject and EscortObject:isExist() and EscortObject.id_ < 50000000 then --- --- local EscortTargetUnit = UNIT:Find( EscortObject ) --- local EscortTargetUnitName = EscortTargetUnit:GetName() --- --- --- --- -- local EscortTargetIsDetected, --- -- EscortTargetIsVisible, --- -- EscortTargetLastTime, --- -- EscortTargetKnowType, --- -- EscortTargetKnowDistance, --- -- EscortTargetLastPos, --- -- EscortTargetLastVelocity --- -- = self.EscortGroup:IsTargetDetected( EscortObject ) --- -- --- -- self:T( { EscortTargetIsDetected, --- -- EscortTargetIsVisible, --- -- EscortTargetLastTime, --- -- EscortTargetKnowType, --- -- EscortTargetKnowDistance, --- -- EscortTargetLastPos, --- -- EscortTargetLastVelocity } ) --- --- --- local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3() --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + --- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + --- ( EscortTargetUnitVec3.z - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- --- self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } ) --- --- if Distance <= 15 then --- --- if not ClientEscortTargets[EscortTargetUnitName] then --- ClientEscortTargets[EscortTargetUnitName] = {} --- end --- ClientEscortTargets[EscortTargetUnitName].AttackUnit = EscortTargetUnit --- ClientEscortTargets[EscortTargetUnitName].visible = EscortTarget.visible --- ClientEscortTargets[EscortTargetUnitName].type = EscortTarget.type --- ClientEscortTargets[EscortTargetUnitName].distance = EscortTarget.distance --- else --- if ClientEscortTargets[EscortTargetUnitName] then --- ClientEscortTargets[EscortTargetUnitName] = nil --- end --- end --- end --- end --- --- self:T( { "Sorting Targets Table:", ClientEscortTargets } ) --- table.sort( ClientEscortTargets, function( a, b ) return a.Distance < b.Distance end ) --- self:T( { "Sorted Targets Table:", ClientEscortTargets } ) --- --- -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. --- self.EscortMenuAttackNearbyTargets:RemoveSubMenus() --- --- if self.EscortMenuTargetAssistance then --- self.EscortMenuTargetAssistance:RemoveSubMenus() --- end --- --- --for MenuIndex = 1, #self.EscortMenuAttackTargets do --- -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } ) --- -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove() --- --end --- --- --- if ClientEscortTargets then --- for ClientEscortTargetUnitName, ClientEscortTargetData in pairs( ClientEscortTargets ) do --- --- for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do --- --- if ClientEscortTargetData and ClientEscortTargetData.AttackUnit:IsAlive() then --- --- local EscortTargetMessage = "" --- local EscortTargetCategoryName = ClientEscortTargetData.AttackUnit:GetCategoryName() --- local EscortTargetCategoryType = ClientEscortTargetData.AttackUnit:GetTypeName() --- if ClientEscortTargetData.type then --- EscortTargetMessage = EscortTargetMessage .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at " --- else --- EscortTargetMessage = EscortTargetMessage .. "Unknown target at " --- end --- --- local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3() --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + --- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + --- ( EscortTargetUnitVec3.z - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- --- self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } ) --- if ClientEscortTargetData.visible == false then --- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " estimated km" --- else --- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km" --- end --- --- if ClientEscortTargetData.visible then --- EscortTargetMessage = EscortTargetMessage .. ", visual" --- end --- --- if ClientEscortGroupName == EscortGroupName then --- --- MENU_CLIENT_COMMAND:New( self.EscortClient, --- EscortTargetMessage, --- self.EscortMenuAttackNearbyTargets, --- ESCORT._AttackTarget, --- { ParamSelf = self, --- ParamUnit = ClientEscortTargetData.AttackUnit --- } --- ) --- EscortTargetMessages = EscortTargetMessages .. "\n - " .. EscortTargetMessage --- else --- if self.EscortMenuTargetAssistance then --- local MenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance ) --- MENU_CLIENT_COMMAND:New( self.EscortClient, --- EscortTargetMessage, --- MenuTargetAssistance, --- ESCORT._AssistTarget, --- self, --- EscortGroupData.EscortGroup, --- ClientEscortTargetData.AttackUnit --- ) --- end --- end --- else --- ClientEscortTargetData = nil --- end --- end --- end --- --- if EscortTargetMessages ~= "" and self.ReportTargets == true then --- self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient ) --- else --- self.EscortGroup:MessageToClient( "No targets detected!", 20, self.EscortClient ) --- end --- end --- --- if self.EscortMenuResumeMission then --- self.EscortMenuResumeMission:RemoveSubMenus() --- --- -- if self.EscortMenuResumeWayPoints then --- -- for MenuIndex = 1, #self.EscortMenuResumeWayPoints do --- -- self:T( { "Remove Menu:", self.EscortMenuResumeWayPoints[MenuIndex] } ) --- -- self.EscortMenuResumeWayPoints[MenuIndex] = self.EscortMenuResumeWayPoints[MenuIndex]:Remove() --- -- end --- -- end --- --- local TaskPoints = self:RegisterRoute() --- for WayPointID, WayPoint in pairs( TaskPoints ) do --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + --- ( WayPoint.y - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- MENU_CLIENT_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } ) --- end --- end --- --- return true - end - end - - return false -end ---- This module contains the MISSILETRAINER class. --- --- === --- --- 1) @{MissileTrainer#MISSILETRAINER} class, extends @{Base#BASE} --- =============================================================== --- The @{#MISSILETRAINER} class uses the DCS world messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, --- the class will destroy the missile within a certain range, to avoid damage to your aircraft. --- It suports the following functionality: --- --- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes. --- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range � --- * Provide alerts when a missile would have killed your aircraft. --- * Provide alerts when the missile self destructs. --- * Enable / Disable and Configure the Missile Trainer using the various menu options. --- --- When running a mission where MISSILETRAINER is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players: --- --- * **Messages**: Menu to configure all messages. --- * **Messages On**: Show all messages. --- * **Messages Off**: Disable all messages. --- * **Tracking**: Menu to configure missile tracking messages. --- * **To All**: Shows missile tracking messages to all players. --- * **To Target**: Shows missile tracking messages only to the player where the missile is targetted at. --- * **Tracking On**: Show missile tracking messages. --- * **Tracking Off**: Disable missile tracking messages. --- * **Frequency Increase**: Increases the missile tracking message frequency with one second. --- * **Frequency Decrease**: Decreases the missile tracking message frequency with one second. --- * **Alerts**: Menu to configure alert messages. --- * **To All**: Shows alert messages to all players. --- * **To Target**: Shows alert messages only to the player where the missile is (was) targetted at. --- * **Hits On**: Show missile hit alert messages. --- * **Hits Off**: Disable missile hit alert messages. --- * **Launches On**: Show missile launch messages. --- * **Launches Off**: Disable missile launch messages. --- * **Details**: Menu to configure message details. --- * **Range On**: Shows range information when a missile is fired to a target. --- * **Range Off**: Disable range information when a missile is fired to a target. --- * **Bearing On**: Shows bearing information when a missile is fired to a target. --- * **Bearing Off**: Disable bearing information when a missile is fired to a target. --- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured. --- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter. --- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter. --- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter. --- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter. --- --- --- 1.1) MISSILETRAINER construction methods: --- ----------------------------------------- --- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method: --- --- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed. --- --- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those. --- --- 1.2) MISSILETRAINER initialization methods: --- ------------------------------------------- --- A MISSILETRAINER object will behave differently based on the usage of initialization methods: --- --- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF. --- * @{#MISSILETRAINER.InitTrackingToAll}: Sets by default the missile tracking report for all players or only for those missiles targetted to you. --- * @{#MISSILETRAINER.InitTrackingOnOff}: Sets by default the display of missile tracking report to be ON or OFF. --- * @{#MISSILETRAINER.InitTrackingFrequency}: Increases, decreases the missile tracking message display frequency with the provided time interval in seconds. --- * @{#MISSILETRAINER.InitAlertsToAll}: Sets by default the display of alerts to be shown to all players or only to you. --- * @{#MISSILETRAINER.InitAlertsHitsOnOff}: Sets by default the display of hit alerts ON or OFF. --- * @{#MISSILETRAINER.InitAlertsLaunchesOnOff}: Sets by default the display of launch alerts ON or OFF. --- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF. --- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF. --- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu. --- --- === --- --- CREDITS --- ======= --- **Stuka (Danny)** Who you can search on the Eagle Dynamics Forums. --- Working together with Danny has resulted in the MISSILETRAINER class. --- Danny has shared his ideas and together we made a design. --- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback! --- --- @module MissileTrainer --- @author FlightControl - - ---- The MISSILETRAINER class --- @type MISSILETRAINER --- @field Core.Set#SET_CLIENT DBClients --- @extends Core.Base#BASE -MISSILETRAINER = { - ClassName = "MISSILETRAINER", - TrackingMissiles = {}, -} - -function MISSILETRAINER._Alive( Client, self ) - - if self.Briefing then - Client:Message( self.Briefing, 15, "Trainer" ) - end - - if self.MenusOnOff == true then - Client:Message( "Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).", 15, "Trainer" ) - - Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) -- Menu#MENU_CLIENT - - Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) - Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) - Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) - - Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu ) - Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } ) - Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } ) - Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = true } ) - Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = false } ) - Client.MenuTrackIncrease = MENU_CLIENT_COMMAND:New( Client, "Frequency Increase", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = -1 } ) - Client.MenuTrackDecrease = MENU_CLIENT_COMMAND:New( Client, "Frequency Decrease", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = 1 } ) - - Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu ) - Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } ) - Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } ) - Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = true } ) - Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = false } ) - Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = true } ) - Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = false } ) - - Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu ) - Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = true } ) - Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = false } ) - Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = true } ) - Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = false } ) - - Client.MenuDistance = MENU_CLIENT:New( Client, "Set distance to plane", Client.MainMenu ) - Client.MenuDistance50 = MENU_CLIENT_COMMAND:New( Client, "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } ) - Client.MenuDistance100 = MENU_CLIENT_COMMAND:New( Client, "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } ) - Client.MenuDistance150 = MENU_CLIENT_COMMAND:New( Client, "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } ) - Client.MenuDistance200 = MENU_CLIENT_COMMAND:New( Client, "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } ) - else - if Client.MainMenu then - Client.MainMenu:Remove() - end - end - - local ClientID = Client:GetID() - self:T( ClientID ) - if not self.TrackingMissiles[ClientID] then - self.TrackingMissiles[ClientID] = {} - end - self.TrackingMissiles[ClientID].Client = Client - if not self.TrackingMissiles[ClientID].MissileData then - self.TrackingMissiles[ClientID].MissileData = {} - end -end - ---- Creates the main object which is handling missile tracking. --- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed. --- @param #MISSILETRAINER self --- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player. --- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes. --- @return #MISSILETRAINER -function MISSILETRAINER:New( Distance, Briefing ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( Distance ) - - if Briefing then - self.Briefing = Briefing - end - - self.Schedulers = {} - self.SchedulerID = 0 - - self.MessageInterval = 2 - self.MessageLastTime = timer.getTime() - - self.Distance = Distance / 1000 - - self:HandleEvent( EVENTS.Shot ) - - self.DBClients = SET_CLIENT:New():FilterStart() - - --- for ClientID, Client in pairs( self.DBClients.Database ) do --- self:E( "ForEach:" .. Client.UnitName ) --- Client:Alive( self._Alive, self ) --- end --- - self.DBClients:ForEachClient( - function( Client ) - self:E( "ForEach:" .. Client.UnitName ) - Client:Alive( self._Alive, self ) - end - ) - - - --- self.DB:ForEachClient( --- --- @param Wrapper.Client#CLIENT Client --- function( Client ) --- --- ... actions ... --- --- end --- ) - - self.MessagesOnOff = true - - self.TrackingToAll = false - self.TrackingOnOff = true - self.TrackingFrequency = 3 - - self.AlertsToAll = true - self.AlertsHitsOnOff = true - self.AlertsLaunchesOnOff = true - - self.DetailsRangeOnOff = true - self.DetailsBearingOnOff = true - - self.MenusOnOff = true - - self.TrackingMissiles = {} - - self.TrackingScheduler = SCHEDULER:New( self, self._TrackMissiles, {}, 0.5, 0.05, 0 ) - - return self -end - --- Initialization methods. - - - ---- Sets by default the display of any message to be ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean MessagesOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitMessagesOnOff( MessagesOnOff ) - self:F( MessagesOnOff ) - - self.MessagesOnOff = MessagesOnOff - if self.MessagesOnOff == true then - MESSAGE:New( "Messages ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Messages OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the missile tracking report for all players or only for those missiles targetted to you. --- @param #MISSILETRAINER self --- @param #boolean TrackingToAll true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingToAll( TrackingToAll ) - self:F( TrackingToAll ) - - self.TrackingToAll = TrackingToAll - if self.TrackingToAll == true then - MESSAGE:New( "Missile tracking to all players ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Missile tracking to all players OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of missile tracking report to be ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean TrackingOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingOnOff( TrackingOnOff ) - self:F( TrackingOnOff ) - - self.TrackingOnOff = TrackingOnOff - if self.TrackingOnOff == true then - MESSAGE:New( "Missile tracking ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Missile tracking OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds. --- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update. --- @param #MISSILETRAINER self --- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency. --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency ) - self:F( TrackingFrequency ) - - self.TrackingFrequency = self.TrackingFrequency + TrackingFrequency - if self.TrackingFrequency < 0.5 then - self.TrackingFrequency = 0.5 - end - if self.TrackingFrequency then - MESSAGE:New( "Missile tracking frequency is " .. self.TrackingFrequency .. " seconds.", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of alerts to be shown to all players or only to you. --- @param #MISSILETRAINER self --- @param #boolean AlertsToAll true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsToAll( AlertsToAll ) - self:F( AlertsToAll ) - - self.AlertsToAll = AlertsToAll - if self.AlertsToAll == true then - MESSAGE:New( "Alerts to all players ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts to all players OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of hit alerts ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean AlertsHitsOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsHitsOnOff( AlertsHitsOnOff ) - self:F( AlertsHitsOnOff ) - - self.AlertsHitsOnOff = AlertsHitsOnOff - if self.AlertsHitsOnOff == true then - MESSAGE:New( "Alerts Hits ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts Hits OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of launch alerts ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean AlertsLaunchesOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsLaunchesOnOff( AlertsLaunchesOnOff ) - self:F( AlertsLaunchesOnOff ) - - self.AlertsLaunchesOnOff = AlertsLaunchesOnOff - if self.AlertsLaunchesOnOff == true then - MESSAGE:New( "Alerts Launches ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts Launches OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of range information of missiles ON of OFF. --- @param #MISSILETRAINER self --- @param #boolean DetailsRangeOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitRangeOnOff( DetailsRangeOnOff ) - self:F( DetailsRangeOnOff ) - - self.DetailsRangeOnOff = DetailsRangeOnOff - if self.DetailsRangeOnOff == true then - MESSAGE:New( "Range display ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Range display OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of bearing information of missiles ON of OFF. --- @param #MISSILETRAINER self --- @param #boolean DetailsBearingOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitBearingOnOff( DetailsBearingOnOff ) - self:F( DetailsBearingOnOff ) - - self.DetailsBearingOnOff = DetailsBearingOnOff - if self.DetailsBearingOnOff == true then - MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Enables / Disables the menus. --- @param #MISSILETRAINER self --- @param #boolean MenusOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitMenusOnOff( MenusOnOff ) - self:F( MenusOnOff ) - - self.MenusOnOff = MenusOnOff - if self.MenusOnOff == true then - MESSAGE:New( "Menus are ENABLED (only when a player rejoins a slot)", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Menus are DISABLED", 15, "Menu" ):ToAll() - end - - return self -end - - --- Menu functions - -function MISSILETRAINER._MenuMessages( MenuParameters ) - - local self = MenuParameters.MenuSelf - - if MenuParameters.MessagesOnOff ~= nil then - self:InitMessagesOnOff( MenuParameters.MessagesOnOff ) - end - - if MenuParameters.TrackingToAll ~= nil then - self:InitTrackingToAll( MenuParameters.TrackingToAll ) - end - - if MenuParameters.TrackingOnOff ~= nil then - self:InitTrackingOnOff( MenuParameters.TrackingOnOff ) - end - - if MenuParameters.TrackingFrequency ~= nil then - self:InitTrackingFrequency( MenuParameters.TrackingFrequency ) - end - - if MenuParameters.AlertsToAll ~= nil then - self:InitAlertsToAll( MenuParameters.AlertsToAll ) - end - - if MenuParameters.AlertsHitsOnOff ~= nil then - self:InitAlertsHitsOnOff( MenuParameters.AlertsHitsOnOff ) - end - - if MenuParameters.AlertsLaunchesOnOff ~= nil then - self:InitAlertsLaunchesOnOff( MenuParameters.AlertsLaunchesOnOff ) - end - - if MenuParameters.DetailsRangeOnOff ~= nil then - self:InitRangeOnOff( MenuParameters.DetailsRangeOnOff ) - end - - if MenuParameters.DetailsBearingOnOff ~= nil then - self:InitBearingOnOff( MenuParameters.DetailsBearingOnOff ) - end - - if MenuParameters.Distance ~= nil then - self.Distance = MenuParameters.Distance - MESSAGE:New( "Hit detection distance set to " .. self.Distance .. " meters", 15, "Menu" ):ToAll() - end - -end - ---- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @param #MISSILETRAINER self --- @param Core.Event#EVENTDATA EventData -function MISSILETRAINER:OnEventShot( EVentData ) - self:F( { EVentData } ) - - local TrainerSourceDCSUnit = EVentData.IniDCSUnit - local TrainerSourceDCSUnitName = EVentData.IniDCSUnitName - local TrainerWeapon = EVentData.Weapon -- Identify the weapon fired - local TrainerWeaponName = EVentData.WeaponName -- return weapon type - - self:T( "Missile Launched = " .. TrainerWeaponName ) - - local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target - if TrainerTargetDCSUnit then - local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit ) - local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill - - self:T(TrainerTargetDCSUnitName ) - - local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName ) - if Client then - - local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit ) - local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit ) - - if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then - - local Message = MESSAGE:New( - string.format( "%s launched a %s", - TrainerSourceUnit:GetTypeName(), - TrainerWeaponName - ) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" ) - - if self.AlertsToAll then - Message:ToAll() - else - Message:ToClient( Client ) - end - end - - local ClientID = Client:GetID() - self:T( ClientID ) - local MissileData = {} - MissileData.TrainerSourceUnit = TrainerSourceUnit - MissileData.TrainerWeapon = TrainerWeapon - MissileData.TrainerTargetUnit = TrainerTargetUnit - MissileData.TrainerWeaponTypeName = TrainerWeapon:getTypeName() - MissileData.TrainerWeaponLaunched = true - table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData ) - --self:T( self.TrackingMissiles ) - end - else - -- TODO: some weapons don't know the target unit... Need to develop a workaround for this. - if ( TrainerWeapon:getTypeName() == "9M311" ) then - SCHEDULER:New( TrainerWeapon, TrainerWeapon.destroy, {}, 1 ) - else - end - end -end - -function MISSILETRAINER:_AddRange( Client, TrainerWeapon ) - - local RangeText = "" - - if self.DetailsRangeOnOff then - - local PositionMissile = TrainerWeapon:getPoint() - local TargetVec3 = Client:GetVec3() - - local Range = ( ( PositionMissile.x - TargetVec3.x )^2 + - ( PositionMissile.y - TargetVec3.y )^2 + - ( PositionMissile.z - TargetVec3.z )^2 - ) ^ 0.5 / 1000 - - RangeText = string.format( ", at %4.2fkm", Range ) - end - - return RangeText -end - -function MISSILETRAINER:_AddBearing( Client, TrainerWeapon ) - - local BearingText = "" - - if self.DetailsBearingOnOff then - - local PositionMissile = TrainerWeapon:getPoint() - local TargetVec3 = Client:GetVec3() - - self:T2( { TargetVec3, PositionMissile }) - - local DirectionVector = { x = PositionMissile.x - TargetVec3.x, y = PositionMissile.y - TargetVec3.y, z = PositionMissile.z - TargetVec3.z } - local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) - --DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget ) - if DirectionRadians < 0 then - DirectionRadians = DirectionRadians + 2 * math.pi - end - local DirectionDegrees = DirectionRadians * 180 / math.pi - - BearingText = string.format( ", %d degrees", DirectionDegrees ) - end - - return BearingText -end - - -function MISSILETRAINER:_TrackMissiles() - self:F2() - - - local ShowMessages = false - if self.MessagesOnOff and self.MessageLastTime + self.TrackingFrequency <= timer.getTime() then - self.MessageLastTime = timer.getTime() - ShowMessages = true - end - - -- ALERTS PART - - -- Loop for all Player Clients to check the alerts and deletion of missiles. - for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do - - local Client = ClientData.Client - self:T2( { Client:GetName() } ) - - for MissileDataID, MissileData in pairs( ClientData.MissileData ) do - self:T3( MissileDataID ) - - local TrainerSourceUnit = MissileData.TrainerSourceUnit - local TrainerWeapon = MissileData.TrainerWeapon - local TrainerTargetUnit = MissileData.TrainerTargetUnit - local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName - local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - - if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then - local PositionMissile = TrainerWeapon:getPosition().p - local TargetVec3 = Client:GetVec3() - - local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 + - ( PositionMissile.y - TargetVec3.y )^2 + - ( PositionMissile.z - TargetVec3.z )^2 - ) ^ 0.5 / 1000 - - if Distance <= self.Distance then - -- Hit alert - TrainerWeapon:destroy() - if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then - - self:T( "killed" ) - - local Message = MESSAGE:New( - string.format( "%s launched by %s killed %s", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetTypeName(), - TrainerTargetUnit:GetPlayerName() - ), 15, "Hit Alert" ) - - if self.AlertsToAll == true then - Message:ToAll() - else - Message:ToClient( Client ) - end - - MissileData = nil - table.remove( ClientData.MissileData, MissileDataID ) - self:T(ClientData.MissileData) - end - end - else - if not ( TrainerWeapon and TrainerWeapon:isExist() ) then - if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then - -- Weapon does not exist anymore. Delete from Table - local Message = MESSAGE:New( - string.format( "%s launched by %s self destructed!", - TrainerWeaponTypeName, - TrainerSourceUnit:GetTypeName() - ), 5, "Tracking" ) - - if self.AlertsToAll == true then - Message:ToAll() - else - Message:ToClient( Client ) - end - end - MissileData = nil - table.remove( ClientData.MissileData, MissileDataID ) - self:T( ClientData.MissileData ) - end - end - end - end - - if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed. - - -- TRACKING PART - - -- For the current client, the missile range and bearing details are displayed To the Player Client. - -- For the other clients, the missile range and bearing details are displayed To the other Player Clients. - -- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information. - - -- Main Player Client loop - for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do - - local Client = ClientData.Client - self:T2( { Client:GetName() } ) - - - ClientData.MessageToClient = "" - ClientData.MessageToAll = "" - - -- Other Players Client loop - for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do - - for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do - self:T3( MissileDataID ) - - local TrainerSourceUnit = MissileData.TrainerSourceUnit - local TrainerWeapon = MissileData.TrainerWeapon - local TrainerTargetUnit = MissileData.TrainerTargetUnit - local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName - local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - - if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then - - if ShowMessages == true then - local TrackingTo - TrackingTo = string.format( " -> %s", - TrainerWeaponTypeName - ) - - if ClientDataID == TrackingDataID then - if ClientData.MessageToClient == "" then - ClientData.MessageToClient = "Missiles to You:\n" - end - ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" - else - if self.TrackingToAll == true then - if ClientData.MessageToAll == "" then - ClientData.MessageToAll = "Missiles to other Players:\n" - end - ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. " ( " .. TrainerTargetUnit:GetPlayerName() .. " )\n" - end - end - end - end - end - end - - -- Once the Player Client and the Other Player Client tracking messages are prepared, show them. - if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then - local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client ) - end - end - end - - return true -end ---- This module contains the AIRBASEPOLICE classes. --- --- === --- --- 1) @{AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Base#BASE} --- ================================================================== --- The @{AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. --- CLIENTS should not be allowed to: --- --- * Don't taxi faster than 40 km/h. --- * Don't take-off on taxiways. --- * Avoid to hit other planes on the airbase. --- * Obey ground control orders. --- --- 2) @{AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} --- ============================================================================================= --- All the airbases on the caucasus map can be monitored using this class. --- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. --- The following names can be given: --- * AnapaVityazevo --- * Batumi --- * Beslan --- * Gelendzhik --- * Gudauta --- * Kobuleti --- * KrasnodarCenter --- * KrasnodarPashkovsky --- * Krymsk --- * Kutaisi --- * MaykopKhanskaya --- * MineralnyeVody --- * Mozdok --- * Nalchik --- * Novorossiysk --- * SenakiKolkhi --- * SochiAdler --- * Soganlug --- * SukhumiBabushara --- * TbilisiLochini --- * Vaziani --- --- 3) @{AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} --- ============================================================================================= --- All the airbases on the NEVADA map can be monitored using this class. --- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. --- The following names can be given: --- * Nellis --- * McCarran --- * Creech --- * Groom Lake --- --- ### Contributions: Dutch Baron - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module AirbasePolice - - - - - ---- @type AIRBASEPOLICE_BASE --- @field Core.Set#SET_CLIENT SetClient --- @extends Core.Base#BASE - -AIRBASEPOLICE_BASE = { - ClassName = "AIRBASEPOLICE_BASE", - SetClient = nil, - Airbases = nil, - AirbaseNames = nil, -} - - ---- Creates a new AIRBASEPOLICE_BASE object. --- @param #AIRBASEPOLICE_BASE self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @param Airbases A table of Airbase Names. --- @return #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:New( SetClient, Airbases ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - self:E( { self.ClassName, SetClient, Airbases } ) - - self.SetClient = SetClient - self.Airbases = Airbases - - for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do - Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(SMOKECOLOR.Red):Flush() - end - end - --- -- Template --- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) --- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) --- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - - self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client - function( Client ) - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0) - Client:SetState( self, "Taxi", false ) - end - ) - - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, {}, 0, 2, 0.05 ) - - return self -end - ---- @type AIRBASEPOLICE_BASE.AirbaseNames --- @list <#string> - ---- Monitor a table of airbase names. --- @param #AIRBASEPOLICE_BASE self --- @param #AIRBASEPOLICE_BASE.AirbaseNames AirbaseNames A list of AirbaseNames to monitor. If this parameters is nil, then all airbases will be monitored. --- @return #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:Monitor( AirbaseNames ) - - if AirbaseNames then - if type( AirbaseNames ) == "table" then - self.AirbaseNames = AirbaseNames - else - self.AirbaseNames = { AirbaseNames } - end - end -end - ---- @param #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:_AirbaseMonitor() - - for AirbaseID, Airbase in pairs( self.Airbases ) do - - if not self.AirbaseNames or self.AirbaseNames[AirbaseID] then - - self:E( AirbaseID ) - - self.SetClient:ForEachClientInZone( Airbase.ZoneBoundary, - - --- @param Wrapper.Client#CLIENT Client - function( Client ) - - self:E( Client.UnitName ) - if Client:IsAlive() then - local NotInRunwayZone = true - for ZoneRunwayID, ZoneRunway in pairs( Airbase.ZoneRunways ) do - NotInRunwayZone = ( Client:IsNotInZone( ZoneRunway ) == true ) and NotInRunwayZone or false - end - - if NotInRunwayZone then - local Taxi = self:GetState( self, "Taxi" ) - self:E( Taxi ) - if Taxi == false then - Client:Message( "Welcome at " .. AirbaseID .. ". The maximum taxiing speed is " .. Airbase.MaximumSpeed " km/h.", 20, "ATC" ) - self:SetState( self, "Taxi", true ) - end - - -- TODO: GetVelocityKMH function usage - local VelocityVec3 = Client:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - -- MESSAGE:New( "Velocity = " .. Velocity, 1 ):ToAll() - local IsAboveRunway = Client:IsAboveRunway() - local IsOnGround = Client:InAir() == false - self:T( IsAboveRunway, IsOnGround ) - - if IsAboveRunway and IsOnGround then - - if Velocity > Airbase.MaximumSpeed then - local IsSpeeding = Client:GetState( self, "Speeding" ) - - if IsSpeeding == true then - local SpeedingWarnings = Client:GetState( self, "Warnings" ) - self:T( SpeedingWarnings ) - - if SpeedingWarnings <= 3 then - Client:Message( "You are speeding on the taxiway! Slow down or you will be removed from this airbase! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Warning " .. SpeedingWarnings .. " / 3" ) - Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) - else - MESSAGE:New( "Player " .. Client:GetPlayerName() .. " has been removed from the airbase, due to a speeding violation ...", 10, "Airbase Police" ):ToAll() - Client:Destroy() - trigger.action.setUserFlag( "AIRCRAFT_"..Client:GetID(), 100) - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - end - - else - Client:Message( "You are speeding on the taxiway, slow down now! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Attention! " ) - Client:SetState( self, "Speeding", true ) - Client:SetState( self, "Warnings", 1 ) - end - - else - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - end - end - - else - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - local Taxi = self:GetState( self, "Taxi" ) - if Taxi == true then - Client:Message( "You have progressed to the runway ... Await take-off clearance ...", 20, "ATC" ) - self:SetState( self, "Taxi", false ) - end - end - end - end - ) - end - end - - return true -end - - ---- @type AIRBASEPOLICE_CAUCASUS --- @field Core.Set#SET_CLIENT SetClient --- @extends #AIRBASEPOLICE_BASE - -AIRBASEPOLICE_CAUCASUS = { - ClassName = "AIRBASEPOLICE_CAUCASUS", - Airbases = { - AnapaVityazevo = { - PointsBoundary = { - [1]={["y"]=242234.85714287,["x"]=-6616.5714285726,}, - [2]={["y"]=241060.57142858,["x"]=-5585.142857144,}, - [3]={["y"]=243806.2857143,["x"]=-3962.2857142868,}, - [4]={["y"]=245240.57142858,["x"]=-4816.5714285726,}, - [5]={["y"]=244783.42857144,["x"]=-5630.8571428583,}, - [6]={["y"]=243800.57142858,["x"]=-5065.142857144,}, - [7]={["y"]=242232.00000001,["x"]=-6622.2857142868,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=242140.57142858,["x"]=-6478.8571428583,}, - [2]={["y"]=242188.57142858,["x"]=-6522.0000000011,}, - [3]={["y"]=244124.2857143,["x"]=-4344.0000000011,}, - [4]={["y"]=244068.2857143,["x"]=-4296.5714285726,}, - [5]={["y"]=242140.57142858,["x"]=-6480.0000000011,} - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Batumi = { - PointsBoundary = { - [1]={["y"]=617567.14285714,["x"]=-355313.14285715,}, - [2]={["y"]=616181.42857142,["x"]=-354800.28571429,}, - [3]={["y"]=616007.14285714,["x"]=-355128.85714286,}, - [4]={["y"]=618230,["x"]=-356914.57142858,}, - [5]={["y"]=618727.14285714,["x"]=-356166,}, - [6]={["y"]=617572.85714285,["x"]=-355308.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=616442.28571429,["x"]=-355090.28571429,}, - [2]={["y"]=618450.57142857,["x"]=-356522,}, - [3]={["y"]=618407.71428571,["x"]=-356584.85714286,}, - [4]={["y"]=618361.99999999,["x"]=-356554.85714286,}, - [5]={["y"]=618324.85714285,["x"]=-356599.14285715,}, - [6]={["y"]=618250.57142856,["x"]=-356543.42857143,}, - [7]={["y"]=618257.7142857,["x"]=-356496.28571429,}, - [8]={["y"]=618237.7142857,["x"]=-356459.14285715,}, - [9]={["y"]=616555.71428571,["x"]=-355258.85714286,}, - [10]={["y"]=616486.28571428,["x"]=-355280.57142858,}, - [11]={["y"]=616410.57142856,["x"]=-355227.71428572,}, - [12]={["y"]=616441.99999999,["x"]=-355179.14285715,}, - [13]={["y"]=616401.99999999,["x"]=-355147.71428572,}, - [14]={["y"]=616441.42857142,["x"]=-355092.57142858,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Beslan = { - PointsBoundary = { - [1]={["y"]=842082.57142857,["x"]=-148445.14285715,}, - [2]={["y"]=845237.71428572,["x"]=-148639.71428572,}, - [3]={["y"]=845232,["x"]=-148765.42857143,}, - [4]={["y"]=844220.57142857,["x"]=-149168.28571429,}, - [5]={["y"]=843274.85714286,["x"]=-149125.42857143,}, - [6]={["y"]=842077.71428572,["x"]=-148554,}, - [7]={["y"]=842083.42857143,["x"]=-148445.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=842104.57142857,["x"]=-148460.57142857,}, - [2]={["y"]=845225.71428572,["x"]=-148656,}, - [3]={["y"]=845220.57142858,["x"]=-148750,}, - [4]={["y"]=842098.85714286,["x"]=-148556.28571429,}, - [5]={["y"]=842104,["x"]=-148460.28571429,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Gelendzhik = { - PointsBoundary = { - [1]={["y"]=297856.00000001,["x"]=-51151.428571429,}, - [2]={["y"]=299044.57142858,["x"]=-49720.000000001,}, - [3]={["y"]=298861.71428572,["x"]=-49580.000000001,}, - [4]={["y"]=298198.85714286,["x"]=-49842.857142858,}, - [5]={["y"]=297990.28571429,["x"]=-50151.428571429,}, - [6]={["y"]=297696.00000001,["x"]=-51054.285714286,}, - [7]={["y"]=297850.28571429,["x"]=-51160.000000001,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=297834.00000001,["x"]=-51107.428571429,}, - [2]={["y"]=297786.57142858,["x"]=-51068.857142858,}, - [3]={["y"]=298946.57142858,["x"]=-49686.000000001,}, - [4]={["y"]=298993.14285715,["x"]=-49725.714285715,}, - [5]={["y"]=297835.14285715,["x"]=-51107.714285715,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Gudauta = { - PointsBoundary = { - [1]={["y"]=517246.57142857,["x"]=-197850.28571429,}, - [2]={["y"]=516749.42857142,["x"]=-198070.28571429,}, - [3]={["y"]=515755.14285714,["x"]=-197598.85714286,}, - [4]={["y"]=515369.42857142,["x"]=-196538.85714286,}, - [5]={["y"]=515623.71428571,["x"]=-195618.85714286,}, - [6]={["y"]=515946.57142857,["x"]=-195510.28571429,}, - [7]={["y"]=517243.71428571,["x"]=-197858.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=517096.57142857,["x"]=-197804.57142857,}, - [2]={["y"]=515880.85714285,["x"]=-195590.28571429,}, - [3]={["y"]=515812.28571428,["x"]=-195628.85714286,}, - [4]={["y"]=517036.57142857,["x"]=-197834.57142857,}, - [5]={["y"]=517097.99999999,["x"]=-197807.42857143,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Kobuleti = { - PointsBoundary = { - [1]={["y"]=634427.71428571,["x"]=-318290.28571429,}, - [2]={["y"]=635033.42857143,["x"]=-317550.2857143,}, - [3]={["y"]=635864.85714286,["x"]=-317333.14285715,}, - [4]={["y"]=636967.71428571,["x"]=-317261.71428572,}, - [5]={["y"]=637144.85714286,["x"]=-317913.14285715,}, - [6]={["y"]=634630.57142857,["x"]=-318687.42857144,}, - [7]={["y"]=634424.85714286,["x"]=-318290.2857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=634509.71428571,["x"]=-318339.42857144,}, - [2]={["y"]=636767.42857143,["x"]=-317516.57142858,}, - [3]={["y"]=636790,["x"]=-317575.71428572,}, - [4]={["y"]=634531.42857143,["x"]=-318398.00000001,}, - [5]={["y"]=634510.28571429,["x"]=-318339.71428572,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - KrasnodarCenter = { - PointsBoundary = { - [1]={["y"]=366680.28571429,["x"]=11699.142857142,}, - [2]={["y"]=366654.28571429,["x"]=11225.142857142,}, - [3]={["y"]=367497.14285715,["x"]=11082.285714285,}, - [4]={["y"]=368025.71428572,["x"]=10396.57142857,}, - [5]={["y"]=369854.28571429,["x"]=11367.999999999,}, - [6]={["y"]=369840.00000001,["x"]=11910.857142856,}, - [7]={["y"]=366682.57142858,["x"]=11697.999999999,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=369205.42857144,["x"]=11789.142857142,}, - [2]={["y"]=369209.71428572,["x"]=11714.857142856,}, - [3]={["y"]=366699.71428572,["x"]=11581.714285713,}, - [4]={["y"]=366698.28571429,["x"]=11659.142857142,}, - [5]={["y"]=369208.85714286,["x"]=11788.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - KrasnodarPashkovsky = { - PointsBoundary = { - [1]={["y"]=386754,["x"]=6476.5714285703,}, - [2]={["y"]=389182.57142858,["x"]=8722.2857142846,}, - [3]={["y"]=388832.57142858,["x"]=9086.5714285703,}, - [4]={["y"]=386961.14285715,["x"]=7707.9999999989,}, - [5]={["y"]=385404,["x"]=9179.4285714274,}, - [6]={["y"]=383239.71428572,["x"]=7386.5714285703,}, - [7]={["y"]=383954,["x"]=6486.5714285703,}, - [8]={["y"]=385775.42857143,["x"]=8097.9999999989,}, - [9]={["y"]=386804,["x"]=7319.4285714274,}, - [10]={["y"]=386375.42857143,["x"]=6797.9999999989,}, - [11]={["y"]=386746.85714286,["x"]=6472.2857142846,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=385891.14285715,["x"]=8416.5714285703,}, - [2]={["y"]=385842.28571429,["x"]=8467.9999999989,}, - [3]={["y"]=384180.85714286,["x"]=6917.1428571417,}, - [4]={["y"]=384228.57142858,["x"]=6867.7142857132,}, - [5]={["y"]=385891.14285715,["x"]=8416.5714285703,}, - }, - [2] = { - [1]={["y"]=386714.85714286,["x"]=6674.857142856,}, - [2]={["y"]=386757.71428572,["x"]=6627.7142857132,}, - [3]={["y"]=389028.57142858,["x"]=8741.4285714275,}, - [4]={["y"]=388981.71428572,["x"]=8790.5714285703,}, - [5]={["y"]=386714.57142858,["x"]=6674.5714285703,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Krymsk = { - PointsBoundary = { - [1]={["y"]=293338.00000001,["x"]=-7575.4285714297,}, - [2]={["y"]=295199.42857144,["x"]=-5434.0000000011,}, - [3]={["y"]=295595.14285715,["x"]=-6239.7142857154,}, - [4]={["y"]=294152.2857143,["x"]=-8325.4285714297,}, - [5]={["y"]=293345.14285715,["x"]=-7596.8571428582,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=293522.00000001,["x"]=-7567.4285714297,}, - [2]={["y"]=293578.57142858,["x"]=-7616.0000000011,}, - [3]={["y"]=295246.00000001,["x"]=-5591.142857144,}, - [4]={["y"]=295187.71428573,["x"]=-5546.0000000011,}, - [5]={["y"]=293523.14285715,["x"]=-7568.2857142868,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Kutaisi = { - PointsBoundary = { - [1]={["y"]=682087.42857143,["x"]=-284512.85714286,}, - [2]={["y"]=685387.42857143,["x"]=-283662.85714286,}, - [3]={["y"]=685294.57142857,["x"]=-284977.14285715,}, - [4]={["y"]=682744.57142857,["x"]=-286505.71428572,}, - [5]={["y"]=682094.57142857,["x"]=-284527.14285715,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=682638,["x"]=-285202.28571429,}, - [2]={["y"]=685050.28571429,["x"]=-284507.42857144,}, - [3]={["y"]=685068.85714286,["x"]=-284578.85714286,}, - [4]={["y"]=682657.42857143,["x"]=-285264.28571429,}, - [5]={["y"]=682638.28571429,["x"]=-285202.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - MaykopKhanskaya = { - PointsBoundary = { - [1]={["y"]=456876.28571429,["x"]=-27665.42857143,}, - [2]={["y"]=457800,["x"]=-28392.857142858,}, - [3]={["y"]=459368.57142857,["x"]=-26378.571428573,}, - [4]={["y"]=459425.71428572,["x"]=-25242.857142858,}, - [5]={["y"]=458961.42857143,["x"]=-24964.285714287,}, - [6]={["y"]=456878.57142857,["x"]=-27667.714285715,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=457005.42857143,["x"]=-27668.000000001,}, - [2]={["y"]=459028.85714286,["x"]=-25168.857142858,}, - [3]={["y"]=459082.57142857,["x"]=-25216.857142858,}, - [4]={["y"]=457060,["x"]=-27714.285714287,}, - [5]={["y"]=457004.57142857,["x"]=-27669.714285715,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - MineralnyeVody = { - PointsBoundary = { - [1]={["y"]=703857.14285714,["x"]=-50226.000000002,}, - [2]={["y"]=707385.71428571,["x"]=-51911.714285716,}, - [3]={["y"]=707595.71428571,["x"]=-51434.857142859,}, - [4]={["y"]=707900,["x"]=-51568.857142859,}, - [5]={["y"]=707542.85714286,["x"]=-52326.000000002,}, - [6]={["y"]=706628.57142857,["x"]=-52568.857142859,}, - [7]={["y"]=705142.85714286,["x"]=-51790.285714288,}, - [8]={["y"]=703678.57142857,["x"]=-50611.714285716,}, - [9]={["y"]=703857.42857143,["x"]=-50226.857142859,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=703904,["x"]=-50352.571428573,}, - [2]={["y"]=707596.28571429,["x"]=-52094.571428573,}, - [3]={["y"]=707560.57142858,["x"]=-52161.714285716,}, - [4]={["y"]=703871.71428572,["x"]=-50420.571428573,}, - [5]={["y"]=703902,["x"]=-50352.000000002,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Mozdok = { - PointsBoundary = { - [1]={["y"]=832123.42857143,["x"]=-83608.571428573,}, - [2]={["y"]=835916.28571429,["x"]=-83144.285714288,}, - [3]={["y"]=835474.28571429,["x"]=-84170.571428573,}, - [4]={["y"]=832911.42857143,["x"]=-84470.571428573,}, - [5]={["y"]=832487.71428572,["x"]=-85565.714285716,}, - [6]={["y"]=831573.42857143,["x"]=-85351.42857143,}, - [7]={["y"]=832123.71428572,["x"]=-83610.285714288,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=832201.14285715,["x"]=-83699.428571431,}, - [2]={["y"]=832212.57142857,["x"]=-83780.571428574,}, - [3]={["y"]=835730.28571429,["x"]=-83335.714285717,}, - [4]={["y"]=835718.85714286,["x"]=-83246.571428574,}, - [5]={["y"]=832200.57142857,["x"]=-83700.000000002,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Nalchik = { - PointsBoundary = { - [1]={["y"]=759370,["x"]=-125502.85714286,}, - [2]={["y"]=761384.28571429,["x"]=-124177.14285714,}, - [3]={["y"]=761472.85714286,["x"]=-124325.71428572,}, - [4]={["y"]=761092.85714286,["x"]=-125048.57142857,}, - [5]={["y"]=760295.71428572,["x"]=-125685.71428572,}, - [6]={["y"]=759444.28571429,["x"]=-125734.28571429,}, - [7]={["y"]=759375.71428572,["x"]=-125511.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=759454.28571429,["x"]=-125551.42857143,}, - [2]={["y"]=759492.85714286,["x"]=-125610.85714286,}, - [3]={["y"]=761406.28571429,["x"]=-124304.28571429,}, - [4]={["y"]=761361.14285714,["x"]=-124239.71428572,}, - [5]={["y"]=759456,["x"]=-125552.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Novorossiysk = { - PointsBoundary = { - [1]={["y"]=278677.71428573,["x"]=-41656.571428572,}, - [2]={["y"]=278446.2857143,["x"]=-41453.714285715,}, - [3]={["y"]=278989.14285716,["x"]=-40188.000000001,}, - [4]={["y"]=279717.71428573,["x"]=-39968.000000001,}, - [5]={["y"]=280020.57142859,["x"]=-40208.000000001,}, - [6]={["y"]=278674.85714287,["x"]=-41660.857142858,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=278673.14285716,["x"]=-41615.142857144,}, - [2]={["y"]=278625.42857144,["x"]=-41570.571428572,}, - [3]={["y"]=279835.42857144,["x"]=-40226.000000001,}, - [4]={["y"]=279882.2857143,["x"]=-40270.000000001,}, - [5]={["y"]=278672.00000001,["x"]=-41614.857142858,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SenakiKolkhi = { - PointsBoundary = { - [1]={["y"]=646036.57142857,["x"]=-281778.85714286,}, - [2]={["y"]=646045.14285714,["x"]=-281191.71428571,}, - [3]={["y"]=647032.28571429,["x"]=-280598.85714285,}, - [4]={["y"]=647669.42857143,["x"]=-281273.14285714,}, - [5]={["y"]=648323.71428571,["x"]=-281370.28571428,}, - [6]={["y"]=648520.85714286,["x"]=-281978.85714285,}, - [7]={["y"]=646039.42857143,["x"]=-281783.14285714,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=646060.85714285,["x"]=-281736,}, - [2]={["y"]=646056.57142857,["x"]=-281631.71428571,}, - [3]={["y"]=648442.28571428,["x"]=-281840.28571428,}, - [4]={["y"]=648432.28571428,["x"]=-281918.85714286,}, - [5]={["y"]=646063.71428571,["x"]=-281738.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SochiAdler = { - PointsBoundary = { - [1]={["y"]=460642.28571428,["x"]=-164861.71428571,}, - [2]={["y"]=462820.85714285,["x"]=-163368.85714286,}, - [3]={["y"]=463649.42857142,["x"]=-163340.28571429,}, - [4]={["y"]=463835.14285714,["x"]=-164040.28571429,}, - [5]={["y"]=462535.14285714,["x"]=-165654.57142857,}, - [6]={["y"]=460678,["x"]=-165247.42857143,}, - [7]={["y"]=460635.14285714,["x"]=-164876,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=460831.42857143,["x"]=-165180,}, - [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, - [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, - [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, - [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, - }, - [2] = { - [1]={["y"]=460831.42857143,["x"]=-165180,}, - [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, - [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, - [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, - [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Soganlug = { - PointsBoundary = { - [1]={["y"]=894530.85714286,["x"]=-316928.28571428,}, - [2]={["y"]=896422.28571428,["x"]=-318622.57142857,}, - [3]={["y"]=896090.85714286,["x"]=-318934,}, - [4]={["y"]=894019.42857143,["x"]=-317119.71428571,}, - [5]={["y"]=894533.71428571,["x"]=-316925.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=894525.71428571,["x"]=-316964,}, - [2]={["y"]=896363.14285714,["x"]=-318634.28571428,}, - [3]={["y"]=896299.14285714,["x"]=-318702.85714286,}, - [4]={["y"]=894464,["x"]=-317031.71428571,}, - [5]={["y"]=894524.57142857,["x"]=-316963.71428571,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SukhumiBabushara = { - PointsBoundary = { - [1]={["y"]=562541.14285714,["x"]=-219852.28571429,}, - [2]={["y"]=562691.14285714,["x"]=-219395.14285714,}, - [3]={["y"]=564326.85714286,["x"]=-219523.71428571,}, - [4]={["y"]=566262.57142857,["x"]=-221166.57142857,}, - [5]={["y"]=566069.71428571,["x"]=-221580.85714286,}, - [6]={["y"]=562534,["x"]=-219873.71428571,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=562684,["x"]=-219779.71428571,}, - [2]={["y"]=562717.71428571,["x"]=-219718,}, - [3]={["y"]=566046.85714286,["x"]=-221376.57142857,}, - [4]={["y"]=566012.28571428,["x"]=-221446.57142857,}, - [5]={["y"]=562684.57142857,["x"]=-219782.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - TbilisiLochini = { - PointsBoundary = { - [1]={["y"]=895172.85714286,["x"]=-314667.42857143,}, - [2]={["y"]=895337.42857143,["x"]=-314143.14285714,}, - [3]={["y"]=895990.28571429,["x"]=-314036,}, - [4]={["y"]=897730.28571429,["x"]=-315284.57142857,}, - [5]={["y"]=897901.71428571,["x"]=-316284.57142857,}, - [6]={["y"]=897684.57142857,["x"]=-316618.85714286,}, - [7]={["y"]=895173.14285714,["x"]=-314667.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=895261.14285715,["x"]=-314652.28571428,}, - [2]={["y"]=897654.57142857,["x"]=-316523.14285714,}, - [3]={["y"]=897711.71428571,["x"]=-316450.28571429,}, - [4]={["y"]=895327.42857143,["x"]=-314568.85714286,}, - [5]={["y"]=895261.71428572,["x"]=-314656,}, - }, - [2] = { - [1]={["y"]=895605.71428572,["x"]=-314724.57142857,}, - [2]={["y"]=897639.71428572,["x"]=-316148,}, - [3]={["y"]=897683.42857143,["x"]=-316087.14285714,}, - [4]={["y"]=895650,["x"]=-314660,}, - [5]={["y"]=895606,["x"]=-314724.85714286,} - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Vaziani = { - PointsBoundary = { - [1]={["y"]=902122,["x"]=-318163.71428572,}, - [2]={["y"]=902678.57142857,["x"]=-317594,}, - [3]={["y"]=903275.71428571,["x"]=-317405.42857143,}, - [4]={["y"]=903418.57142857,["x"]=-317891.14285714,}, - [5]={["y"]=904292.85714286,["x"]=-318748.28571429,}, - [6]={["y"]=904542,["x"]=-319740.85714286,}, - [7]={["y"]=904042,["x"]=-320166.57142857,}, - [8]={["y"]=902121.42857143,["x"]=-318164.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=902239.14285714,["x"]=-318190.85714286,}, - [2]={["y"]=904014.28571428,["x"]=-319994.57142857,}, - [3]={["y"]=904064.85714285,["x"]=-319945.14285715,}, - [4]={["y"]=902294.57142857,["x"]=-318146,}, - [5]={["y"]=902247.71428571,["x"]=-318190.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - }, -} - ---- Creates a new AIRBASEPOLICE_CAUCASUS object. --- @param #AIRBASEPOLICE_CAUCASUS self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @return #AIRBASEPOLICE_CAUCASUS self -function AIRBASEPOLICE_CAUCASUS:New( SetClient ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AIRBASEPOLICE_BASE:New( SetClient, self.Airbases ) ) - - -- -- AnapaVityazevo - -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) - -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) - -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Batumi - -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) - -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) - -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Beslan - -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) - -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) - -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Gelendzhik - -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) - -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) - -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Gudauta - -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) - -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) - -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Kobuleti - -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) - -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) - -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- KrasnodarCenter - -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) - -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) - -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- KrasnodarPashkovsky - -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Krymsk - -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) - -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) - -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Kutaisi - -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) - -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) - -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- MaykopKhanskaya - -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) - -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) - -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- MineralnyeVody - -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) - -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) - -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Mozdok - -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) - -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) - -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Nalchik - -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) - -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) - -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Novorossiysk - -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) - -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) - -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SenakiKolkhi - -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) - -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) - -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SochiAdler - -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) - -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) - -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) - -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Soganlug - -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) - -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) - -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- SukhumiBabushara - -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) - -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) - -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- TbilisiLochini - -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) - -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - -- -- Vaziani - -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) - -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) - -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - -- - -- - -- - - - -- Template - -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) - -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() - -- - -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) - -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() - - return self - -end - - - - ---- @type AIRBASEPOLICE_NEVADA --- @extends Functional.AirbasePolice#AIRBASEPOLICE_BASE -AIRBASEPOLICE_NEVADA = { - ClassName = "AIRBASEPOLICE_NEVADA", - Airbases = { - Nellis = { - PointsBoundary = { - [1]={["y"]=-17814.714285714,["x"]=-399823.14285714,}, - [2]={["y"]=-16875.857142857,["x"]=-398763.14285714,}, - [3]={["y"]=-16251.571428571,["x"]=-398988.85714286,}, - [4]={["y"]=-16163,["x"]=-398693.14285714,}, - [5]={["y"]=-16328.714285714,["x"]=-398034.57142857,}, - [6]={["y"]=-15943,["x"]=-397571.71428571,}, - [7]={["y"]=-15711.571428571,["x"]=-397551.71428571,}, - [8]={["y"]=-15748.714285714,["x"]=-396806,}, - [9]={["y"]=-16288.714285714,["x"]=-396517.42857143,}, - [10]={["y"]=-16751.571428571,["x"]=-396308.85714286,}, - [11]={["y"]=-17263,["x"]=-396234.57142857,}, - [12]={["y"]=-17577.285714286,["x"]=-396640.28571429,}, - [13]={["y"]=-17614.428571429,["x"]=-397400.28571429,}, - [14]={["y"]=-19405.857142857,["x"]=-399428.85714286,}, - [15]={["y"]=-19234.428571429,["x"]=-399683.14285714,}, - [16]={["y"]=-18708.714285714,["x"]=-399408.85714286,}, - [17]={["y"]=-18397.285714286,["x"]=-399657.42857143,}, - [18]={["y"]=-17814.428571429,["x"]=-399823.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-18687,["x"]=-399380.28571429,}, - [2]={["y"]=-18620.714285714,["x"]=-399436.85714286,}, - [3]={["y"]=-16217.857142857,["x"]=-396596.85714286,}, - [4]={["y"]=-16300.142857143,["x"]=-396530,}, - [5]={["y"]=-18687,["x"]=-399380.85714286,}, - }, - [2] = { - [1]={["y"]=-18451.571428572,["x"]=-399580.57142857,}, - [2]={["y"]=-18392.142857143,["x"]=-399628.57142857,}, - [3]={["y"]=-16011,["x"]=-396806.85714286,}, - [4]={["y"]=-16074.714285714,["x"]=-396751.71428572,}, - [5]={["y"]=-18451.571428572,["x"]=-399580.85714285,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - McCarran = { - PointsBoundary = { - [1]={["y"]=-29455.285714286,["x"]=-416277.42857142,}, - [2]={["y"]=-28860.142857143,["x"]=-416492,}, - [3]={["y"]=-25044.428571429,["x"]=-416344.85714285,}, - [4]={["y"]=-24580.142857143,["x"]=-415959.14285714,}, - [5]={["y"]=-25073,["x"]=-415630.57142857,}, - [6]={["y"]=-25087.285714286,["x"]=-415130.57142857,}, - [7]={["y"]=-25830.142857143,["x"]=-414866.28571428,}, - [8]={["y"]=-26658.714285715,["x"]=-414880.57142857,}, - [9]={["y"]=-26973,["x"]=-415273.42857142,}, - [10]={["y"]=-27380.142857143,["x"]=-415187.71428571,}, - [11]={["y"]=-27715.857142857,["x"]=-414144.85714285,}, - [12]={["y"]=-27551.571428572,["x"]=-413473.42857142,}, - [13]={["y"]=-28630.142857143,["x"]=-413201.99999999,}, - [14]={["y"]=-29494.428571429,["x"]=-415437.71428571,}, - [15]={["y"]=-29455.571428572,["x"]=-416277.71428571,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-29408.428571429,["x"]=-416016.28571428,}, - [2]={["y"]=-29408.142857144,["x"]=-416105.42857142,}, - [3]={["y"]=-24680.714285715,["x"]=-416003.14285713,}, - [4]={["y"]=-24681.857142858,["x"]=-415926.57142856,}, - [5]={["y"]=-29408.42857143,["x"]=-416016.57142856,}, - }, - [2] = { - [1]={["y"]=-28575.571428572,["x"]=-416303.14285713,}, - [2]={["y"]=-28575.571428572,["x"]=-416382.57142856,}, - [3]={["y"]=-25111.000000001,["x"]=-416309.7142857,}, - [4]={["y"]=-25111.000000001,["x"]=-416249.14285713,}, - [5]={["y"]=-28575.571428572,["x"]=-416303.7142857,}, - }, - [3] = { - [1]={["y"]=-29331.000000001,["x"]=-416275.42857141,}, - [2]={["y"]=-29259.000000001,["x"]=-416306.85714284,}, - [3]={["y"]=-28005.571428572,["x"]=-413449.7142857,}, - [4]={["y"]=-28068.714285715,["x"]=-413422.85714284,}, - [5]={["y"]=-29331.000000001,["x"]=-416275.7142857,}, - }, - [4] = { - [1]={["y"]=-29073.285714286,["x"]=-416386.57142856,}, - [2]={["y"]=-28997.285714286,["x"]=-416417.42857141,}, - [3]={["y"]=-27697.571428572,["x"]=-413464.57142856,}, - [4]={["y"]=-27767.857142858,["x"]=-413434.28571427,}, - [5]={["y"]=-29073.000000001,["x"]=-416386.85714284,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Creech = { - PointsBoundary = { - [1]={["y"]=-74522.714285715,["x"]=-360887.99999998,}, - [2]={["y"]=-74197,["x"]=-360556.57142855,}, - [3]={["y"]=-74402.714285715,["x"]=-359639.42857141,}, - [4]={["y"]=-74637,["x"]=-359279.42857141,}, - [5]={["y"]=-75759.857142857,["x"]=-359005.14285712,}, - [6]={["y"]=-75834.142857143,["x"]=-359045.14285712,}, - [7]={["y"]=-75902.714285714,["x"]=-359782.28571427,}, - [8]={["y"]=-76099.857142857,["x"]=-360399.42857141,}, - [9]={["y"]=-77314.142857143,["x"]=-360219.42857141,}, - [10]={["y"]=-77728.428571429,["x"]=-360445.14285713,}, - [11]={["y"]=-77585.571428571,["x"]=-360585.14285713,}, - [12]={["y"]=-76471.285714286,["x"]=-360819.42857141,}, - [13]={["y"]=-76325.571428571,["x"]=-360942.28571427,}, - [14]={["y"]=-74671.857142857,["x"]=-360927.7142857,}, - [15]={["y"]=-74522.714285714,["x"]=-360888.85714284,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-74237.571428571,["x"]=-360591.7142857,}, - [2]={["y"]=-74234.428571429,["x"]=-360493.71428571,}, - [3]={["y"]=-77605.285714286,["x"]=-360399.14285713,}, - [4]={["y"]=-77608.714285715,["x"]=-360498.85714285,}, - [5]={["y"]=-74237.857142857,["x"]=-360591.7142857,}, - }, - [2] = { - [1]={["y"]=-75807.571428572,["x"]=-359073.42857142,}, - [2]={["y"]=-74770.142857144,["x"]=-360581.71428571,}, - [3]={["y"]=-74641.285714287,["x"]=-360585.42857142,}, - [4]={["y"]=-75734.142857144,["x"]=-359023.14285714,}, - [5]={["y"]=-75807.285714287,["x"]=-359073.42857142,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - GroomLake = { - PointsBoundary = { - [1]={["y"]=-88916.714285714,["x"]=-289102.28571425,}, - [2]={["y"]=-87023.571428572,["x"]=-290388.57142857,}, - [3]={["y"]=-85916.428571429,["x"]=-290674.28571428,}, - [4]={["y"]=-87645.000000001,["x"]=-286567.14285714,}, - [5]={["y"]=-88380.714285715,["x"]=-286388.57142857,}, - [6]={["y"]=-89670.714285715,["x"]=-283524.28571428,}, - [7]={["y"]=-89797.857142858,["x"]=-283567.14285714,}, - [8]={["y"]=-88635.000000001,["x"]=-286749.99999999,}, - [9]={["y"]=-89177.857142858,["x"]=-287207.14285714,}, - [10]={["y"]=-89092.142857144,["x"]=-288892.85714285,}, - [11]={["y"]=-88917.000000001,["x"]=-289102.85714285,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-86039.000000001,["x"]=-290606.28571428,}, - [2]={["y"]=-85965.285714287,["x"]=-290573.99999999,}, - [3]={["y"]=-87692.714285715,["x"]=-286634.85714285,}, - [4]={["y"]=-87756.714285715,["x"]=-286663.99999999,}, - [5]={["y"]=-86038.714285715,["x"]=-290606.85714285,}, - }, - [2] = { - [1]={["y"]=-86808.428571429,["x"]=-290375.7142857,}, - [2]={["y"]=-86732.714285715,["x"]=-290344.28571427,}, - [3]={["y"]=-89672.714285714,["x"]=-283546.57142855,}, - [4]={["y"]=-89772.142857143,["x"]=-283587.71428569,}, - [5]={["y"]=-86808.142857143,["x"]=-290375.7142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - }, -} - ---- Creates a new AIRBASEPOLICE_NEVADA object. --- @param #AIRBASEPOLICE_NEVADA self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @return #AIRBASEPOLICE_NEVADA self -function AIRBASEPOLICE_NEVADA:New( SetClient ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AIRBASEPOLICE_BASE:New( SetClient, self.Airbases ) ) - --- -- Nellis --- local NellisBoundary = GROUP:FindByName( "Nellis Boundary" ) --- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local NellisRunway1 = GROUP:FindByName( "Nellis Runway 1" ) --- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local NellisRunway2 = GROUP:FindByName( "Nellis Runway 2" ) --- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- McCarran --- local McCarranBoundary = GROUP:FindByName( "McCarran Boundary" ) --- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local McCarranRunway1 = GROUP:FindByName( "McCarran Runway 1" ) --- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway2 = GROUP:FindByName( "McCarran Runway 2" ) --- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway3 = GROUP:FindByName( "McCarran Runway 3" ) --- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local McCarranRunway4 = GROUP:FindByName( "McCarran Runway 4" ) --- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- Creech --- local CreechBoundary = GROUP:FindByName( "Creech Boundary" ) --- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local CreechRunway1 = GROUP:FindByName( "Creech Runway 1" ) --- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local CreechRunway2 = GROUP:FindByName( "Creech Runway 2" ) --- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- -- Groom Lake --- local GroomLakeBoundary = GROUP:FindByName( "GroomLake Boundary" ) --- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(SMOKECOLOR.White):Flush() --- --- local GroomLakeRunway1 = GROUP:FindByName( "GroomLake Runway 1" ) --- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() --- --- local GroomLakeRunway2 = GROUP:FindByName( "GroomLake Runway 2" ) --- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() - -end - - - - - - --- This module contains the DETECTION classes. --- --- === --- --- # 1) @{#DETECTION_BASE} class, extends @{Fsm#FSM} --- --- The @{#DETECTION_BASE} class defines the core functions to administer detected objects. --- The @{#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). --- --- ## 1.1) DETECTION_BASE constructor --- --- Construct a new DETECTION_BASE instance using the @{#DETECTION_BASE.New}() method. --- --- ## 1.2) DETECTION_BASE initialization --- --- By default, detection will return detected objects with all the detection sensors available. --- However, you can ask how the objects were found with specific detection methods. --- If you use one of the below methods, the detection will work with the detection method specified. --- You can specify to apply multiple detection methods. --- --- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: --- --- * @{#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. --- * @{#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. --- * @{#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. --- * @{#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. --- * @{#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. --- * @{#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. --- --- ## 1.3) DETECTION_BASE derived classes group the detected units into a DetectedItems[] list --- --- DETECTION_BASE derived classes build a list called DetectedItems[], which is essentially a first later --- of grouping of detected units. Each DetectedItem within the DetectedItems[] list contains --- a SET_UNIT object that contains the detected units that belong to that group. --- --- Derived classes will apply different methods to group the detected units. --- Examples are per area, per quadrant, per distance, per type. --- See further the derived DETECTION classes on which grouping methods are currently supported. --- --- Various methods exist how to retrieve the grouped items from a DETECTION_BASE derived class: --- --- * The method @{Detection#DETECTION_BASE.GetDetectedItems}() retrieves the DetectedItems[] list. --- * A DetectedItem from the DetectedItems[] list can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedItem}( DetectedItemIndex ). --- Note that this method returns a DetectedItem element from the list, that contains a Set variable and further information --- about the DetectedItem that is set by the DETECTION_BASE derived classes, used to group the DetectedItem. --- * A DetectedSet from the DetectedItems[] list can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSet}( DetectedItemIndex ). --- This method retrieves the Set from a DetectedItem element from the DetectedItem list (DetectedItems[ DetectedItemIndex ].Set ). --- --- ## 1.4) Apply additional Filters to fine-tune the detected objects --- --- By default, DCS World will return any object that is in LOS and within "visual reach", or detectable through one of the electronic detection means. --- That being said, the DCS World detection algorithm can sometimes be unrealistic. --- Especially for a visual detection, DCS World is able to report within 1 second a detailed detection of a group of 20 units (including types of the units) that are 10 kilometers away, using only visual capabilities. --- Additionally, trees and other obstacles are not accounted during the DCS World detection. --- --- Therefore, an additional (optional) filtering has been built into the DETECTION_BASE class, that can be set for visual detected units. --- For electronic detection, this filtering is not applied, only for visually detected targets. --- --- The following additional filtering can be applied for visual filtering: --- --- * A probability factor per kilometer distance. --- * A probability factor based on the alpha angle between the detected object and the unit detecting. --- A detection from a higher altitude allows for better detection than when on the ground. --- * Define a probability factor for "cloudy zones", which are zones where forests or villages are located. In these zones, detection will be much more difficult. --- The mission designer needs to define these cloudy zones within the mission, and needs to register these zones in the DETECTION_ objects additing a probability factor per zone. --- --- I advise however, that, when you first use the DETECTION derived classes, that you don't use these filters. --- Only when you experience unrealistic behaviour in your missions, these filters could be applied. --- --- ### 1.4.1 ) Distance visual detection probability --- --- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly. --- Also, the speed of accurate detection plays a role. --- --- A distance probability factor between 0 and 1 can be given, that will model a linear extrapolated probability over 10 km distance. --- --- For example, if a probability factor of 0.6 (60%) is given, the extrapolated probabilities over 15 kilometers would like like: --- 1 km: 96%, 2 km: 92%, 3 km: 88%, 4 km: 84%, 5 km: 80%, 6 km: 76%, 7 km: 72%, 8 km: 68%, 9 km: 64%, 10 km: 60%, 11 km: 56%, 12 km: 52%, 13 km: 48%, 14 km: 44%, 15 km: 40%. --- --- Note that based on this probability factor, not only the detection but also the **type** of the unit will be applied! --- --- Use the method @{Detection#DETECTION_BASE.SetDistanceProbability}() to set the probability factor upon a 10 km distance. --- --- ### 1.4.2 ) Alpha Angle visual detection probability --- --- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. --- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. --- --- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. --- --- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: --- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% --- --- Use the method @{Detection#DETECTION_BASE.SetAlphaAngleProbability}() to set the probability factor if 0°. --- --- ### 1.4.3 ) Cloudy Zones detection probability --- --- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully. --- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission --- zones that reflect cloudy areas where detected units may not be so easily visually detected. --- --- Use the method @{Detection#DETECTION_BASE.SetZoneProbability}() to set for a defined number of zones, the probability factors. --- --- Note however, that the more zones are defined to be "cloudy" within a detection, the more performance it will take --- from the DETECTION_BASE to calculate the presence of the detected unit within each zone. --- Expecially for ZONE_POLYGON, try to limit the amount of nodes of the polygon! --- --- Typically, this kind of filter would be applied for very specific areas were a detection needs to be very realisting for --- AI not to detect so easily targets within a forrest or village rich area. --- --- ## 1.5 ) Accept / Reject detected units --- --- DETECTION_BASE can accept or reject successful detections based on the location of the detected object, --- if it is located in range or located inside or outside of specific zones. --- --- ### 1.5.1 ) Detection acceptance of within range limit --- --- A range can be set that will limit a successful detection for a unit. --- Use the method @{Detection#DETECTION_BASE.SetAcceptRange}() to apply a range in meters till where detected units will be accepted. --- --- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. --- --- -- Build a detect object. --- local Detection = DETECTION_BASE:New( SetGroup ) --- --- -- This will accept detected units if the range is below 5000 meters. --- Detection:SetAcceptRange( 5000 ) --- --- -- Start the Detection. --- Detection:Start() --- --- --- ### 1.5.2 ) Detection acceptance if within zone(s). --- --- Specific ZONE_BASE object(s) can be given as a parameter, which will only accept a detection if the unit is within the specified ZONE_BASE object(s). --- Use the method @{Detection#DETECTION_BASE.SetAcceptZones}() will accept detected units if they are within the specified zones. --- --- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. --- --- -- Search fo the zones where units are to be accepted. --- local ZoneAccept1 = ZONE:New( "AcceptZone1" ) --- local ZoneAccept2 = ZONE:New( "AcceptZone2" ) --- --- -- Build a detect object. --- local Detection = DETECTION_BASE:New( SetGroup ) --- --- -- This will accept detected units by Detection when the unit is within ZoneAccept1 OR ZoneAccept2. --- Detection:SetAcceptZones( { ZoneAccept1, ZoneAccept2 } ) --- --- -- Start the Detection. --- Detection:Start() --- --- ### 1.5.3 ) Detection rejectance if within zone(s). --- --- Specific ZONE_BASE object(s) can be given as a parameter, which will reject detection if the unit is within the specified ZONE_BASE object(s). --- Use the method @{Detection#DETECTION_BASE.SetRejectZones}() will reject detected units if they are within the specified zones. --- An example of how to use the method is shown below. --- --- local SetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() -- Build a SetGroup of Forward Air Controllers. --- --- -- Search fo the zones where units are to be rejected. --- local ZoneReject1 = ZONE:New( "RejectZone1" ) --- local ZoneReject2 = ZONE:New( "RejectZone2" ) --- --- -- Build a detect object. --- local Detection = DETECTION_BASE:New( SetGroup ) --- --- -- This will reject detected units by Detection when the unit is within ZoneReject1 OR ZoneReject2. --- Detection:SetRejectZones( { ZoneReject1, ZoneReject2 } ) --- --- -- Start the Detection. --- Detection:Start() --- --- ## 1.6) DETECTION_BASE is a Finite State Machine --- --- Various Events and State Transitions can be tailored using DETECTION_BASE. --- --- ### 1.6.1) DETECTION_BASE States --- --- * **Detecting**: The detection is running. --- * **Stopped**: The detection is stopped. --- --- ### 1.6.2) DETECTION_BASE Events --- --- * **Start**: Start the detection process. --- * **Detect**: Detect new units. --- * **Detected**: New units have been detected. --- * **Stop**: Stop the detection process. --- --- === --- --- # 2) @{Detection#DETECTION_UNITS} class, extends @{Detection#DETECTION_BASE} --- --- The @{Detection#DETECTION_UNITS} class will detect units within the battle zone. --- It will build a DetectedItems list filled with DetectedItems. Each DetectedItem will contain a field Set, which contains a @{Set#SET_UNIT} containing ONE @{UNIT} object reference. --- Beware that when the amount of units detected is large, the DetectedItems list will be large also. --- --- # 3) @{Detection#DETECTION_TYPES} class, extends @{Detection#DETECTION_BASE} --- --- The @{Detection#DETECTION_TYPES} class will detect units within the battle zone. --- It will build a DetectedItems[] list filled with DetectedItems, grouped by the type of units detected. --- Each DetectedItem will contain a field Set, which contains a @{Set#SET_UNIT} containing ONE @{UNIT} object reference. --- Beware that when the amount of different types detected is large, the DetectedItems[] list will be large also. --- --- # 4) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} --- --- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), --- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. --- The class is group the detected units within zones given a DetectedZoneRange parameter. --- A set with multiple detected zones will be created as there are groups of units detected. --- --- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones --- --- The methods to manage the DetectedItems[].Set(s) are implemented in @{Detection#DECTECTION_BASE} and --- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Detection#DETECTION_AREAS}. --- --- Retrieve the DetectedItems[].Set with the method @{Detection#DETECTION_BASE.GetDetectedSet}(). A @{Set#SET_UNIT} object will be returned. --- --- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Detection#DETECTION_BASE.GetDetectionZones}(). --- To understand the amount of zones created, use the method @{Detection#DETECTION_BASE.GetDetectionZoneCount}(). --- If you want to obtain a specific zone from the DetectedZones, use the method @{Detection#DETECTION_BASE.GetDetectionZone}() with a given index. --- --- ## 4.4) Flare or Smoke detected units --- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. --- --- ## 4.5) Flare or Smoke or Bound detected zones --- --- Use the methods: --- --- * @{Detection#DETECTION_AREAS.FlareDetectedZones}() to flare in a color --- * @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to smoke in a color --- * @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to bound with a tire with a white flag --- --- the detected zones when a new detection has taken place. --- --- === --- --- ### Contributions: --- --- * Mechanist : Early concept of DETECTION_AREAS. --- --- ### Authors: --- --- * FlightControl : Analysis, Design, Programming, Testing --- --- @module Detection - - -do -- DETECTION_BASE - - --- DETECTION_BASE class - -- @type DETECTION_BASE - -- @field Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. - -- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. - -- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. - -- @field #number DetectionRun - -- @extends Core.Fsm#FSM - DETECTION_BASE = { - ClassName = "DETECTION_BASE", - DetectionSetGroup = nil, - DetectionRange = nil, - DetectedObjects = {}, - DetectionRun = 0, - DetectedObjectsIdentified = {}, - DetectedItems = {}, - } - - --- @type DETECTION_BASE.DetectedObjects - -- @list <#DETECTION_BASE.DetectedObject> - - --- @type DETECTION_BASE.DetectedObject - -- @field #string Name - -- @field #boolean Visible - -- @field #string Type - -- @field #number Distance - -- @field #boolean Identified - - --- @type DETECTION_BASE.DetectedItems - -- @list <#DETECTION_BASE.DetectedItem> - - --- @type DETECTION_BASE.DetectedItem - -- @field Core.Set#SET_UNIT Set - -- @field Core.Set#SET_UNIT Set -- The Set of Units in the detected area. - -- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. - -- @field #boolean Changed Documents if the detected area has changes. - -- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). - -- @field #number ItemID -- The identifier of the detected area. - -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. - -- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. - - - --- DETECTION constructor. - -- @param #DETECTION_BASE self - -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @return #DETECTION_BASE self - function DETECTION_BASE:New( DetectionSetGroup ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM:New() ) -- #DETECTION_BASE - - self.DetectedItemCount = 0 - self.DetectedItemMax = 0 - self.DetectedItems = {} - - self.DetectionSetGroup = DetectionSetGroup - - self.DetectionInterval = 30 - - self:InitDetectVisual( true ) - self:InitDetectOptical( false ) - self:InitDetectRadar( false ) - self:InitDetectRWR( false ) - self:InitDetectIRST( false ) - self:InitDetectDLINK( false ) - - -- Create FSM transitions. - - self:SetStartState( "Stopped" ) - self.CountryID = DetectionSetGroup:GetFirst():GetCountry() - - self:AddTransition( "Stopped", "Start", "Detecting") - - --- OnLeave Transition Handler for State Stopped. - -- @function [parent=#DETECTION_BASE] OnLeaveStopped - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Stopped. - -- @function [parent=#DETECTION_BASE] OnEnterStopped - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Start. - -- @function [parent=#DETECTION_BASE] OnBeforeStart - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Start. - -- @function [parent=#DETECTION_BASE] OnAfterStart - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Start. - -- @function [parent=#DETECTION_BASE] Start - -- @param #DETECTION_BASE self - - --- Asynchronous Event Trigger for Event Start. - -- @function [parent=#DETECTION_BASE] __Start - -- @param #DETECTION_BASE self - -- @param #number Delay The delay in seconds. - - --- OnLeave Transition Handler for State Detecting. - -- @function [parent=#DETECTION_BASE] OnLeaveDetecting - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Detecting. - -- @function [parent=#DETECTION_BASE] OnEnterDetecting - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - self:AddTransition( "Detecting", "Detect", "Detecting" ) - self:AddTransition( "Detecting", "DetectionGroup", "Detecting" ) - - --- OnBefore Transition Handler for Event Detect. - -- @function [parent=#DETECTION_BASE] OnBeforeDetect - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Detect. - -- @function [parent=#DETECTION_BASE] OnAfterDetect - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Detect. - -- @function [parent=#DETECTION_BASE] Detect - -- @param #DETECTION_BASE self - - --- Asynchronous Event Trigger for Event Detect. - -- @function [parent=#DETECTION_BASE] __Detect - -- @param #DETECTION_BASE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Detecting", "Detected", "Detecting" ) - - --- OnBefore Transition Handler for Event Detected. - -- @function [parent=#DETECTION_BASE] OnBeforeDetected - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Detected. - -- @function [parent=#DETECTION_BASE] OnAfterDetected - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Detected. - -- @function [parent=#DETECTION_BASE] Detected - -- @param #DETECTION_BASE self - - --- Asynchronous Event Trigger for Event Detected. - -- @function [parent=#DETECTION_BASE] __Detected - -- @param #DETECTION_BASE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "*", "Stop", "Stopped" ) - - --- OnBefore Transition Handler for Event Stop. - -- @function [parent=#DETECTION_BASE] OnBeforeStop - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Stop. - -- @function [parent=#DETECTION_BASE] OnAfterStop - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Stop. - -- @function [parent=#DETECTION_BASE] Stop - -- @param #DETECTION_BASE self - - --- Asynchronous Event Trigger for Event Stop. - -- @function [parent=#DETECTION_BASE] __Stop - -- @param #DETECTION_BASE self - -- @param #number Delay The delay in seconds. - - --- OnLeave Transition Handler for State Stopped. - -- @function [parent=#DETECTION_BASE] OnLeaveStopped - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Stopped. - -- @function [parent=#DETECTION_BASE] OnEnterStopped - -- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - return self - end - - do -- State Transition Handling - - --- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - function DETECTION_BASE:onafterStart(From,Event,To) - self:__Detect(0.1) - end - - --- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - function DETECTION_BASE:onafterDetect(From,Event,To) - self:E( {From,Event,To}) - - local DetectDelay = 0.1 - self.DetectionCount = 0 - self.DetectionRun = 0 - self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table - - self.DetectionSetGroup:Flush() - - for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - self:E( {DetectionGroupData}) - self:__DetectionGroup( DetectDelay, DetectionGroupData ) -- Process each detection asynchronously. - self.DetectionCount = self.DetectionCount + 1 - DetectDelay = DetectDelay + 0.1 - end - end - - --- @param #DETECTION_BASE self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @param Wrapper.Group#GROUP DetectionGroup The Group detecting. - function DETECTION_BASE:onafterDetectionGroup( From, Event, To, DetectionGroup ) - self:E( {From,Event,To}) - - self.DetectionRun = self.DetectionRun + 1 - - local HasDetectedObjects = false - - if DetectionGroup:IsAlive() then - - self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) - - local DetectionGroupName = DetectionGroup:GetName() - - local DetectedUnits = {} - - local DetectedTargets = DetectionGroup:GetDetectedTargets( - self.DetectVisual, - self.DetectOptical, - self.DetectRadar, - self.DetectIRST, - self.DetectRWR, - self.DetectDLINK - ) - - self:T( DetectedTargets ) - - for DetectionObjectID, Detection in pairs( DetectedTargets ) do - local DetectedObject = Detection.object -- Dcs.DCSWrapper.Object#Object - self:T2( DetectedObject ) - - if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then - - local DetectionAccepted = true - - local DetectedObjectName = DetectedObject:getName() - - local DetectedObjectVec3 = DetectedObject:getPoint() - local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z } - local DetectionGroupVec3 = DetectionGroup:GetVec3() - local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z } - - local Distance = ( ( DetectedObjectVec3.x - DetectionGroupVec3.x )^2 + - ( DetectedObjectVec3.y - DetectionGroupVec3.y )^2 + - ( DetectedObjectVec3.z - DetectionGroupVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T( { "Detected Target", DetectionGroupName, DetectedObjectName, Distance } ) - - -- Calculate Acceptance - - if self.AcceptRange and Distance > self.AcceptRange then - DetectionAccepted = false - end - - if self.AcceptZones then - for AcceptZoneID, AcceptZone in pairs( self.AcceptZones ) do - local AcceptZone = AcceptZone -- Core.Zone#ZONE_BASE - if AcceptZone:IsPointVec2InZone( DetectedObjectVec2 ) == false then - DetectionAccepted = false - end - end - end - - if self.RejectZones then - for RejectZoneID, RejectZone in pairs( self.RejectZones ) do - local RejectZone = RejectZone -- Core.Zone#ZONE_BASE - if RejectZone:IsPointVec2InZone( DetectedObjectVec2 ) == true then - DetectionAccepted = false - end - end - end - - -- Calculate additional probabilities - - if not self.DetectedObjects[DetectedObjectName] and Detection.visible and self.DistanceProbability then - local DistanceFactor = Distance / 4 - local DistanceProbabilityReversed = ( 1 - self.DistanceProbability ) * DistanceFactor - local DistanceProbability = 1 - DistanceProbabilityReversed - DistanceProbability = DistanceProbability * 30 / 300 - local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, DistanceProbability } ) - if Probability > DistanceProbability then - DetectionAccepted = false - end - end - - if not self.DetectedObjects[DetectedObjectName] and Detection.visible and self.AlphaAngleProbability then - local NormalVec2 = { x = DetectedObjectVec2.x - DetectionGroupVec2.x, y = DetectedObjectVec2.y - DetectionGroupVec2.y } - local AlphaAngle = math.atan2( NormalVec2.y, NormalVec2.x ) - local Sinus = math.sin( AlphaAngle ) - local AlphaAngleProbabilityReversed = ( 1 - self.AlphaAngleProbability ) * ( 1 - Sinus ) - local AlphaAngleProbability = 1 - AlphaAngleProbabilityReversed - - AlphaAngleProbability = AlphaAngleProbability * 30 / 300 - - local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, AlphaAngleProbability } ) - if Probability > AlphaAngleProbability then - DetectionAccepted = false - end - - end - - if not self.DetectedObjects[DetectedObjectName] and Detection.visible and self.ZoneProbability then - - for ZoneDataID, ZoneData in pairs( self.ZoneProbability ) do - self:E({ZoneData}) - local ZoneObject = ZoneData[1] -- Core.Zone#ZONE_BASE - local ZoneProbability = ZoneData[2] -- #number - ZoneProbability = ZoneProbability * 30 / 300 - - if ZoneObject:IsPointVec2InZone( DetectedObjectVec2 ) == true then - local Probability = math.random() -- Selects a number between 0 and 1 - self:T( { Probability, ZoneProbability } ) - if Probability > ZoneProbability then - DetectionAccepted = false - break - end - end - end - end - - if DetectionAccepted then - - HasDetectedObjects = true - - if not self.DetectedObjects[DetectedObjectName] then - self.DetectedObjects[DetectedObjectName] = {} - end - self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName - self.DetectedObjects[DetectedObjectName].Visible = Detection.visible - self.DetectedObjects[DetectedObjectName].Type = Detection.type - self.DetectedObjects[DetectedObjectName].Distance = Distance - - local DetectedUnit = UNIT:FindByName( DetectedObjectName ) - - DetectedUnits[DetectedObjectName] = DetectedUnit - else - -- if beyond the DetectionRange then nullify... - if self.DetectedObjects[DetectedObjectName] then - self.DetectedObjects[DetectedObjectName] = nil - end - end - end - - self:T2( self.DetectedObjects ) - end - - if HasDetectedObjects then - self:__Detected( 0.1, DetectedUnits ) - end - - end - - if self.DetectionCount > 0 and self.DetectionRun == self.DetectionCount then - self:__Detect( self.DetectionInterval ) - - self:T( "--> Create Detection Sets" ) - self:CreateDetectionSets() - end - - end - - - end - - do -- Initialization methods - - --- Detect Visual. - -- @param #DETECTION_BASE self - -- @param #boolean DetectVisual - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectVisual( DetectVisual ) - - self.DetectVisual = DetectVisual - end - - --- Detect Optical. - -- @param #DETECTION_BASE self - -- @param #boolean DetectOptical - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectOptical( DetectOptical ) - self:F2() - - self.DetectOptical = DetectOptical - end - - --- Detect Radar. - -- @param #DETECTION_BASE self - -- @param #boolean DetectRadar - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectRadar( DetectRadar ) - self:F2() - - self.DetectRadar = DetectRadar - end - - --- Detect IRST. - -- @param #DETECTION_BASE self - -- @param #boolean DetectIRST - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectIRST( DetectIRST ) - self:F2() - - self.DetectIRST = DetectIRST - end - - --- Detect RWR. - -- @param #DETECTION_BASE self - -- @param #boolean DetectRWR - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectRWR( DetectRWR ) - self:F2() - - self.DetectRWR = DetectRWR - end - - --- Detect DLINK. - -- @param #DETECTION_BASE self - -- @param #boolean DetectDLINK - -- @return #DETECTION_BASE self - function DETECTION_BASE:InitDetectDLINK( DetectDLINK ) - self:F2() - - self.DetectDLINK = DetectDLINK - end - - end - - do - - --- Set the detection interval time in seconds. - -- @param #DETECTION_BASE self - -- @param #number DetectionInterval Interval in seconds. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetDetectionInterval( DetectionInterval ) - self:F2() - - self.DetectionInterval = DetectionInterval - - return self - end - - end - - do -- Accept / Reject detected units - - --- Accept detections if within a range in meters. - -- @param #DETECTION_BASE self - -- @param #number AcceptRange Accept a detection if the unit is within the AcceptRange in meters. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetAcceptRange( AcceptRange ) - self:F2() - - self.AcceptRange = AcceptRange - - return self - end - - --- Accept detections if within the specified zone(s). - -- @param #DETECTION_BASE self - -- @param AcceptZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetAcceptZones( AcceptZones ) - self:F2() - - if type( AcceptZones ) == "table" then - self.AcceptZones = AcceptZones - else - self.AcceptZones = { AcceptZones } - end - - return self - end - - --- Reject detections if within the specified zone(s). - -- @param #DETECTION_BASE self - -- @param RejectZones Can be a list or ZONE_BASE objects, or a single ZONE_BASE object. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetRejectZones( RejectZones ) - self:F2() - - if type( RejectZones ) == "table" then - self.RejectZones = RejectZones - else - self.RejectZones = { RejectZones } - end - - return self - end - - end - - do -- Probability methods - - --- Upon a **visual** detection, the further away a detected object is, the less likely it is to be detected properly. - -- Also, the speed of accurate detection plays a role. - -- A distance probability factor between 0 and 1 can be given, that will model a linear extrapolated probability over 10 km distance. - -- For example, if a probability factor of 0.6 (60%) is given, the extrapolated probabilities over 15 kilometers would like like: - -- 1 km: 96%, 2 km: 92%, 3 km: 88%, 4 km: 84%, 5 km: 80%, 6 km: 76%, 7 km: 72%, 8 km: 68%, 9 km: 64%, 10 km: 60%, 11 km: 56%, 12 km: 52%, 13 km: 48%, 14 km: 44%, 15 km: 40%. - -- @param #DETECTION_BASE self - -- @param DistanceProbability The probability factor. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetDistanceProbability( DistanceProbability ) - self:F2() - - self.DistanceProbability = DistanceProbability - - return self - end - - - --- Upon a **visual** detection, the higher the unit is during the detecting process, the more likely the detected unit is to be detected properly. - -- A detection at a 90% alpha angle is the most optimal, a detection at 10% is less and a detection at 0% is less likely to be correct. - -- - -- A probability factor between 0 and 1 can be given, that will model a progressive extrapolated probability if the target would be detected at a 0° angle. - -- - -- For example, if a alpha angle probability factor of 0.7 is given, the extrapolated probabilities of the different angles would look like: - -- 0°: 70%, 10°: 75,21%, 20°: 80,26%, 30°: 85%, 40°: 89,28%, 50°: 92,98%, 60°: 95,98%, 70°: 98,19%, 80°: 99,54%, 90°: 100% - -- @param #DETECTION_BASE self - -- @param AlphaAngleProbability The probability factor. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetAlphaAngleProbability( AlphaAngleProbability ) - self:F2() - - self.AlphaAngleProbability = AlphaAngleProbability - - return self - end - - --- Upon a **visual** detection, the more a detected unit is within a cloudy zone, the less likely the detected unit is to be detected successfully. - -- The Cloudy Zones work with the ZONE_BASE derived classes. The mission designer can define within the mission - -- zones that reflect cloudy areas where detected units may not be so easily visually detected. - -- @param #DETECTION_BASE self - -- @param ZoneArray Aray of a The ZONE_BASE object and a ZoneProbability pair.. - -- @return #DETECTION_BASE self - function DETECTION_BASE:SetZoneProbability( ZoneArray ) - self:F2() - - self.ZoneProbability = ZoneArray - - return self - end - - - end - - do -- Change processing - - --- Accepts changes from the detected item. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #DETECTION_BASE self - function DETECTION_BASE:AcceptChanges( DetectedItem ) - - DetectedItem.Changed = false - DetectedItem.Changes = {} - - return self - end - - --- Add a change to the detected zone. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @param #string ChangeCode - -- @return #DETECTION_BASE self - function DETECTION_BASE:AddChangeItem( DetectedItem, ChangeCode, ItemUnitType ) - - DetectedItem.Changed = true - local ItemID = DetectedItem.ItemID - - DetectedItem.Changes = DetectedItem.Changes or {} - DetectedItem.Changes[ChangeCode] = DetectedItem.Changes[ChangeCode] or {} - DetectedItem.Changes[ChangeCode].ItemID = ItemID - DetectedItem.Changes[ChangeCode].ItemUnitType = ItemUnitType - - self:T( { "Change on Detection Item:", DetectedItem.ItemID, ChangeCode, ItemUnitType } ) - - return self - end - - - --- Add a change to the detected zone. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @param #string ChangeCode - -- @param #string ChangeUnitType - -- @return #DETECTION_BASE self - function DETECTION_BASE:AddChangeUnit( DetectedItem, ChangeCode, ChangeUnitType ) - - DetectedItem.Changed = true - local ItemID = DetectedItem.ItemID - - DetectedItem.Changes = DetectedItem.Changes or {} - DetectedItem.Changes[ChangeCode] = DetectedItem.Changes[ChangeCode] or {} - DetectedItem.Changes[ChangeCode][ChangeUnitType] = DetectedItem.Changes[ChangeCode][ChangeUnitType] or 0 - DetectedItem.Changes[ChangeCode][ChangeUnitType] = DetectedItem.Changes[ChangeCode][ChangeUnitType] + 1 - DetectedItem.Changes[ChangeCode].ItemID = ItemID - - self:T( { "Change on Detection Item:", DetectedItem.ItemID, ChangeCode, ChangeUnitType } ) - - return self - end - - - end - - do -- Threat - - --- Returns if there are friendlies nearby the FAC units ... - -- @param #DETECTION_BASE self - -- @return #boolean trhe if there are friendlies nearby - function DETECTION_BASE:IsFriendliesNearBy( DetectedItem ) - - self:T3( DetectedItem.FriendliesNearBy ) - return DetectedItem.FriendliesNearBy or false - end - - --- Background worker function to determine if there are friendlies nearby ... - -- @param #DETECTION_BASE self - function DETECTION_BASE:ReportFriendliesNearBy( ReportGroupData ) - self:F2() - - local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = ReportGroupData.DetectedItem.Set - local DetectedUnit = DetectedSet:GetFirst() - - DetectedItem.FriendliesNearBy = false - - local SphereSearch = { - id = world.VolumeType.SPHERE, - params = { - point = DetectedUnit:GetVec3(), - radius = 6000, - } - - } - - --- @param Dcs.DCSWrapper.Unit#Unit FoundDCSUnit - -- @param Wrapper.Group#GROUP ReportGroup - -- @param Set#SET_GROUP ReportSetGroup - local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - - local DetectedItem = ReportGroupData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = ReportGroupData.DetectedItem.Set - local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - local ReportSetGroup = ReportGroupData.ReportSetGroup - - local EnemyCoalition = DetectedUnit:GetCoalition() - - local FoundUnitCoalition = FoundDCSUnit:getCoalition() - local FoundUnitName = FoundDCSUnit:getName() - local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() - local EnemyUnitName = DetectedUnit:GetName() - local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil - - self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - - if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then - DetectedItem.FriendliesNearBy = true - return false - end - - return true - end - - world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) - - end - - end - - --- Determines if a detected object has already been identified during detection processing. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedObject DetectedObject - -- @return #boolean true if already identified. - function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) - self:F3( DetectedObject.Name ) - - local DetectedObjectName = DetectedObject.Name - local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true - self:T3( DetectedObjectIdentified ) - return DetectedObjectIdentified - end - - --- Identifies a detected object during detection processing. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedObject DetectedObject - function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) - self:F( { "Identified:", DetectedObject.Name } ) - - local DetectedObjectName = DetectedObject.Name - self.DetectedObjectsIdentified[DetectedObjectName] = true - end - - --- UnIdentify a detected object during detection processing. - -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedObject DetectedObject - function DETECTION_BASE:UnIdentifyDetectedObject( DetectedObject ) - - local DetectedObjectName = DetectedObject.Name - self.DetectedObjectsIdentified[DetectedObjectName] = false - end - - --- UnIdentify all detected objects during detection processing. - -- @param #DETECTION_BASE self - function DETECTION_BASE:UnIdentifyAllDetectedObjects() - - self.DetectedObjectsIdentified = {} -- Table will be garbage collected. - end - - --- Gets a detected object with a given name. - -- @param #DETECTION_BASE self - -- @param #string ObjectName - -- @return #DETECTION_BASE.DetectedObject - function DETECTION_BASE:GetDetectedObject( ObjectName ) - self:F( ObjectName ) - - if ObjectName then - local DetectedObject = self.DetectedObjects[ObjectName] - - -- Only return detected objects that are alive! - local DetectedUnit = UNIT:FindByName( ObjectName ) - if DetectedUnit and DetectedUnit:IsAlive() then - if self:IsDetectedObjectIdentified( DetectedObject ) == false then - return DetectedObject - end - end - end - - return nil - end - - - --- Adds a new DetectedItem to the DetectedItems list. - -- The DetectedItem is a table and contains a SET_UNIT in the field Set. - -- @param #DETECTION_BASE self - -- @param #string DetectedItemIndex The index of the DetectedItem. - -- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added. - -- @return #DETECTION_BASE.DetectedItem - function DETECTION_BASE:AddDetectedItem( DetectedItemIndex, Set ) - - local DetectedItem = {} - self.DetectedItemCount = self.DetectedItemCount + 1 - self.DetectedItemMax = self.DetectedItemMax + 1 - - if DetectedItemIndex then - self.DetectedItems[DetectedItemIndex] = DetectedItem - else - self.DetectedItems[self.DetectedItemCount] = DetectedItem - end - - DetectedItem.Set = Set or SET_UNIT:New() - DetectedItem.ItemID = self.DetectedItemMax - DetectedItem.Removed = false - - return DetectedItem - end - - --- Adds a new DetectedItem to the DetectedItems list. - -- The DetectedItem is a table and contains a SET_UNIT in the field Set. - -- @param #DETECTION_BASE self - -- @param #string DetectedItemIndex The index of the DetectedItem. - -- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added. - -- @param Core.Zone#ZONE_UNIT Zone (optional) The Zone to be added where the Units are located. - -- @return #DETECTION_BASE.DetectedItem - function DETECTION_BASE:AddDetectedItemZone( DetectedItemIndex, Set, Zone ) - - local DetectedItem = self:AddDetectedItem( DetectedItemIndex, Set ) - - DetectedItem.Zone = Zone - - return DetectedItem - end - - --- Removes an existing DetectedItem from the DetectedItems list. - -- The DetectedItem is a table and contains a SET_UNIT in the field Set. - -- @param #DETECTION_BASE self - -- @param #number DetectedItemIndex The index or position in the DetectedItems list where the item needs to be removed. - function DETECTION_BASE:RemoveDetectedItem( DetectedItemIndex ) - - self.DetectedItemCount = self.DetectedItemCount - 1 - self.DetectedItems[DetectedItemIndex] = nil - end - - - --- Get the detected @{Set#SET_BASE}s. - -- @param #DETECTION_BASE self - -- @return #DETECTION_BASE.DetectedItems - function DETECTION_BASE:GetDetectedItems() - - return self.DetectedItems - end - - --- Get the amount of SETs with detected objects. - -- @param #DETECTION_BASE self - -- @return #number Count - function DETECTION_BASE:GetDetectedItemsCount() - - local DetectedCount = self.DetectedItemCount - return DetectedCount - end - - --- Get a detected item using a given numeric index. - -- @param #DETECTION_BASE self - -- @param #number Index - -- @return DETECTION_BASE.DetectedItem - function DETECTION_BASE:GetDetectedItem( Index ) - - local DetectedItem = self.DetectedItems[Index] - if DetectedItem then - return DetectedItem - end - - return nil - end - - --- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. - -- @param #DETECTION_BASE self - -- @param #number Index - -- @return Core.Set#SET_UNIT DetectedSet - function DETECTION_BASE:GetDetectedSet( Index ) - - local DetectedItem = self:GetDetectedItem( Index ) - local DetectedSetUnit = DetectedItem.Set - if DetectedSetUnit then - return DetectedSetUnit - end - - return nil - end - - do -- Zones - - --- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. - -- @param #DETECTION_BASE self - -- @param #number Index - -- @return Core.Zone#ZONE_UNIT DetectedZone - function DETECTION_BASE:GetDetectedZone( Index ) - - local DetectedZone = self.DetectedItems[Index].Zone - if DetectedZone then - return DetectedZone - end - - return nil - end - - end - - - --- Report summary of a detected item using a given numeric index. - -- @param #DETECTION_BASE self - -- @param Index - -- @return #string - function DETECTION_BASE:DetectedItemReportSummary( Index ) - self:F( Index ) - return nil - end - - --- Report detailed of a detectedion result. - -- @param #DETECTION_BASE self - -- @return #string - function DETECTION_BASE:DetectedReportDetailed() - self:F() - return nil - end - - --- Get the detection Groups. - -- @param #DETECTION_BASE self - -- @return Wrapper.Group#GROUP - function DETECTION_BASE:GetDetectionSetGroup() - - local DetectionSetGroup = self.DetectionSetGroup - return DetectionSetGroup - end - - --- Make a DetectionSet table. This function will be overridden in the derived clsses. - -- @param #DETECTION_BASE self - -- @return #DETECTION_BASE self - function DETECTION_BASE:CreateDetectionSets() - self:F2() - - self:E( "Error, in DETECTION_BASE class..." ) - - end - - - --- Schedule the DETECTION construction. - -- @param #DETECTION_BASE self - -- @param #number DelayTime The delay in seconds to wait the reporting. - -- @param #number RepeatInterval The repeat interval in seconds for the reporting to happen repeatedly. - -- @return #DETECTION_BASE self - function DETECTION_BASE:Schedule( DelayTime, RepeatInterval ) - self:F2() - - self.ScheduleDelayTime = DelayTime - self.ScheduleRepeatInterval = RepeatInterval - - self.DetectionScheduler = SCHEDULER:New( self, self._DetectionScheduler, { self, "Detection" }, DelayTime, RepeatInterval ) - return self - end - -end - -do -- DETECTION_UNITS - - --- DETECTION_UNITS class - -- @type DETECTION_UNITS - -- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are detected. - -- @extends #DETECTION_BASE - DETECTION_UNITS = { - ClassName = "DETECTION_UNITS", - DetectionRange = nil, - } - - --- DETECTION_UNITS constructor. - -- @param Functional.Detection#DETECTION_UNITS self - -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @return Functional.Detection#DETECTION_UNITS self - function DETECTION_UNITS:New( DetectionSetGroup ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) -- #DETECTION_UNITS - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - self._BoundDetectedZones = false - - return self - end - - --- Make text documenting the changes of the detected zone. - -- @param #DETECTION_UNITS self - -- @param #DETECTION_UNITS.DetectedItem DetectedItem - -- @return #string The Changes text - function DETECTION_UNITS:GetChangeText( DetectedItem ) - self:F( DetectedItem ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - - end - - - --- Create the DetectedItems list from the DetectedObjects table. - -- For each DetectedItem, a one field array is created containing the Unit detected. - -- @param #DETECTION_UNITS self - -- @return #DETECTION_UNITS self - function DETECTION_UNITS:CreateDetectionSets() - self:F2( #self.DetectedObjects ) - - -- Loop the current detected items, and check if each object still exists and is detected. - - for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do - - local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedTypeName = DetectedItem.Type - - for DetectedUnitName, DetectedUnitData in pairs( DetectedItemSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - - local DetectedObject = nil - self:E( DetectedUnit ) - if DetectedUnit:IsAlive() then - --self:E(DetectedUnit:GetName()) - DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) - end - if DetectedObject then - - -- Yes, the DetectedUnit is still detected or exists. Flag as identified. - self:IdentifyDetectedObject( DetectedObject ) - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedItem, "RU", DetectedUnitName ) - DetectedItemSet:Remove( DetectedUnitName ) - end - end - end - - - -- Now we need to loop through the unidentified detected units and add these... These are all new items. - for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - if DetectedObject then - self:T( { "Detected Unit #", DetectedUnitName } ) - - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - - if DetectedUnit then - local DetectedTypeName = DetectedUnit:GetTypeName() - local DetectedItem = self:GetDetectedItem( DetectedUnitName ) - if not DetectedItem then - self:T( "Added new DetectedItem" ) - DetectedItem = self:AddDetectedItem( DetectedUnitName ) - DetectedItem.Type = DetectedUnit:GetTypeName() - DetectedItem.Name = DetectedObjectData.Name - DetectedItem.Visible = DetectedObjectData.Visible - DetectedItem.Distance = DetectedObjectData.Distance - end - - DetectedItem.Set:AddUnit( DetectedUnit ) - self:AddChangeUnit( DetectedItem, "AU", DetectedTypeName ) - end - end - end - - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set - - self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - --self:NearestFAC( DetectedItem ) - end - - end - - --- Report summary of a DetectedItem using a given numeric index. - -- @param #DETECTION_UNITS self - -- @param Index - -- @return #string - function DETECTION_UNITS:DetectedItemReportSummary( Index ) - self:F( Index ) - - local DetectedItem = self:GetDetectedItem( Index ) - local DetectedSet = self:GetDetectedSet( Index ) - - self:T( DetectedSet ) - if DetectedSet then - local ReportSummary = "" - local UnitDistanceText = "" - local UnitCategoryText = "" - - local DetectedItemUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT - - if DetectedItemUnit then - self:T(DetectedItemUnit) - - local UnitCategoryName = DetectedItemUnit:GetCategoryName() - local UnitCategoryType = DetectedItemUnit:GetTypeName() - - if DetectedItem.Type then - UnitCategoryText = UnitCategoryName .. " (" .. UnitCategoryType .. ") at " - else - UnitCategoryText = "Unknown target at " - end - - if DetectedItem.Visible == false then - UnitDistanceText = string.format( "%.2f", DetectedItem.Distance ) .. " estimated km" - else - UnitDistanceText = string.format( "%.2f", DetectedItem.Distance ) .. " km, visual contact" - end - - ReportSummary = string.format( - "%s%s", - UnitCategoryText, - UnitDistanceText - ) - end - - self:T( ReportSummary ) - - return ReportSummary - end - end - - --- Report detailed of a detection result. - -- @param #DETECTION_UNITS self - -- @return #string - function DETECTION_UNITS:DetectedReportDetailed() - self:F() - - local Report = REPORT:New( "Detected units:" ) - for DetectedItemID, DetectedItem in ipairs( self.DetectedItems ) do - local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem - local ReportSummary = self:DetectedItemReportSummary( DetectedItemID ) - Report:Add( ReportSummary ) - end - - local ReportText = Report:Text() - - return ReportText - end - -end - -do -- DETECTION_TYPES - - --- DETECTION_TYPES class - -- @type DETECTION_TYPES - -- @extends #DETECTION_BASE - DETECTION_TYPES = { - ClassName = "DETECTION_TYPES", - DetectionRange = nil, - } - - --- DETECTION_TYPES constructor. - -- @param Functional.Detection#DETECTION_TYPES self - -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Recce role. - -- @return Functional.Detection#DETECTION_TYPES self - function DETECTION_TYPES:New( DetectionSetGroup ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) -- #DETECTION_TYPES - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - self._BoundDetectedZones = false - - return self - end - - --- Make text documenting the changes of the detected zone. - -- @param #DETECTION_TYPES self - -- @param #DETECTION_TYPES.DetectedItem DetectedItem - -- @return #string The Changes text - function DETECTION_TYPES:GetChangeText( DetectedItem ) - self:F( DetectedItem ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = " New target(s) detected: " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = " Invisible or destroyed target(s): " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - - end - - - --- Create the DetectedItems list from the DetectedObjects table. - -- For each DetectedItem, a one field array is created containing the Unit detected. - -- @param #DETECTION_TYPES self - -- @return #DETECTION_TYPES self - function DETECTION_TYPES:CreateDetectionSets() - self:F2( #self.DetectedObjects ) - - -- Loop the current detected items, and check if each object still exists and is detected. - - for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do - - local DetectedItemSet = DetectedItem:GetSet() -- Core.Set#SET_UNIT - local DetectedTypeName = DetectedItem.Type - - for DetectedUnitName, DetectedUnitData in pairs( DetectedItemSet ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - - local DetectedObject = nil - if DetectedUnit:IsAlive() then - --self:E(DetectedUnit:GetName()) - DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) - end - if DetectedObject then - - -- Yes, the DetectedUnit is still detected or exists. Flag as identified. - self:IdentifyDetectedObject( DetectedObject ) - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedItem, "RU", DetectedUnitName ) - DetectedItemSet:Remove( DetectedUnitName ) - end - end - end - - - -- Now we need to loop through the unidentified detected units and add these... These are all new items. - for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - if DetectedObject then - self:T( { "Detected Unit #", DetectedUnitName } ) - - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - - if DetectedUnit then - local DetectedTypeName = DetectedUnit:GetTypeName() - local DetectedItem = self:GetDetectedItem( DetectedTypeName ) - if not DetectedItem then - DetectedItem = self:AddDetectedItem( DetectedTypeName ) - DetectedItem.Type = DetectedUnit:GetTypeName() - end - - DetectedItem.Set:AddUnit( DetectedUnit ) - self:AddChangeUnit( DetectedItem, "AU", DetectedTypeName ) - end - end - end - - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set - - self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - --self:NearestFAC( DetectedItem ) - end - - end - - --- Report summary of a DetectedItem using a given numeric index. - -- @param #DETECTION_TYPES self - -- @param Index - -- @return #string - function DETECTION_TYPES:DetectedItemReportSummary( DetectedTypeName ) - self:F( DetectedTypeName ) - - local DetectedItem = self:GetDetectedItem( DetectedTypeName ) - local DetectedSet = self:GetDetectedSet( DetectedTypeName ) - - self:T( DetectedItem ) - if DetectedItem then - - local ThreatLevelA2G = DetectedSet:CalculateThreatLevelA2G() - - local ReportSummary = string.format( - "Type #%s - Threat Level [%s] (%2d)", - DetectedItem.Type, - string.rep( "■", ThreatLevelA2G ), - ThreatLevelA2G - ) - self:T( ReportSummary ) - - return ReportSummary - end - end - - --- Report detailed of a detection result. - -- @param #DETECTION_TYPES self - -- @return #string - function DETECTION_TYPES:DetectedReportDetailed() - self:F() - - local Report = REPORT:New( "Detected types:" ) - for DetectedItemTypeName, DetectedItem in pairs( self.DetectedItems ) do - local DetectedItem = DetectedItem -- #DETECTION_BASE.DetectedItem - local ReportSummary = self:DetectedItemReportSummary( DetectedItemTypeName ) - Report:Add( ReportSummary ) - end - - local ReportText = Report:Text() - - return ReportText - end - -end - - -do -- DETECTION_AREAS - - --- DETECTION_AREAS class - -- @type DETECTION_AREAS - -- @field Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. - -- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. - -- @extends #DETECTION_BASE - DETECTION_AREAS = { - ClassName = "DETECTION_AREAS", - DetectionZoneRange = nil, - } - - - --- DETECTION_AREAS constructor. - -- @param #DETECTION_AREAS self - -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. - -- @param Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. - -- @return #DETECTION_AREAS - function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) - - self.DetectionZoneRange = DetectionZoneRange - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - self._BoundDetectedZones = false - - return self - end - - --- Report summary of a detected item using a given numeric index. - -- @param #DETECTION_AREAS self - -- @param Index - -- @return #string - function DETECTION_AREAS:DetectedItemReportSummary( Index ) - self:F( Index ) - - local DetectedItem = self:GetDetectedItem( Index ) - if DetectedItem then - local DetectedSet = self:GetDetectedSet( Index ) - local ThreatLevelA2G = self:GetTreatLevelA2G( DetectedItem ) - local ReportSummaryItem - - local DetectedZone = self:GetDetectedZone( Index ) - local DetectedItemPointVec3 = DetectedZone:GetPointVec3() - local DetectedItemPointLL = DetectedItemPointVec3:ToStringLL( 3, true ) - local ReportSummary = string.format( - "%s - Threat Level [%s] (%2d)", - DetectedItemPointLL, - string.rep( "■", ThreatLevelA2G ), - ThreatLevelA2G - ) - - return ReportSummary - end - - return nil - end - - - --- Returns if there are friendlies nearby the FAC units ... - -- @param #DETECTION_AREAS self - -- @return #boolean trhe if there are friendlies nearby - function DETECTION_AREAS:IsFriendliesNearBy( DetectedItem ) - - self:T3( DetectedItem.FriendliesNearBy ) - return DetectedItem.FriendliesNearBy or false - end - - --- Calculate the maxium A2G threat level of the DetectedItem. - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedItem ) - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( DetectedItem.Set:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - DetectedItem.MaxThreatLevelA2G = MaxThreatLevelA2G - - end - - --- Find the nearest FAC of the DetectedItem. - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return Wrapper.Unit#UNIT The nearest FAC unit - function DETECTION_AREAS:NearestFAC( DetectedItem ) - - local NearestFAC = nil - local MinDistance = 1000000000 -- Units are not further than 1000000 km away from an area :-) - - for FACGroupName, FACGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do - local FACUnit = FACUnitData -- Wrapper.Unit#UNIT - if FACUnit:IsActive() then - local Vec3 = FACUnit:GetVec3() - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetVec3() ) ) - if Distance < MinDistance then - MinDistance = Distance - NearestFAC = FACUnit - end - end - end - end - - DetectedItem.NearestFAC = NearestFAC - - end - - --- Returns the A2G threat level of the units in the DetectedItem - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #number a scale from 0 to 10. - function DETECTION_AREAS:GetTreatLevelA2G( DetectedItem ) - - self:T3( DetectedItem.MaxThreatLevelA2G ) - return DetectedItem.MaxThreatLevelA2G - end - - - - --- Smoke the detected units - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:SmokeDetectedUnits() - self:F2() - - self._SmokeDetectedUnits = true - return self - end - - --- Flare the detected units - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:FlareDetectedUnits() - self:F2() - - self._FlareDetectedUnits = true - return self - end - - --- Smoke the detected zones - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:SmokeDetectedZones() - self:F2() - - self._SmokeDetectedZones = true - return self - end - - --- Flare the detected zones - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:FlareDetectedZones() - self:F2() - - self._FlareDetectedZones = true - return self - end - - --- Bound the detected zones - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:BoundDetectedZones() - self:F2() - - self._BoundDetectedZones = true - return self - end - - --- Make text documenting the changes of the detected zone. - -- @param #DETECTION_AREAS self - -- @param #DETECTION_BASE.DetectedItem DetectedItem - -- @return #string The Changes text - function DETECTION_AREAS:GetChangeText( DetectedItem ) - self:F( DetectedItem ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedItem.Changes ) do - - if ChangeCode == "AA" then - MT[#MT+1] = "Detected new area " .. ChangeData.ItemID .. ". The center target is a " .. ChangeData.ItemUnitType .. "." - end - - if ChangeCode == "RAU" then - MT[#MT+1] = "Changed area " .. ChangeData.ItemID .. ". Removed the center target." - end - - if ChangeCode == "AAU" then - MT[#MT+1] = "Changed area " .. ChangeData.ItemID .. ". The new center target is a " .. ChangeData.ItemUnitType "." - end - - if ChangeCode == "RA" then - MT[#MT+1] = "Removed old area " .. ChangeData.ItemID .. ". No more targets in this area." - end - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Detected for area " .. ChangeData.ItemID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "ItemID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Removed for area " .. ChangeData.ItemID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - - end - - - --- Make a DetectionSet table. This function will be overridden in the derived clsses. - -- @param #DETECTION_AREAS self - -- @return #DETECTION_AREAS self - function DETECTION_AREAS:CreateDetectionSets() - self:F2() - - - self:T( "Checking Detected Items for new Detected Units ..." ) - -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. - -- Regroup when needed, split groups when needed. - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - if DetectedItem then - - self:T( { "Detected Item ID:", DetectedItemID } ) - - - local DetectedSet = DetectedItem.Set - - local AreaExists = false -- This flag will determine of the detected area is still existing. - - -- First test if the center unit is detected in the detection area. - self:T3( { "Zone Center Unit:", DetectedItem.Zone.ZoneUNIT.UnitName } ) - local DetectedZoneObject = self:GetDetectedObject( DetectedItem.Zone.ZoneUNIT.UnitName ) - self:T3( { "Detected Zone Object:", DetectedItem.Zone:GetName(), DetectedZoneObject } ) - - if DetectedZoneObject then - - --self:IdentifyDetectedObject( DetectedZoneObject ) - AreaExists = true - - - - else - -- The center object of the detected area has not been detected. Find an other unit of the set to become the center of the area. - -- First remove the center unit from the set. - DetectedSet:RemoveUnitsByName( DetectedItem.Zone.ZoneUNIT.UnitName ) - - self:AddChangeItem( DetectedItem, 'RAU', "Dummy" ) - - -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. - for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) - - -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. - -- If the DetectedUnit was already identified, DetectedObject will be nil. - if DetectedObject then - self:IdentifyDetectedObject( DetectedObject ) - AreaExists = true - - DetectedItem.Zone:BoundZone( 12, self.CountryID, true) - - -- Assign the Unit as the new center unit of the detected area. - DetectedItem.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) - - self:AddChangeItem( DetectedItem, "AAU", DetectedItem.Zone.ZoneUNIT:GetTypeName() ) - - -- We don't need to add the DetectedObject to the area set, because it is already there ... - break - end - end - end - - -- Now we've determined the center unit of the area, now we can iterate the units in the detected area. - -- Note that the position of the area may have moved due to the center unit repositioning. - -- If no center unit was identified, then the detected area does not exist anymore and should be deleted, as there are no valid units that can be the center unit. - if AreaExists then - - -- ok, we found the center unit of the area, now iterate through the detected area set and see which units are still within the center unit zone ... - -- Those units within the zone are flagged as Identified. - -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. - for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - local DetectedObject = nil - if DetectedUnit:IsAlive() then - --self:E(DetectedUnit:GetName()) - DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) - end - if DetectedObject then - - -- Check if the DetectedUnit is within the DetectedItem.Zone - if DetectedUnit:IsInZone( DetectedItem.Zone ) then - - -- Yes, the DetectedUnit is within the DetectedItem.Zone, no changes, DetectedUnit can be kept within the Set. - self:IdentifyDetectedObject( DetectedObject ) - - else - -- No, the DetectedUnit is not within the DetectedItem.Zone, remove DetectedUnit from the Set. - DetectedSet:Remove( DetectedUnitName ) - self:AddChangeUnit( DetectedItem, "RU", DetectedUnit:GetTypeName() ) - end - - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedItem, "RU", "destroyed target" ) - DetectedSet:Remove( DetectedUnitName ) - - -- The DetectedObject has been identified, because it does not exist ... - -- self:IdentifyDetectedObject( DetectedObject ) - end - end - else - DetectedItem.Zone:BoundZone( 12, self.CountryID, true) - self:RemoveDetectedItem( DetectedItemID ) - self:AddChangeItem( DetectedItem, "RA" ) - end - end - end - - -- We iterated through the existing detection areas and: - -- - We checked which units are still detected in each detection area. Those units were flagged as Identified. - -- - We recentered the detection area to new center units where it was needed. - -- - -- Now we need to loop through the unidentified detected units and see where they belong: - -- - They can be added to a new detection area and become the new center unit. - -- - They can be added to a new detection area. - for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - - if DetectedObject then - - -- We found an unidentified unit outside of any existing detection area. - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - - local AddedToDetectionArea = false - - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - if DetectedItem then - self:T( "Detection Area #" .. DetectedItem.ItemID ) - local DetectedSet = DetectedItem.Set - if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedItem.Zone ) then - self:IdentifyDetectedObject( DetectedObject ) - DetectedSet:AddUnit( DetectedUnit ) - AddedToDetectionArea = true - self:AddChangeUnit( DetectedItem, "AU", DetectedUnit:GetTypeName() ) - end - end - end - - if AddedToDetectionArea == false then - - -- New detection area - local DetectedItem = self:AddDetectedItemZone( nil, - SET_UNIT:New(), - ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - ) - --self:E( DetectedItem.Zone.ZoneUNIT.UnitName ) - DetectedItem.Set:AddUnit( DetectedUnit ) - self:AddChangeItem( DetectedItem, "AA", DetectedUnit:GetTypeName() ) - end - end - end - - -- Now all the tests should have been build, now make some smoke and flares... - -- We also report here the friendlies within the detected areas. - - for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do - - local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - self:ReportFriendliesNearBy( { DetectedItem = DetectedItem, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - self:CalculateThreatLevelA2G( DetectedItem ) -- Calculate A2G threat level - self:NearestFAC( DetectedItem ) - - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedZone.ZoneUNIT:SmokeRed() - end - DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit - function( DetectedUnit ) - if DetectedUnit:IsAlive() then - self:T( "Detected Set #" .. DetectedItem.ItemID .. ":" .. DetectedUnit:GetName() ) - if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then - DetectedUnit:FlareGreen() - end - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedUnit:SmokeGreen() - end - end - end - ) - if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) ) - end - if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then - DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) - end - - if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then - DetectedZone:BoundZone( 12, self.CountryID ) - end - end - - end - -end ---- Single-Player:**No** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**All** -- **AI Balancing will replace in multi player missions --- non-occupied human slots with AI groups, in order to provide an engaging simulation environment, --- even when there are hardly any players in the mission.** --- --- ![Banner Image](..\Presentations\AI_Balancer\Dia1.JPG) --- --- === --- --- # 1) @{AI_Balancer#AI_BALANCER} class, extends @{Fsm#FSM_SET} --- --- The @{AI_Balancer#AI_BALANCER} class monitors and manages as many replacement AI groups as there are --- CLIENTS in a SET_CLIENT collection, which are not occupied by human players. --- In other words, use AI_BALANCER to simulate human behaviour by spawning in replacement AI in multi player missions. --- --- The parent class @{Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM). --- The mission designer can tailor the behaviour of the AI_BALANCER, by defining event and state transition methods. --- An explanation about state and event transition methods can be found in the @{FSM} module documentation. --- --- The mission designer can tailor the AI_BALANCER behaviour, by implementing a state or event handling method for the following: --- --- * **@{#AI_BALANCER.OnAfterSpawned}**( AISet, From, Event, To, AIGroup ): Define to add extra logic when an AI is spawned. --- --- ## 1.1) AI_BALANCER construction --- --- Create a new AI_BALANCER object with the @{#AI_BALANCER.New}() method: --- --- ## 1.2) AI_BALANCER is a FSM --- --- ![Process](..\Presentations\AI_Balancer\Dia13.JPG) --- --- ### 1.2.1) AI_BALANCER States --- --- * **Monitoring** ( Set ): Monitoring the Set if all AI is spawned for the Clients. --- * **Spawning** ( Set, ClientName ): There is a new AI group spawned with ClientName as the name of reference. --- * **Spawned** ( Set, AIGroup ): A new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes. --- * **Destroying** ( Set, AIGroup ): The AI is being destroyed. --- * **Returning** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. Handle this state to customize the return behaviour of the AI, if any. --- --- ### 1.2.2) AI_BALANCER Events --- --- * **Monitor** ( Set ): Every 10 seconds, the Monitor event is triggered to monitor the Set. --- * **Spawn** ( Set, ClientName ): Triggers when there is a new AI group to be spawned with ClientName as the name of reference. --- * **Spawned** ( Set, AIGroup ): Triggers when a new AI has been spawned. You can handle this event to customize the AI behaviour with other AI FSMs or own processes. --- * **Destroy** ( Set, AIGroup ): The AI is being destroyed. --- * **Return** ( Set, AIGroup ): The AI is returning to the airbase specified by the ReturnToAirbase methods. --- --- ## 1.3) AI_BALANCER spawn interval for replacement AI --- --- Use the method @{#AI_BALANCER.InitSpawnInterval}() to set the earliest and latest interval in seconds that is waited until a new replacement AI is spawned. --- --- ## 1.4) AI_BALANCER returns AI to Airbases --- --- By default, When a human player joins a slot that is AI_BALANCED, the AI group will be destroyed by default. --- However, there are 2 additional options that you can use to customize the destroy behaviour. --- When a human player joins a slot, you can configure to let the AI return to: --- --- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the **home** @{Airbase#AIRBASE}. --- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the **nearest friendly** @{Airbase#AIRBASE}. --- --- Note that when AI returns to an airbase, the AI_BALANCER will trigger the **Return** event and the AI will return, --- otherwise the AI_BALANCER will trigger a **Destroy** event, and the AI will be destroyed. --- --- === --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-01-17: There is still a problem with AI being destroyed, but not respawned. Need to check further upon that. --- --- 2017-01-08: AI_BALANCER:**InitSpawnInterval( Earliest, Latest )** added. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- * **SNAFU**: Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. None of the script code has been used however within the new AI_BALANCER moose class. --- --- ### Authors: --- --- * FlightControl: Framework Design & Programming and Documentation. --- --- @module AI_Balancer - ---- AI_BALANCER class --- @type AI_BALANCER --- @field Core.Set#SET_CLIENT SetClient --- @field Functional.Spawn#SPAWN SpawnAI --- @field Wrapper.Group#GROUP Test --- @extends Core.Fsm#FSM_SET -AI_BALANCER = { - ClassName = "AI_BALANCER", - PatrolZones = {}, - AIGroups = {}, - Earliest = 5, -- Earliest a new AI can be spawned is in 5 seconds. - Latest = 60, -- Latest a new AI can be spawned is in 60 seconds. -} - - - ---- Creates a new AI_BALANCER object --- @param #AI_BALANCER self --- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). --- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed. --- @return #AI_BALANCER -function AI_BALANCER:New( SetClient, SpawnAI ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- AI.AI_Balancer#AI_BALANCER - - -- TODO: Define the OnAfterSpawned event - self:SetStartState( "None" ) - self:AddTransition( "*", "Monitor", "Monitoring" ) - self:AddTransition( "*", "Spawn", "Spawning" ) - self:AddTransition( "Spawning", "Spawned", "Spawned" ) - self:AddTransition( "*", "Destroy", "Destroying" ) - self:AddTransition( "*", "Return", "Returning" ) - - self.SetClient = SetClient - self.SetClient:FilterOnce() - self.SpawnAI = SpawnAI - - self.SpawnQueue = {} - - self.ToNearestAirbase = false - self.ToHomeAirbase = false - - self:__Monitor( 1 ) - - return self -end - ---- Sets the earliest to the latest interval in seconds how long AI_BALANCER will wait to spawn a new AI. --- Provide 2 identical seconds if the interval should be a fixed amount of seconds. --- @param #AI_BALANCER self --- @param #number Earliest The earliest a new AI can be spawned in seconds. --- @param #number Latest The latest a new AI can be spawned in seconds. --- @return self -function AI_BALANCER:InitSpawnInterval( Earliest, Latest ) - - self.Earliest = Earliest - self.Latest = Latest - - return self -end - ---- Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- @param #AI_BALANCER self --- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. --- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to. -function AI_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) - - self.ToNearestAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange - self.ReturnAirbaseSet = ReturnAirbaseSet -end - ---- Returns the AI to the home @{Airbase#AIRBASE}. --- @param #AI_BALANCER self --- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. -function AI_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) - - self.ToHomeAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange -end - ---- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param #string ClientName --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterSpawning( SetGroup, From, Event, To, ClientName ) - - -- OK, Spawn a new group from the default SpawnAI object provided. - local AIGroup = self.SpawnAI:Spawn() -- Wrapper.Group#GROUP - if AIGroup then - AIGroup:E( "Spawning new AIGroup" ) - --TODO: need to rework UnitName thing ... - - SetGroup:Add( ClientName, AIGroup ) - self.SpawnQueue[ClientName] = nil - - -- Fire the Spawned event. The first parameter is the AIGroup just Spawned. - -- Mission designers can catch this event to bind further actions to the AIGroup. - self:Spawned( AIGroup ) - end -end - ---- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterDestroying( SetGroup, From, Event, To, ClientName, AIGroup ) - - AIGroup:Destroy() - SetGroup:Flush() - SetGroup:Remove( ClientName ) - SetGroup:Flush() -end - ---- @param #AI_BALANCER self --- @param Core.Set#SET_GROUP SetGroup --- @param Wrapper.Group#GROUP AIGroup -function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup ) - - local AIGroupTemplate = AIGroup:GetTemplate() - if self.ToHomeAirbase == true then - local WayPointCount = #AIGroupTemplate.route.points - local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) - AIGroup:SetCommand( SwitchWayPointCommand ) - AIGroup:MessageToRed( "Returning to home base ...", 30 ) - else - -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) - local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:T( ClosestAirbase.AirbaseName ) - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - end - -end - - ---- @param #AI_BALANCER self -function AI_BALANCER:onenterMonitoring( SetGroup ) - - self:T2( { self.SetClient:Count() } ) - --self.SetClient:Flush() - - self.SetClient:ForEachClient( - --- @param Wrapper.Client#CLIENT Client - function( Client ) - self:T3(Client.ClientName) - - local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP - if Client:IsAlive() then - - if AIGroup and AIGroup:IsAlive() == true then - - if self.ToNearestAirbase == false and self.ToHomeAirbase == false then - self:Destroy( Client.UnitName, AIGroup ) - else - -- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group. - -- If there is a CLIENT, the AI stays engaged and will not return. - -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. - - local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) - - self:T2( RangeZone ) - - _DATABASE:ForEachPlayer( - --- @param Wrapper.Unit#UNIT RangeTestUnit - function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) - self:T2( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) - if RangeTestUnit:IsInZone( RangeZone ) == true then - self:T2( "in zone" ) - if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then - self:T2( "in range" ) - PlayerInRange.Value = true - end - end - end, - - --- @param Core.Zone#ZONE_RADIUS RangeZone - -- @param Wrapper.Group#GROUP AIGroup - function( RangeZone, AIGroup, PlayerInRange ) - if PlayerInRange.Value == false then - self:Return( AIGroup ) - end - end - , RangeZone, AIGroup, PlayerInRange - ) - - end - self.Set:Remove( Client.UnitName ) - end - else - if not AIGroup or not AIGroup:IsAlive() == true then - self:T( "Client " .. Client.UnitName .. " not alive." ) - if not self.SpawnQueue[Client.UnitName] then - -- Spawn a new AI taking into account the spawn interval Earliest, Latest - self:__Spawn( math.random( self.Earliest, self.Latest ), Client.UnitName ) - self.SpawnQueue[Client.UnitName] = true - self:E( "New AI Spawned for Client " .. Client.UnitName ) - end - end - end - return true - end - ) - - self:__Monitor( 10 ) -end - - - ---- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**Air** -- --- **Air Patrolling or Staging.** --- --- ![Banner Image](..\Presentations\AI_PATROL\Dia1.JPG) --- --- === --- --- # 1) @{#AI_PATROL_ZONE} class, extends @{Fsm#FSM_CONTROLLABLE} --- --- The @{#AI_PATROL_ZONE} class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group}. --- --- ![Process](..\Presentations\AI_PATROL\Dia3.JPG) --- --- The AI_PATROL_ZONE is assigned a @{Group} and this must be done before the AI_PATROL_ZONE process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_PATROL\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_PATROL\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_PATROL\Dia9.JPG) --- ----- Note that the enemy is not engaged! To model enemy engagement, either tailor the **Detected** event, or --- use derived AI_ classes to model AI offensive or defensive behaviour. --- --- ![Process](..\Presentations\AI_PATROL\Dia10.JPG) --- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_PATROL\Dia11.JPG) --- --- ## 1.1) AI_PATROL_ZONE constructor --- --- * @{#AI_PATROL_ZONE.New}(): Creates a new AI_PATROL_ZONE object. --- --- ## 1.2) AI_PATROL_ZONE is a FSM --- --- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) --- --- ### 1.2.1) AI_PATROL_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Returning** ( Group ): The AI is returning to Base. --- * **Stopped** ( Group ): The process is stopped. --- * **Crashed** ( Group ): The AI has crashed or is dead. --- --- ### 1.2.2) AI_PATROL_ZONE Events --- --- * **Start** ( Group ): Start the process. --- * **Stop** ( Group ): Stop the process. --- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone. --- * **RTB** ( Group ): Route the AI to the home base. --- * **Detect** ( Group ): The AI is detecting targets. --- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. --- --- ## 1.3) Set or Get the AI controllable --- --- * @{#AI_PATROL_ZONE.SetControllable}(): Set the AIControllable. --- * @{#AI_PATROL_ZONE.GetControllable}(): Get the AIControllable. --- --- ## 1.4) Set the Speed and Altitude boundaries of the AI controllable --- --- * @{#AI_PATROL_ZONE.SetSpeed}(): Set the patrol speed boundaries of the AI, for the next patrol. --- * @{#AI_PATROL_ZONE.SetAltitude}(): Set altitude boundaries of the AI, for the next patrol. --- --- ## 1.5) Manage the detection process of the AI controllable --- --- The detection process of the AI controllable can be manipulated. --- Detection requires an amount of CPU power, which has an impact on your mission performance. --- Only put detection on when absolutely necessary, and the frequency of the detection can also be set. --- --- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. --- --- The detection frequency can be set with @{#AI_PATROL_ZONE.SetDetectionInterval}( seconds ), where the amount of seconds specify how much seconds will be waited before the next detection. --- Use the method @{#AI_PATROL_ZONE.GetDetectedUnits}() to obtain a list of the @{Unit}s detected by the AI. --- --- The detection can be filtered to potential targets in a specific zone. --- Use the method @{#AI_PATROL_ZONE.SetDetectionZone}() to set the zone where targets need to be detected. --- Note that when the zone is too far away, or the AI is not heading towards the zone, or the AI is too high, no targets may be detected --- according the weather conditions. --- --- ## 1.6) Manage the "out of fuel" in the AI_PATROL_ZONE --- --- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, --- while a new AI is targetted to the AI_PATROL_ZONE. --- Once the time is finished, the old AI will return to the base. --- Use the method @{#AI_PATROL_ZONE.ManageFuel}() to have this proces in place. --- --- ## 1.7) Manage "damage" behaviour of the AI in the AI_PATROL_ZONE --- --- When the AI is damaged, it is required that a new AIControllable is started. However, damage cannon be foreseen early on. --- Therefore, when the damage treshold is reached, the AI will return immediately to the home base (RTB). --- Use the method @{#AI_PATROL_ZONE.ManageDamage}() to have this proces in place. --- --- ==== --- --- # **OPEN ISSUES** --- --- 2017-01-17: When Spawned AI is located at an airbase, it will be routed first back to the airbase after take-off. --- --- 2016-01-17: --- -- Fixed problem with AI returning to base too early and unexpected. --- -- ReSpawning of AI will reset the AI_PATROL and derived classes. --- -- Checked the correct workings of SCHEDULER, and it DOES work correctly. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-01-17: Rename of class: **AI\_PATROL\_ZONE** is the new name for the old _AI\_PATROLZONE_. --- --- 2017-01-15: Complete revision. AI_PATROL_ZONE is the base class for other AI_PATROL like classes. --- --- 2016-09-01: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[Dutch_Baron](https://forums.eagle.ru/member.php?u=112075)**: Working together with James has resulted in the creation of the AI_BALANCER class. James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Testing and API concept review. --- --- ### Authors: --- --- * **FlightControl**: Design & Programming. --- --- @module AI_Patrol - ---- AI_PATROL_ZONE class --- @type AI_PATROL_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. --- @field Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @field Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @field Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @field Functional.Spawn#SPAWN CoordTest --- @extends Core.Fsm#FSM_CONTROLLABLE -AI_PATROL_ZONE = { - ClassName = "AI_PATROL_ZONE", -} - ---- Creates a new AI_PATROL_ZONE object --- @param #AI_PATROL_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_PATROL_ZONE self --- @usage --- -- Define a new AI_PATROL_ZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolSpawn = SPAWN:New( 'Patrol Group' ) --- PatrolArea = AI_PATROL_ZONE:New( PatrolZone, 3000, 6000, 600, 900 ) -function AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_PATROL_ZONE - - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - -- defafult PatrolAltType to "RADIO" if not specified - self.PatrolAltType = PatrolAltType or "RADIO" - - self:SetDetectionInterval( 30 ) - - self.CheckStatus = true - - self:ManageFuel( .2, 60 ) - self:ManageDamage( 1 ) - - - self.DetectedUnits = {} -- This table contains the targets detected during patrol. - - self:SetStartState( "None" ) - - self:AddTransition( "*", "Stop", "Stopped" ) - ---- OnLeave Transition Handler for State Stopped. --- @function [parent=#AI_PATROL_ZONE] OnLeaveStopped --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Stopped. --- @function [parent=#AI_PATROL_ZONE] OnEnterStopped --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- OnBefore Transition Handler for Event Stop. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStop --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Stop. --- @function [parent=#AI_PATROL_ZONE] OnAfterStop --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Stop. --- @function [parent=#AI_PATROL_ZONE] Stop --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Stop. --- @function [parent=#AI_PATROL_ZONE] __Stop --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "None", "Start", "Patrolling" ) - ---- OnBefore Transition Handler for Event Start. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStart --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Start. --- @function [parent=#AI_PATROL_ZONE] OnAfterStart --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Start. --- @function [parent=#AI_PATROL_ZONE] Start --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Start. --- @function [parent=#AI_PATROL_ZONE] __Start --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Patrolling. --- @function [parent=#AI_PATROL_ZONE] OnLeavePatrolling --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Patrolling. --- @function [parent=#AI_PATROL_ZONE] OnEnterPatrolling --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Patrolling", "Route", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Route. --- @function [parent=#AI_PATROL_ZONE] OnBeforeRoute --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Route. --- @function [parent=#AI_PATROL_ZONE] OnAfterRoute --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Route. --- @function [parent=#AI_PATROL_ZONE] Route --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Route. --- @function [parent=#AI_PATROL_ZONE] __Route --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Status. --- @function [parent=#AI_PATROL_ZONE] OnBeforeStatus --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Status. --- @function [parent=#AI_PATROL_ZONE] OnAfterStatus --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Status. --- @function [parent=#AI_PATROL_ZONE] Status --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Status. --- @function [parent=#AI_PATROL_ZONE] __Status --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Detect", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Detect. --- @function [parent=#AI_PATROL_ZONE] OnBeforeDetect --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Detect. --- @function [parent=#AI_PATROL_ZONE] OnAfterDetect --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Detect. --- @function [parent=#AI_PATROL_ZONE] Detect --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Detect. --- @function [parent=#AI_PATROL_ZONE] __Detect --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Detected", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event Detected. --- @function [parent=#AI_PATROL_ZONE] OnBeforeDetected --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Detected. --- @function [parent=#AI_PATROL_ZONE] OnAfterDetected --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Detected. --- @function [parent=#AI_PATROL_ZONE] Detected --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event Detected. --- @function [parent=#AI_PATROL_ZONE] __Detected --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "RTB", "Returning" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - ---- OnBefore Transition Handler for Event RTB. --- @function [parent=#AI_PATROL_ZONE] OnBeforeRTB --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event RTB. --- @function [parent=#AI_PATROL_ZONE] OnAfterRTB --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event RTB. --- @function [parent=#AI_PATROL_ZONE] RTB --- @param #AI_PATROL_ZONE self - ---- Asynchronous Event Trigger for Event RTB. --- @function [parent=#AI_PATROL_ZONE] __RTB --- @param #AI_PATROL_ZONE self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Returning. --- @function [parent=#AI_PATROL_ZONE] OnLeaveReturning --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Returning. --- @function [parent=#AI_PATROL_ZONE] OnEnterReturning --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_PATROL_ZONE. - - self:AddTransition( "*", "Eject", "*" ) - self:AddTransition( "*", "Crash", "Crashed" ) - self:AddTransition( "*", "PilotDead", "*" ) - - return self -end - - - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_PATROL_ZONE self --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_PATROL_ZONE self --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - --- * @{#AI_PATROL_ZONE.SetDetectionOn}(): Set the detection on. The AI will detect for targets. --- * @{#AI_PATROL_ZONE.SetDetectionOff}(): Set the detection off, the AI will not detect for targets. The existing target list will NOT be erased. - ---- Set the detection on. The AI will detect for targets. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionOn() - self:F2() - - self.DetectOn = true -end - ---- Set the detection off. The AI will NOT detect for targets. --- However, the list of already detected targets will be kept and can be enquired! --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionOff() - self:F2() - - self.DetectOn = false -end - ---- Set the status checking off. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetStatusOff() - self:F2() - - self.CheckStatus = false -end - ---- Activate the detection. The AI will detect for targets if the Detection is switched On. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionActivated() - self:F2() - - self:ClearDetectedUnits() - self.DetectActivated = true - self:__Detect( -self.DetectInterval ) -end - ---- Deactivate the detection. The AI will NOT detect for targets. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionDeactivated() - self:F2() - - self:ClearDetectedUnits() - self.DetectActivated = false -end - ---- Set the interval in seconds between each detection executed by the AI. --- The list of already detected targets will be kept and updated. --- Newly detected targets will be added, but already detected targets that were --- not detected in this cycle, will NOT be removed! --- The default interval is 30 seconds. --- @param #AI_PATROL_ZONE self --- @param #number Seconds The interval in seconds. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionInterval( Seconds ) - self:F2() - - if Seconds then - self.DetectInterval = Seconds - else - self.DetectInterval = 30 - end -end - ---- Set the detection zone where the AI is detecting targets. --- @param #AI_PATROL_ZONE self --- @param Core.Zone#ZONE DetectionZone The zone where to detect targets. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:SetDetectionZone( DetectionZone ) - self:F2() - - if DetectionZone then - self.DetectZone = DetectionZone - else - self.DetectZone = nil - end -end - ---- Gets a list of @{Unit#UNIT}s that were detected by the AI. --- No filtering is applied, so, ANY detected UNIT can be in this list. --- It is up to the mission designer to use the @{Unit} class and methods to filter the targets. --- @param #AI_PATROL_ZONE self --- @return #table The list of @{Unit#UNIT}s -function AI_PATROL_ZONE:GetDetectedUnits() - self:F2() - - return self.DetectedUnits -end - ---- Clears the list of @{Unit#UNIT}s that were detected by the AI. --- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ClearDetectedUnits() - self:F2() - self.DetectedUnits = {} -end - ---- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROL_ZONE. --- Once the time is finished, the old AI will return to the base. --- @param #AI_PATROL_ZONE self --- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolManageFuel = true - self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - return self -end - ---- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base. --- However, damage cannot be foreseen early on. --- Therefore, when the damage treshold is reached, --- the AI will return immediately to the home base (RTB). --- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25. --- @param #AI_PATROL_ZONE self --- @param #number PatrolDamageTreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged. --- @return #AI_PATROL_ZONE self -function AI_PATROL_ZONE:ManageDamage( PatrolDamageTreshold ) - - self.PatrolManageDamage = true - self.PatrolDamageTreshold = PatrolDamageTreshold - - return self -end - ---- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. --- @param #AI_PATROL_ZONE self --- @return #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To ) - self:F2() - - self:__Route( 1 ) -- Route to the patrol point. The asynchronous trigger is important, because a spawned group and units takes at least one second to come live. - self:__Status( 60 ) -- Check status status every 30 seconds. - self:SetDetectionActivated() - - self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead ) - self:HandleEvent( EVENTS.Crash, self.OnCrash ) - self:HandleEvent( EVENTS.Ejection, self.OnEjection ) - - Controllable:OptionROEHoldFire() - Controllable:OptionROTVertical() - - self.Controllable:OnReSpawn( - function( PatrolGroup ) - self:E( "ReSpawn" ) - self:__Reset( 1 ) - self:__Route( 5 ) - end - ) - - self:SetDetectionOn() - -end - - ---- @param #AI_PATROL_ZONE self ---- @param Wrapper.Controllable#CONTROLLABLE Controllable -function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To ) - - return self.DetectOn and self.DetectActivated -end - ---- @param #AI_PATROL_ZONE self ---- @param Wrapper.Controllable#CONTROLLABLE Controllable -function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To ) - - local Detected = false - - local DetectedTargets = Controllable:GetDetectedTargets() - for TargetID, Target in pairs( DetectedTargets or {} ) do - local TargetObject = Target.object - - if TargetObject and TargetObject:isExist() and TargetObject.id_ < 50000000 then - - local TargetUnit = UNIT:Find( TargetObject ) - local TargetUnitName = TargetUnit:GetName() - - if self.DetectionZone then - if TargetUnit:IsInZone( self.DetectionZone ) then - self:T( {"Detected ", TargetUnit } ) - if self.DetectedUnits[TargetUnit] == nil then - self.DetectedUnits[TargetUnit] = true - end - Detected = true - end - else - if self.DetectedUnits[TargetUnit] == nil then - self.DetectedUnits[TargetUnit] = true - end - Detected = true - end - end - end - - self:__Detect( -self.DetectInterval ) - - if Detected == true then - self:__Detected( 1.5 ) - end - -end - ---- @param Wrapper.Controllable#CONTROLLABLE AIControllable --- This statis method is called from the route path within the last task at the last waaypoint of the Controllable. --- Note that this method is required, as triggers the next route when patrolling for the Controllable. -function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable ) - - local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROL_ZONE - PatrolZone:__Route( 1 ) -end - - ---- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. --- @param #AI_PATROL_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) - - self:F2() - - -- When RTB, don't allow anymore the routing. - if From == "RTB" then - return - end - - - if self.Controllable:IsAlive() then - -- Determine if the AIControllable is within the PatrolZone. - -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. - - local PatrolRoute = {} - - -- Calculate the current route point of the controllable as the start point of the route. - -- However, when the controllable is not in the air, - -- the controllable current waypoint is probably the airbase... - -- Thus, if we would take the current waypoint as the startpoint, upon take-off, the controllable flies - -- immediately back to the airbase, and this is not correct. - -- Therefore, when on a runway, get as the current route point a random point within the PatrolZone. - -- This will make the plane fly immediately to the patrol zone. - - if self.Controllable:InAir() == false then - self:E( "Not in the air, finding route path within PatrolZone" ) - local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TakeOffParking, - POINT_VEC3.RoutePointAction.FromParkingArea, - ToPatrolZoneSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - else - self:E( "In the air, finding route path within PatrolZone" ) - local CurrentVec2 = self.Controllable:GetVec2() - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - end - - - --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - --self.CoordTest:SpawnFromVec3( ToTargetPointVec3:GetVec3() ) - - --ToTargetPointVec3:SmokeRed() - - PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( PatrolRoute ) - - --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "PatrolZone", self ) - self.Controllable:WayPointFunction( #PatrolRoute, 1, "AI_PATROL_ZONE:_NewPatrolRoute" ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 2 ) - end - -end - ---- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onbeforeStatus() - - return self.CheckStatus -end - ---- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterStatus() - self:F2() - - if self.Controllable and self.Controllable:IsAlive() then - - local RTB = false - - local Fuel = self.Controllable:GetUnit(1):GetFuel() - if Fuel < self.PatrolFuelTresholdPercentage then - self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" ) - local OldAIControllable = self.Controllable - local AIControllableTemplate = self.Controllable:GetTemplate() - - local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldAIControllable:SetTask( TimedOrbitTask, 10 ) - - RTB = true - else - end - - -- TODO: Check GROUP damage function. - local Damage = self.Controllable:GetLife() - if Damage <= self.PatrolDamageTreshold then - self:E( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" ) - RTB = true - end - - if RTB == true then - self:RTB() - else - self:__Status( 60 ) -- Execute the Patrol event after 30 seconds. - end - end -end - ---- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterRTB() - self:F2() - - if self.Controllable and self.Controllable:IsAlive() then - - self:SetDetectionOff() - self.CheckStatus = false - - local PatrolRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( PatrolRoute ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 1 ) - - end - -end - ---- @param #AI_PATROL_ZONE self -function AI_PATROL_ZONE:onafterDead() - self:SetDetectionOff() - self:SetStatusOff() -end - ---- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnCrash( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:E( self.Controllable:GetUnits() ) - if #self.Controllable:GetUnits() == 1 then - self:__Crash( 1, EventData ) - end - end -end - ---- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnEjection( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__Eject( 1, EventData ) - end -end - ---- @param #AI_PATROL_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_PATROL_ZONE:OnPilotDead( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__PilotDead( 1, EventData ) - end -end ---- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**Air** -- --- **Provide Close Air Support to friendly ground troops.** --- --- ![Banner Image](..\Presentations\AI_CAS\Dia1.JPG) --- --- === --- --- # 1) @{#AI_CAS_ZONE} class, extends @{AI_Patrol#AI_PATROL_ZONE} --- --- @{#AI_CAS_ZONE} derives from the @{AI_Patrol#AI_PATROL_ZONE}, inheriting its methods and behaviour. --- --- The @{#AI_CAS_ZONE} class implements the core functions to provide Close Air Support in an Engage @{Zone} by an AIR @{Controllable} or @{Group}. --- The AI_CAS_ZONE runs a process. It holds an AI in a Patrol Zone and when the AI is commanded to engage, it will fly to an Engage Zone. --- --- ![HoldAndEngage](..\Presentations\AI_CAS\Dia3.JPG) --- --- The AI_CAS_ZONE is assigned a @{Group} and this must be done before the AI_CAS_ZONE process can be started through the **Start** event. --- --- ![Start Event](..\Presentations\AI_CAS\Dia4.JPG) --- --- Upon started, The AI will **Route** itself towards the random 3D point within a patrol zone, --- using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- This cycle will continue until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- --- ![Route Event](..\Presentations\AI_CAS\Dia5.JPG) --- --- When the AI is commanded to provide Close Air Support (through the event **Engage**), the AI will fly towards the Engage Zone. --- Any target that is detected in the Engage Zone will be reported and will be destroyed by the AI. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia6.JPG) --- --- The AI will detect the targets and will only destroy the targets within the Engage Zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia7.JPG) --- --- Every target that is destroyed, is reported< by the AI. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia8.JPG) --- --- Note that the AI does not know when the Engage Zone is cleared, and therefore will keep circling in the zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia9.JPG) --- --- Until it is notified through the event **Accomplish**, which is to be triggered by an observing party: --- --- * a FAC --- * a timed event --- * a menu option selected by a human --- * a condition --- * others ... --- --- ![Engage Event](..\Presentations\AI_CAS\Dia10.JPG) --- --- When the AI has accomplished the CAS, it will fly back to the Patrol Zone. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia11.JPG) --- --- It will keep patrolling there, until it is notified to RTB or move to another CAS Zone. --- It can be notified to go RTB through the **RTB** event. --- --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Engage Event](..\Presentations\AI_CAS\Dia12.JPG) --- --- # 1.1) AI_CAS_ZONE constructor --- --- * @{#AI_CAS_ZONE.New}(): Creates a new AI_CAS_ZONE object. --- --- ## 1.2) AI_CAS_ZONE is a FSM --- --- ![Process](..\Presentations\AI_CAS\Dia2.JPG) --- --- ### 1.2.1) AI_CAS_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the targets in the Engage Zone, executing CAS. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 1.2.2) AI_CAS_ZONE Events --- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_CAS_ZONE.Engage}**: Engage the AI to provide CAS in the Engage Zone, destroying any target it finds. --- * **@{#AI_CAS_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAS_ZONE.Destroy}**: The AI has destroyed a target @{Unit}. --- * **@{#AI_CAS_ZONE.Destroyed}**: The AI has destroyed all target @{Unit}s assigned in the CAS task. --- * **Status**: The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-01-15: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing. --- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Concept, Advice & Testing. --- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision. --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module AI_Cas - - ---- AI_CAS_ZONE class --- @type AI_CAS_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. --- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed. --- @extends AI.AI_Patrol#AI_PATROL_ZONE -AI_CAS_ZONE = { - ClassName = "AI_CAS_ZONE", -} - - - ---- Creates a new AI_CAS_ZONE object --- @param #AI_CAS_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @param Core.Zone#ZONE_BASE EngageZone The zone where the engage will happen. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_CAS_ZONE self -function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageZone, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAS_ZONE - - self.EngageZone = EngageZone - self.Accomplished = false - - self:SetDetectionZone( self.EngageZone ) - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_CAS_ZONE] OnBeforeEngage - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_CAS_ZONE] OnAfterEngage - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAS_ZONE] Engage - -- @param #AI_CAS_ZONE self - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAS_ZONE] __Engage - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - -- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. - -- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. - -- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. - -- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. - -- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_CAS_ZONE] OnEnterEngaging --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Target", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_CAS_ZONE] OnBeforeFired - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_CAS_ZONE] OnAfterFired - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAS_ZONE] Fired - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAS_ZONE] __Fired - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] OnBeforeDestroy - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] OnAfterDestroy - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] Destroy - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAS_ZONE] __Destroy - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_CAS_ZONE] OnBeforeAbort - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_CAS_ZONE] OnAfterAbort - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAS_ZONE] Abort - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAS_ZONE] __Abort - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAS_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] OnBeforeAccomplish - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] OnAfterAccomplish - -- @param #AI_CAS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] Accomplish - -- @param #AI_CAS_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAS_ZONE] __Accomplish - -- @param #AI_CAS_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - - ---- Set the Engage Zone where the AI is performing CAS. Note that if the EngageZone is changed, the AI needs to re-detect targets. --- @param #AI_CAS_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAS. --- @return #AI_CAS_ZONE self -function AI_CAS_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - - - ---- onafter State Transition for Event Start. --- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To ) - - -- Call the parent Start event handler - self:GetParent(self).onafterStart( self, Controllable, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) - - self:SetDetectionDeactivated() -- When not engaging, set the detection off. -end - ---- @param Wrapper.Controllable#CONTROLLABLE AIControllable -function _NewEngageRoute( AIControllable ) - - AIControllable:T( "NewEngageRoute" ) - local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cas#AI_CAS_ZONE - EngageZone:__Engage( 1, EngageZone.EngageSpeed, EngageZone.EngageAltitude, EngageZone.EngageWeaponExpend, EngageZone.EngageAttackQty, EngageZone.EngageDirection ) -end - - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To ) - self:E("onafterTarget") - - if Controllable:IsAlive() then - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - if DetectedUnit:IsInZone( self.EngageZone ) then - if Detected == true then - self:E( {"Target: ", DetectedUnit } ) - self.DetectedUnits[DetectedUnit] = false - local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) - self.Controllable:PushTask( AttackTask, 1 ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - self:__Target( -10 ) - - end -end - - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param #number EngageSpeed (optional) The speed the Group will hold when engaging to the target zone. --- @param Dcs.DCSTypes#Distance EngageAltitude (optional) Desired altitude to perform the unit engagement. --- @param Dcs.DCSTypes#AI.Task.WeaponExpend EngageWeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number EngageAttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param Dcs.DCSTypes#Azimuth EngageDirection (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, - EngageSpeed, - EngageAltitude, - EngageWeaponExpend, - EngageAttackQty, - EngageDirection ) - self:F("onafterEngage") - - self.EngageSpeed = EngageSpeed or 400 - self.EngageAltitude = EngageAltitude or 2000 - self.EngageWeaponExpend = EngageWeaponExpend - self.EngageAttackQty = EngageAttackQty - self.EngageDirection = EngageDirection - - if Controllable:IsAlive() then - - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - self.EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - --- if self.Controllable:IsNotInZone( self.EngageZone ) then --- --- -- Find a random 2D point in EngageZone. --- local ToEngageZoneVec2 = self.EngageZone:GetRandomVec2() --- self:T2( ToEngageZoneVec2 ) --- --- -- Obtain a 3D @{Point} from the 2D point + altitude. --- local ToEngageZonePointVec3 = POINT_VEC3:New( ToEngageZoneVec2.x, self.EngageAltitude, ToEngageZoneVec2.y ) --- --- -- Create a route point of type air. --- local ToEngageZoneRoutePoint = ToEngageZonePointVec3:RoutePointAir( --- self.PatrolAltType, --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, --- self.EngageSpeed, --- true --- ) --- --- EngageRoute[#EngageRoute+1] = ToEngageZoneRoutePoint --- --- end --- - --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in EngageZone. - local ToTargetVec2 = self.EngageZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - self.EngageSpeed, - true - ) - - --ToTargetPointVec3:SmokeBlue() - - EngageRoute[#EngageRoute+1] = ToTargetRoutePoint - - - Controllable:OptionROEOpenFire() - Controllable:OptionROTVertical() - --- local AttackTasks = {} --- --- for DetectedUnitID, DetectedUnit in pairs( self.DetectedUnits ) do --- local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT --- self:T( DetectedUnit ) --- if DetectedUnit:IsAlive() then --- if DetectedUnit:IsInZone( self.EngageZone ) then --- self:E( {"Engaging ", DetectedUnit } ) --- AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) --- end --- else --- self.DetectedUnits[DetectedUnit] = nil --- end --- end --- --- EngageRoute[1].task = Controllable:TaskCombo( AttackTasks ) - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( EngageRoute ) - - --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "EngageZone", self ) - - self.Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageRoute" ) - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1 ) - - self:SetDetectionInterval( 2 ) - self:SetDetectionActivated() - self:__Target( -2 ) -- Start Targetting - end -end - - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAS_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionDeactivated() -end - - ---- @param #AI_CAS_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_CAS_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - - ---- @param #AI_CAS_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_CAS_ZONE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end - - ---- Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**Air** -- **Execute Combat Air Patrol (CAP).** --- --- ![Banner Image](..\Presentations\AI_CAP\Dia1.JPG) --- --- === --- --- # 1) @{#AI_CAP_ZONE} class, extends @{AI_CAP#AI_PATROL_ZONE} --- --- The @{#AI_CAP_ZONE} class implements the core functions to patrol a @{Zone} by an AI @{Controllable} or @{Group} --- and automatically engage any airborne enemies that are within a certain range or within a certain zone. --- --- ![Process](..\Presentations\AI_CAP\Dia3.JPG) --- --- The AI_CAP_ZONE is assigned a @{Group} and this must be done before the AI_CAP_ZONE process can be started using the **Start** event. --- --- ![Process](..\Presentations\AI_CAP\Dia4.JPG) --- --- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. --- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits. --- --- ![Process](..\Presentations\AI_CAP\Dia5.JPG) --- --- This cycle will continue. --- --- ![Process](..\Presentations\AI_CAP\Dia6.JPG) --- --- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event. --- --- ![Process](..\Presentations\AI_CAP\Dia9.JPG) --- --- When enemies are detected, the AI will automatically engage the enemy. --- --- ![Process](..\Presentations\AI_CAP\Dia10.JPG) --- --- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB. --- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. --- --- ![Process](..\Presentations\AI_CAP\Dia13.JPG) --- --- ## 1.1) AI_CAP_ZONE constructor --- --- * @{#AI_CAP_ZONE.New}(): Creates a new AI_CAP_ZONE object. --- --- ## 1.2) AI_CAP_ZONE is a FSM --- --- ![Process](..\Presentations\AI_CAP\Dia2.JPG) --- --- ### 1.2.1) AI_CAP_ZONE States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Engaging** ( Group ): The AI is engaging the bogeys. --- * **Returning** ( Group ): The AI is returning to Base.. --- --- ### 1.2.2) AI_CAP_ZONE Events --- --- * **@{AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. --- * **@{AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_CAP_ZONE.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_CAP_ZONE.Abort}**: Aborts the engagement and return patrolling in the patrol zone. --- * **@{AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. --- * **@{AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_CAP_ZONE.Destroy}**: The AI has destroyed a bogey @{Unit}. --- * **@{#AI_CAP_ZONE.Destroyed}**: The AI has destroyed all bogeys @{Unit}s assigned in the CAS task. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. --- --- ## 1.3) Set the Range of Engagement --- --- ![Range](..\Presentations\AI_CAP\Dia11.JPG) --- --- An optional range can be set in meters, --- that will define when the AI will engage with the detected airborne enemy targets. --- The range can be beyond or smaller than the range of the Patrol Zone. --- The range is applied at the position of the AI. --- Use the method @{AI_CAP#AI_CAP_ZONE.SetEngageRange}() to define that range. --- --- ## 1.4) Set the Zone of Engagement --- --- ![Zone](..\Presentations\AI_CAP\Dia12.JPG) --- --- An optional @{Zone} can be set, --- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{AI_Cap#AI_CAP_ZONE.SetEngageZone}() to define that Zone. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-01-15: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[Quax](https://forums.eagle.ru/member.php?u=90530)**: Concept, Advice & Testing. --- * **[Pikey](https://forums.eagle.ru/member.php?u=62835)**: Concept, Advice & Testing. --- * **[Gunterlund](http://forums.eagle.ru:8080/member.php?u=75036)**: Test case revision. --- * **[Whisper](http://forums.eagle.ru/member.php?u=3829): Testing. --- * **[Delta99](https://forums.eagle.ru/member.php?u=125166): Testing. --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module AI_Cap - - ---- AI_CAP_ZONE class --- @type AI_CAP_ZONE --- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. --- @field Core.Zone#ZONE_BASE TargetZone The @{Zone} where the patrol needs to be executed. --- @extends AI.AI_Patrol#AI_PATROL_ZONE -AI_CAP_ZONE = { - ClassName = "AI_CAP_ZONE", -} - - - ---- Creates a new AI_CAP_ZONE object --- @param #AI_CAP_ZONE self --- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. --- @param Dcs.DCSTypes#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_PATROL_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_CAP_ZONE - - self.Accomplished = false - self.Engaging = false - - self:AddTransition( { "Patrolling", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_CAP_ZONE] OnBeforeEngage - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_CAP_ZONE] OnAfterEngage - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAP_ZONE] Engage - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_CAP_ZONE] __Engage - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_CAP_ZONE] OnLeaveEngaging --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_CAP_ZONE] OnEnterEngaging --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_CAP_ZONE] OnBeforeFired - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_CAP_ZONE] OnAfterFired - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAP_ZONE] Fired - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_CAP_ZONE] __Fired - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] OnBeforeDestroy - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] OnAfterDestroy - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] Destroy - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_CAP_ZONE] __Destroy - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_CAP_ZONE] OnBeforeAbort - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_CAP_ZONE] OnAfterAbort - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAP_ZONE] Abort - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_CAP_ZONE] __Abort - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_CAP_ZONE. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] OnBeforeAccomplish - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] OnAfterAccomplish - -- @param #AI_CAP_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] Accomplish - -- @param #AI_CAP_ZONE self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_CAP_ZONE] __Accomplish - -- @param #AI_CAP_ZONE self - -- @param #number Delay The delay in seconds. - - return self -end - - ---- Set the Engage Zone which defines where the AI will engage bogies. --- @param #AI_CAP_ZONE self --- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP. --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:SetEngageZone( EngageZone ) - self:F2() - - if EngageZone then - self.EngageZone = EngageZone - else - self.EngageZone = nil - end -end - ---- Set the Engage Range when the AI will engage with airborne enemies. --- @param #AI_CAP_ZONE self --- @param #number EngageRange The Engage Range. --- @return #AI_CAP_ZONE self -function AI_CAP_ZONE:SetEngageRange( EngageRange ) - self:F2() - - if EngageRange then - self.EngageRange = EngageRange - else - self.EngageRange = nil - end -end - ---- onafter State Transition for Event Start. --- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To ) - - -- Call the parent Start event handler - self:GetParent(self).onafterStart( self, Controllable, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) - -end - --- todo: need to fix this global function - ---- @param Wrapper.Controllable#CONTROLLABLE AIControllable -function _NewEngageCapRoute( AIControllable ) - - AIControllable:T( "NewEngageRoute" ) - local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_CAP_ZONE - EngageZone:__Engage( 1 ) -end - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onbeforeEngage( Controllable, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To ) - - if From ~= "Engaging" then - - local Engage = false - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( DetectedUnit ) - if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then - Engage = true - break - end - end - - if Engage == true then - self:E( 'Detected -> Engaging' ) - self:__Engage( 1 ) - end - end -end - - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterAbort( Controllable, From, Event, To ) - Controllable:ClearTasks() - self:__Route( 1 ) -end - - - - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) - - if Controllable:IsAlive() then - - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToEngageZoneSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = ToTargetPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - - Controllable:OptionROEOpenFire() - Controllable:OptionROTPassiveDefense() - - local AttackTasks = {} - - for DetectedUnit, Detected in pairs( self.DetectedUnits ) do - local DetectedUnit = DetectedUnit -- Wrapper.Unit#UNIT - self:T( { DetectedUnit, DetectedUnit:IsAlive(), DetectedUnit:IsAir() } ) - if DetectedUnit:IsAlive() and DetectedUnit:IsAir() then - if self.EngageZone then - if DetectedUnit:IsInZone( self.EngageZone ) then - self:E( {"Within Zone and Engaging ", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - else - if self.EngageRange then - if DetectedUnit:GetPointVec3():Get2DDistance(Controllable:GetPointVec3() ) <= self.EngageRange then - self:E( {"Within Range and Engaging", DetectedUnit } ) - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - else - AttackTasks[#AttackTasks+1] = Controllable:TaskAttackUnit( DetectedUnit ) - end - end - else - self.DetectedUnits[DetectedUnit] = nil - end - end - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( EngageRoute ) - - - if #AttackTasks == 0 then - self:E("No targets found -> Going back to Patrolling") - self:__Abort( 1 ) - self:__Route( 1 ) - self:SetDetectionActivated() - else - EngageRoute[1].task = Controllable:TaskCombo( AttackTasks ) - - --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "EngageZone", self ) - - self.Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageCapRoute" ) - - self:SetDetectionDeactivated() - end - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 2 ) - - end -end - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_CAP_ZONE:onafterAccomplish( Controllable, From, Event, To ) - self.Accomplished = true - self:SetDetectionOff() -end - ---- @param #AI_CAP_ZONE self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_CAP_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) - - if EventData.IniUnit then - self.DetectedUnits[EventData.IniUnit] = nil - end -end - ---- @param #AI_CAP_ZONE self --- @param Core.Event#EVENTDATA EventData -function AI_CAP_ZONE:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.DetectedUnits and self.DetectedUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end ----Single-Player:**Yes** / Multi-Player:**Yes** / AI:**Yes** / Human:**No** / Types:**Ground** -- --- **Management of logical cargo objects, that can be transported from and to transportation carriers.** --- --- ![Banner Image](..\Presentations\AI_CARGO\CARGO.JPG) --- --- === --- --- Cargo can be of various forms, always are composed out of ONE object ( one unit or one static or one slingload crate ): --- --- * AI_CARGO_UNIT, represented by a @{Unit} in a @{Group}: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost. --- * CARGO_STATIC, represented by a @{Static}: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost. --- * AI_CARGO_PACKAGE, contained in a @{Unit} of a @{Group}: Cargo can be contained within a Unit of a Group. The cargo can be **delivered** by the @{Unit}. If the Unit is destroyed, the cargo will be destroyed also. --- * AI_CARGO_PACKAGE, Contained in a @{Static}: Cargo can be contained within a Static. The cargo can be **collected** from the @Static. If the @{Static} is destroyed, the cargo will be destroyed. --- * CARGO_SLINGLOAD, represented by a @{Cargo} that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost. --- --- * AI_CARGO_GROUPED, represented by a Group of CARGO_UNITs. --- --- # 1) @{#AI_CARGO} class, extends @{Fsm#FSM_PROCESS} --- --- The @{#AI_CARGO} class defines the core functions that defines a cargo object within MOOSE. --- A cargo is a logical object defined that is available for transport, and has a life status within a simulation. --- --- The AI_CARGO is a state machine: it manages the different events and states of the cargo. --- All derived classes from AI_CARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states. --- --- ## 1.2.1) AI_CARGO Events: --- --- * @{#AI_CARGO.Board}( ToCarrier ): Boards the cargo to a carrier. --- * @{#AI_CARGO.Load}( ToCarrier ): Loads the cargo into a carrier, regardless of its position. --- * @{#AI_CARGO.UnBoard}( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2. --- * @{#AI_CARGO.UnLoad}( ToPointVec2 ): UnLoads the cargo from a carrier. --- * @{#AI_CARGO.Dead}( Controllable ): The cargo is dead. The cargo process will be ended. --- --- ## 1.2.2) AI_CARGO States: --- --- * **UnLoaded**: The cargo is unloaded from a carrier. --- * **Boarding**: The cargo is currently boarding (= running) into a carrier. --- * **Loaded**: The cargo is loaded into a carrier. --- * **UnBoarding**: The cargo is currently unboarding (=running) from a carrier. --- * **Dead**: The cargo is dead ... --- * **End**: The process has come to an end. --- --- ## 1.2.3) AI_CARGO state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Leaving** the state. --- The state transition method needs to start with the name **OnLeave + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **Entering** the state. --- The state transition method needs to start with the name **OnEnter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- # 2) #AI_CARGO_UNIT class --- --- The AI_CARGO_UNIT class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. --- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. --- --- # 5) #AI_CARGO_GROUPED class --- --- The AI_CARGO_GROUPED class defines a cargo that is represented by a group of UNIT objects within the simulator, and can be transported by a carrier. --- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. --- --- This module is still under construction, but is described above works already, and will keep working ... --- --- @module Cargo - --- Events - --- Board - ---- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#AI_CARGO] Board --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - ---- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#AI_CARGO] __Board --- @param #AI_CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - - --- UnBoard - ---- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. --- The cargo must be in the **Loaded** state. --- @function [parent=#AI_CARGO] UnBoard --- @param #AI_CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. - ---- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. --- The cargo must be in the **Loaded** state. --- @function [parent=#AI_CARGO] __UnBoard --- @param #AI_CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. - - --- Load - ---- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#AI_CARGO] Load --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - ---- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. --- The cargo must be in the **UnLoaded** state. --- @function [parent=#AI_CARGO] __Load --- @param #AI_CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. - - --- UnLoad - ---- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. --- The cargo must be in the **Loaded** state. --- @function [parent=#AI_CARGO] UnLoad --- @param #AI_CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. - ---- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. --- The cargo must be in the **Loaded** state. --- @function [parent=#AI_CARGO] __UnLoad --- @param #AI_CARGO self --- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. - --- State Transition Functions - --- UnLoaded - ---- @function [parent=#AI_CARGO] OnLeaveUnLoaded --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#AI_CARGO] OnEnterUnLoaded --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- Loaded - ---- @function [parent=#AI_CARGO] OnLeaveLoaded --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#AI_CARGO] OnEnterLoaded --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- Boarding - ---- @function [parent=#AI_CARGO] OnLeaveBoarding --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#AI_CARGO] OnEnterBoarding --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - --- UnBoarding - ---- @function [parent=#AI_CARGO] OnLeaveUnBoarding --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable --- @return #boolean - ---- @function [parent=#AI_CARGO] OnEnterUnBoarding --- @param #AI_CARGO self --- @param Wrapper.Controllable#CONTROLLABLE Controllable - - --- TODO: Find all Carrier objects and make the type of the Carriers Wrapper.Unit#UNIT in the documentation. - -CARGOS = {} - -do -- AI_CARGO - - --- @type AI_CARGO - -- @extends Core.Fsm#FSM_PROCESS - -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. - -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. - -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. - -- @field #number ReportRadius (optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier. - -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. - -- @field Wrapper.Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... - -- @field Wrapper.Controllable#CONTROLLABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... - -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. - -- @field #boolean Moveable This flag defines if the cargo is moveable. - -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. - -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. - AI_CARGO = { - ClassName = "AI_CARGO", - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, - } - ---- @type AI_CARGO.CargoObjects --- @map < #string, Wrapper.Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. - - ---- AI_CARGO Constructor. This class is an abstract class and should not be instantiated. --- @param #AI_CARGO self --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO -function AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) - - local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:SetStartState( "UnLoaded" ) - self:AddTransition( "UnLoaded", "Board", "Boarding" ) - self:AddTransition( "Boarding", "Boarding", "Boarding" ) - self:AddTransition( "Boarding", "Load", "Loaded" ) - self:AddTransition( "UnLoaded", "Load", "Loaded" ) - self:AddTransition( "Loaded", "UnBoard", "UnBoarding" ) - self:AddTransition( "UnBoarding", "UnBoarding", "UnBoarding" ) - self:AddTransition( "UnBoarding", "UnLoad", "UnLoaded" ) - self:AddTransition( "Loaded", "UnLoad", "UnLoaded" ) - - - self.Type = Type - self.Name = Name - self.Weight = Weight - self.ReportRadius = ReportRadius - self.NearRadius = NearRadius - self.CargoObject = nil - self.CargoCarrier = nil - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.Name] = self - - return self -end - - ---- Template method to spawn a new representation of the AI_CARGO in the simulator. --- @param #AI_CARGO self --- @return #AI_CARGO -function AI_CARGO:Spawn( PointVec2 ) - self:F() - -end - - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #AI_CARGO self --- @param Core.Point#POINT_VEC2 PointVec2 --- @return #boolean -function AI_CARGO:IsNear( PointVec2 ) - self:F( { PointVec2 } ) - - local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - -end - -do -- AI_CARGO_REPRESENTABLE - - --- @type AI_CARGO_REPRESENTABLE - -- @extends #AI_CARGO - AI_CARGO_REPRESENTABLE = { - ClassName = "AI_CARGO_REPRESENTABLE" - } - ---- AI_CARGO_REPRESENTABLE Constructor. --- @param #AI_CARGO_REPRESENTABLE self --- @param Wrapper.Controllable#Controllable CargoObject --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_REPRESENTABLE -function AI_CARGO_REPRESENTABLE:New( CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - return self -end - ---- Route a cargo unit to a PointVec2. --- @param #AI_CARGO_REPRESENTABLE self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #number Speed --- @return #AI_CARGO_REPRESENTABLE -function AI_CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) - self:F2( ToPointVec2 ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - return self -end - -end -- AI_CARGO - -do -- AI_CARGO_UNIT - - --- @type AI_CARGO_UNIT - -- @extends #AI_CARGO_REPRESENTABLE - AI_CARGO_UNIT = { - ClassName = "AI_CARGO_UNIT" - } - ---- AI_CARGO_UNIT Constructor. --- @param #AI_CARGO_UNIT self --- @param Wrapper.Unit#UNIT CargoUnit --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_UNIT -function AI_CARGO_UNIT:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_UNIT - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoUnit ) - self.CargoObject = CargoUnit - - self:T( self.ClassName ) - - return self -end - ---- Enter UnBoarding State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function AI_CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local DeployDistance = 5 - local RouteDistance = 60 - - if From == "Loaded" then - - local CargoCarrierPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, CargoDeployHeading ) - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 - ToPointVec2 = ToPointVec2 or CargoRoutePointVec2 - - local FromPointVec2 = CargoCarrierPointVec2 - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - self.CargoCarrier = nil - - local Points = {} - Points[#Points+1] = FromPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - self:__UnBoarding( 1, ToPointVec2 ) - end - end - -end - ---- Leave UnBoarding State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function AI_CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - if self:IsNear( ToPointVec2 ) then - return true - else - self:__UnBoarding( 1, ToPointVec2 ) - end - return false - end - -end - ---- UnBoard Event. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 ToPointVec2 -function AI_CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - end - - self:__UnLoad( 1, ToPointVec2 ) - -end - - - ---- Enter UnLoaded State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Core.Point#POINT_VEC2 -function AI_CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "Loaded" then - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - ToPointVec2 = ToPointVec2 or POINT_VEC2:New( CargoDeployPointVec2:GetX(), CargoDeployPointVec2:GetY() ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - -end - - - ---- Enter Boarding State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - local Speed = 10 - local Angle = 180 - local Distance = 5 - - if From == "UnLoaded" then - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - end - -end - ---- Leave Boarding State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_UNIT:onleaveBoarding( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - if self:IsNear( CargoCarrier:GetPointVec2() ) then - self:__Load( 1, CargoCarrier ) - return true - else - self:__Boarding( 1, CargoCarrier ) - end - return false -end - ---- Loaded State. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier ) - self:F() - - self.CargoCarrier = CargoCarrier - - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self:T("Destroying") - self.CargoObject:Destroy() - end -end - - ---- Board Event. --- @param #AI_CARGO_UNIT self --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier ) - self:F() - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - self:Load( CargoCarrier ) - end - -end - -end - -do -- AI_CARGO_PACKAGE - - --- @type AI_CARGO_PACKAGE - -- @extends #AI_CARGO_REPRESENTABLE - AI_CARGO_PACKAGE = { - ClassName = "AI_CARGO_PACKAGE" - } - ---- AI_CARGO_PACKAGE Constructor. --- @param #AI_CARGO_PACKAGE self --- @param Wrapper.Unit#UNIT CargoCarrier The UNIT carrying the package. --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_PACKAGE -function AI_CARGO_PACKAGE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_PACKAGE - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoCarrier ) - self.CargoCarrier = CargoCarrier - - return self -end - ---- Board Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number Angle -function AI_CARGO_PACKAGE:onafterOnBoard( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. - if not self.CargoInAir then - - local Points = {} - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:Boarded( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #AI_CARGO_PACKAGE self --- @param Wrapper.Unit#UNIT CargoCarrier --- @return #boolean -function AI_CARGO_PACKAGE:IsNear( CargoCarrier ) - self:F() - - local CargoCarrierPoint = CargoCarrier:GetPointVec2() - - local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - ---- Boarded Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_PACKAGE:onafterOnBoarded( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:__Load( 1, CargoCarrier, Speed, LoadDistance, Angle ) - else - self:__Boarded( 1, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - end -end - ---- UnBoard Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param #number Speed --- @param #number UnLoadDistance --- @param #number UnBoardDistance --- @param #number Radius --- @param #number Angle -function AI_CARGO_PACKAGE:onafterUnBoard( From, Event, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) - - local Points = {} - - local StartPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = CargoCarrier:TaskRoute( Points ) - CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:__UnBoarded( 1 , CargoCarrier, Speed ) - -end - ---- UnBoarded Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_PACKAGE:onafterUnBoarded( From, Event, To, CargoCarrier, Speed ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:__UnLoad( 1, CargoCarrier, Speed ) - else - self:__UnBoarded( 1, CargoCarrier, Speed ) - end -end - ---- Load Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number LoadDistance --- @param #number Angle -function AI_CARGO_PACKAGE:onafterLoad( From, Event, To, CargoCarrier, Speed, LoadDistance, Angle ) - self:F() - - self.CargoCarrier = CargoCarrier - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - ---- UnLoad Event. --- @param #AI_CARGO_PACKAGE self --- @param #string Event --- @param #string From --- @param #string To --- @param #number Distance --- @param #number Angle -function AI_CARGO_PACKAGE:onafterUnLoad( From, Event, To, CargoCarrier, Speed, Distance, Angle ) - self:F() - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - self.CargoCarrier = CargoCarrier - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - - -end - -do -- AI_CARGO_GROUP - - --- @type AI_CARGO_GROUP - -- @extends AI.AI_Cargo#AI_CARGO - -- @field Set#SET_BASE CargoSet A set of cargo objects. - -- @field #string Name A string defining the name of the cargo group. The name is the unique identifier of the cargo. - AI_CARGO_GROUP = { - ClassName = "AI_CARGO_GROUP", - } - ---- AI_CARGO_GROUP constructor. --- @param #AI_CARGO_GROUP self --- @param Core.Set#Set_BASE CargoSet --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_GROUP -function AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, 0, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUP - self:F( { Type, Name, ReportRadius, NearRadius } ) - - self.CargoSet = CargoSet - - - return self -end - -end -- AI_CARGO_GROUP - -do -- AI_CARGO_GROUPED - - --- @type AI_CARGO_GROUPED - -- @extends AI.AI_Cargo#AI_CARGO_GROUP - AI_CARGO_GROUPED = { - ClassName = "AI_CARGO_GROUPED", - } - ---- AI_CARGO_GROUPED constructor. --- @param #AI_CARGO_GROUPED self --- @param Core.Set#Set_BASE CargoSet --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #AI_CARGO_GROUPED -function AI_CARGO_GROUPED:New( CargoSet, Type, Name, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUPED - self:F( { Type, Name, ReportRadius, NearRadius } ) - - return self -end - ---- Enter Boarding State. --- @param #AI_CARGO_GROUPED self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onenterBoarding( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - if From == "UnLoaded" then - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - Cargo:__Board( 1, CargoCarrier ) - end - ) - - self:__Boarding( 1, CargoCarrier ) - end - -end - ---- Enter Loaded State. --- @param #AI_CARGO_GROUPED self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onenterLoaded( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - if From == "UnLoaded" then - -- For each Cargo object within the AI_CARGO_GROUPED, load each cargo to the CargoCarrier. - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - Cargo:Load( CargoCarrier ) - end - end -end - ---- Leave Boarding State. --- @param #AI_CARGO_GROUPED self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onleaveBoarding( From, Event, To, CargoCarrier ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - local Boarded = true - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - self:T( Cargo.current ) - if not Cargo:is( "Loaded" ) then - Boarded = false - end - end - - if not Boarded then - self:__Boarding( 1, CargoCarrier ) - else - self:__Load( 1, CargoCarrier ) - end - return Boarded -end - ---- Enter UnBoarding State. --- @param #AI_CARGO_GROUPED self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onenterUnBoarding( From, Event, To, ToPointVec2 ) - self:F() - - local Timer = 1 - - if From == "Loaded" then - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - Cargo:__UnBoard( Timer, ToPointVec2 ) - Timer = Timer + 10 - end - ) - - self:__UnBoarding( 1, ToPointVec2 ) - end - -end - ---- Leave UnBoarding State. --- @param #AI_CARGO_GROUPED self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onleaveUnBoarding( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - local UnBoarded = true - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - self:T( Cargo.current ) - if not Cargo:is( "UnLoaded" ) then - UnBoarded = false - end - end - - if UnBoarded then - return true - else - self:__UnBoarding( 1, ToPointVec2 ) - end - - return false - end - -end - ---- UnBoard Event. --- @param #AI_CARGO_GROUPED self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onafterUnBoarding( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - self:__UnLoad( 1, ToPointVec2 ) -end - - - ---- Enter UnLoaded State. --- @param #AI_CARGO_GROUPED self --- @param Core.Point#POINT_VEC2 --- @param #string Event --- @param #string From --- @param #string To -function AI_CARGO_GROUPED:onenterUnLoaded( From, Event, To, ToPointVec2 ) - self:F( { ToPointVec2, From, Event, To } ) - - if From == "Loaded" then - - -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - Cargo:UnLoad( ToPointVec2 ) - end - ) - - end - -end - -end -- AI_CARGO_GROUPED - - - ---- (SP) (MP) (FSM) Accept or reject process for player (task) assignments. --- --- === --- --- # @{#ACT_ASSIGN} FSM template class, extends @{Fsm#FSM_PROCESS} --- --- ## ACT_ASSIGN state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ASSIGN **Events**: --- --- These are the events defined in this class: --- --- * **Start**: Start the tasking acceptance process. --- * **Assign**: Assign the task. --- * **Reject**: Reject the task.. --- --- ### ACT_ASSIGN **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ASSIGN **States**: --- --- * **UnAssigned**: The player has not accepted the task. --- * **Assigned (*)**: The player has accepted the task. --- * **Rejected (*)**: The player has not accepted the task. --- * **Waiting**: The process is awaiting player feedback. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ASSIGN state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} --- --- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task. --- --- ## 1.1) ACT_ASSIGN_ACCEPT constructor: --- --- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object. --- --- === --- --- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} --- --- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option. --- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. --- The assignment type also allows to reject the task. --- --- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor: --- ----------------------------------------- --- --- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object. --- --- === --- --- @module Assign - - -do -- ACT_ASSIGN - - --- ACT_ASSIGN class - -- @type ACT_ASSIGN - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends Core.Fsm#FSM_PROCESS - ACT_ASSIGN = { - ClassName = "ACT_ASSIGN", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #ACT_ASSIGN self - -- @return #ACT_ASSIGN The task acceptance process. - function ACT_ASSIGN:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "UnAssigned", "Start", "Waiting" ) - self:AddTransition( "Waiting", "Assign", "Assigned" ) - self:AddTransition( "Waiting", "Reject", "Rejected" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Assigned" ) - self:AddEndState( "Rejected" ) - self:AddEndState( "Failed" ) - - self:SetStartState( "UnAssigned" ) - - return self - end - -end -- ACT_ASSIGN - - - -do -- ACT_ASSIGN_ACCEPT - - --- ACT_ASSIGN_ACCEPT class - -- @type ACT_ASSIGN_ACCEPT - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIGN - ACT_ASSIGN_ACCEPT = { - ClassName = "ACT_ASSIGN_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #ACT_ASSIGN_ACCEPT self - -- @param #string TaskBriefing - function ACT_ASSIGN_ACCEPT:New( TaskBriefing ) - - local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT - - self.TaskBriefing = TaskBriefing - - return self - end - - function ACT_ASSIGN_ACCEPT:Init( FsmAssign ) - - self.TaskBriefing = FsmAssign.TaskBriefing - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit, From, Event, To } ) - - self:__Assign( 1 ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_ACCEPT self - -- @param Wrapper.Unit#UNIT ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, From, Event, To ) - env.info( "in here" ) - self:E( { ProcessUnit, From, Event, To } ) - - local ProcessGroup = ProcessUnit:GetGroup() - - self:Message( "You are assigned to the task " .. self.Task:GetName() ) - - self.Task:Assign( ProcessUnit, self.Task ) - end - -end -- ACT_ASSIGN_ACCEPT - - -do -- ACT_ASSIGN_MENU_ACCEPT - - --- ACT_ASSIGN_MENU_ACCEPT class - -- @type ACT_ASSIGN_MENU_ACCEPT - -- @field Tasking.Task#TASK Task - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIGN - ACT_ASSIGN_MENU_ACCEPT = { - ClassName = "ACT_ASSIGN_MENU_ACCEPT", - } - - --- Init. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskName - -- @param #string TaskBriefing - -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:New( TaskName, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT - - self.TaskName = TaskName - self.TaskBriefing = TaskBriefing - - return self - end - - function ACT_ASSIGN_MENU_ACCEPT:Init( FsmAssign ) - - self.TaskName = FsmAssign.TaskName - self.TaskBriefing = FsmAssign.TaskBriefing - end - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param #string TaskName - -- @param #string TaskBriefing - -- @return #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:Init( TaskName, TaskBriefing ) - - self.TaskBriefing = TaskBriefing - self.TaskName = TaskName - - return self - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit, From, Event, To } ) - - self:Message( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled." ) - - local ProcessGroup = ProcessUnit:GetGroup() - - self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.TaskName .. " acceptance" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.TaskName, self.Menu, self.MenuAssign, self ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.TaskName, self.Menu, self.MenuReject, self ) - end - - --- Menu function. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuAssign() - self:E( ) - - self:__Assign( 1 ) - end - - --- Menu function. - -- @param #ACT_ASSIGN_MENU_ACCEPT self - function ACT_ASSIGN_MENU_ACCEPT:MenuReject() - self:E( ) - - self:__Reject( 1 ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit.UnitNameFrom, Event, To } ) - - self.Menu:Remove() - end - - --- StateMachine callback function - -- @param #ACT_ASSIGN_MENU_ACCEPT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit.UnitName, From, Event, To } ) - - self.Menu:Remove() - --TODO: need to resolve this problem ... it has to do with the events ... - --self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event - ProcessUnit:Destroy() - end - -end -- ACT_ASSIGN_MENU_ACCEPT ---- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. --- --- === --- --- # @{#ACT_ROUTE} FSM class, extends @{Fsm#FSM_PROCESS} --- --- ## ACT_ROUTE state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ROUTE **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. The process will go into the Report state. --- * **Report**: The process is reporting to the player the route to be followed. --- * **Route**: The process is routing the controllable. --- * **Pause**: The process is pausing the route of the controllable. --- * **Arrive**: The controllable has arrived at a route point. --- * **More**: There are more route points that need to be followed. The process will go back into the Report state. --- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state. --- --- ### ACT_ROUTE **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ROUTE **States**: --- --- * **None**: The controllable did not receive route commands. --- * **Arrived (*)**: The controllable has arrived at a route point. --- * **Aborted (*)**: The controllable has aborted the route path. --- * **Routing**: The controllable is understay to the route point. --- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around. --- * **Success (*)**: All route points were reached. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ROUTE state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Fsm.Route#ACT_ROUTE} --- --- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Controllable} player @{Unit} to a @{Zone}. --- The player receives on perioding times messages with the coordinates of the route to follow. --- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended. --- --- # 1.1) ACT_ROUTE_ZONE constructor: --- --- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object. --- --- === --- --- @module Route - - -do -- ACT_ROUTE - - --- ACT_ROUTE class - -- @type ACT_ROUTE - -- @field Tasking.Task#TASK TASK - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE Zone - -- @extends Core.Fsm#FSM_PROCESS - ACT_ROUTE = { - ClassName = "ACT_ROUTE", - } - - - --- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE. - -- @param #ACT_ROUTE self - -- @return #ACT_ROUTE self - function ACT_ROUTE:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "None", "Start", "Routing" ) - self:AddTransition( "*", "Report", "Reporting" ) - self:AddTransition( "*", "Route", "Routing" ) - self:AddTransition( "Routing", "Pause", "Pausing" ) - self:AddTransition( "*", "Abort", "Aborted" ) - self:AddTransition( "Routing", "Arrive", "Arrived" ) - self:AddTransition( "Arrived", "Success", "Success" ) - self:AddTransition( "*", "Fail", "Failed" ) - self:AddTransition( "", "", "" ) - self:AddTransition( "", "", "" ) - - self:AddEndState( "Arrived" ) - self:AddEndState( "Failed" ) - - self:SetStartState( "None" ) - - return self - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE:onafterStart( ProcessUnit, From, Event, To ) - - - self:__Route( 1 ) - end - - --- Check if the controllable has arrived. - -- @param #ACT_ROUTE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @return #boolean - function ACT_ROUTE:onfuncHasArrived( ProcessUnit ) - return false - end - - --- StateMachine callback function - -- @param #ACT_ROUTE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To ) - self:F( { "BeforeRoute 1", self.DisplayCount, self.DisplayInterval } ) - - if ProcessUnit:IsAlive() then - self:F( "BeforeRoute 2" ) - local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic - if self.DisplayCount >= self.DisplayInterval then - self:T( { HasArrived = HasArrived } ) - if not HasArrived then - self:Report() - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - self:T( { DisplayCount = self.DisplayCount } ) - - if HasArrived then - self:__Arrive( 1 ) - else - self:__Route( 1 ) - end - - return HasArrived -- if false, then the event will not be executed... - end - - return false - - end - -end -- ACT_ROUTE - - -do -- ACT_ROUTE_POINT - - --- ACT_ROUTE_POINT class - -- @type ACT_ROUTE_POINT - -- @field Tasking.Task#TASK TASK - -- @extends #ACT_ROUTE - ACT_ROUTE_POINT = { - ClassName = "ACT_ROUTE_POINT", - } - - - --- Creates a new routing state machine. - -- The task will route a controllable to a PointVec2 until the controllable is within the Range. - -- @param #ACT_ROUTE_POINT self - -- @param Core.Point#POINT_VEC2 The PointVec2 to Target. - -- @param #number Range The Distance to Target. - -- @param Core.Zone#ZONE_BASE Zone - function ACT_ROUTE_POINT:New( PointVec2, Range ) - local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_POINT - - self.PointVec2 = PointVec2 - self.Range = Range or 0 - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - - return self - end - - function ACT_ROUTE_POINT:Init( FsmRoute ) - - self.PointVec2 = FsmRoute.PointVec2 - self.Range = FsmRoute.Range or 0 - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - end - - --- Set PointVec2 - -- @param #ACT_ROUTE_POINT self - -- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 to route to. - function ACT_ROUTE_POINT:SetPointVec2( PointVec2 ) - self:F2( { PointVec2 } ) - self.PointVec2 = PointVec2 - end - - --- Get PointVec2 - -- @param #ACT_ROUTE_POINT self - -- @return Core.Point#POINT_VEC2 PointVec2 The PointVec2 to route to. - function ACT_ROUTE_POINT:GetPointVec2() - self:F2( { self.PointVec2 } ) - return self.PointVec2 - end - - --- Set Range around PointVec2 - -- @param #ACT_ROUTE_POINT self - -- @param #number Range The Range to consider the arrival. Default is 10000 meters. - function ACT_ROUTE_POINT:SetRange( Range ) - self:F2( { self.Range } ) - self.Range = Range or 10000 - end - - --- Get Range around PointVec2 - -- @param #ACT_ROUTE_POINT self - -- @return #number The Range to consider the arrival. Default is 10000 meters. - function ACT_ROUTE_POINT:GetRange() - return self.Range - end - - --- Method override to check if the controllable has arrived. - -- @param #ACT_ROUTE_POINT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @return #boolean - function ACT_ROUTE_POINT:onfuncHasArrived( ProcessUnit ) - - local Distance = self.PointVec2:Get2DDistance( ProcessUnit:GetPointVec2() ) - - if Distance <= self.Range then - local RouteText = "You have arrived." - self:Message( RouteText ) - return true - end - - return false - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE_POINT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE_POINT:onenterReporting( ProcessUnit, From, Event, To ) - - local TaskUnitPointVec2 = ProcessUnit:GetPointVec2() - local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( self.PointVec2 ) .. " km." - self:Message( RouteText ) - end - -end -- ACT_ROUTE_POINT - - -do -- ACT_ROUTE_ZONE - - --- ACT_ROUTE_ZONE class - -- @type ACT_ROUTE_ZONE - -- @field Tasking.Task#TASK TASK - -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE Zone - -- @extends #ACT_ROUTE - ACT_ROUTE_ZONE = { - ClassName = "ACT_ROUTE_ZONE", - } - - - --- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE. - -- @param #ACT_ROUTE_ZONE self - -- @param Core.Zone#ZONE_BASE Zone - function ACT_ROUTE_ZONE:New( Zone ) - local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE - - self.Zone = Zone - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - - return self - end - - function ACT_ROUTE_ZONE:Init( FsmRoute ) - - self.Zone = FsmRoute.Zone - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - end - - --- Set Zone - -- @param #ACT_ROUTE_ZONE self - -- @param Core.Zone#ZONE_BASE Zone The Zone object where to route to. - function ACT_ROUTE_ZONE:SetZone( Zone ) - self.Zone = Zone - end - - --- Get Zone - -- @param #ACT_ROUTE_ZONE self - -- @return Core.Zone#ZONE_BASE Zone The Zone object where to route to. - function ACT_ROUTE_ZONE:GetZone() - return self.Zone - end - - --- Method override to check if the controllable has arrived. - -- @param #ACT_ROUTE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @return #boolean - function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit ) - - if ProcessUnit:IsInZone( self.Zone ) then - local RouteText = "You have arrived within the zone." - self:Message( RouteText ) - end - - return ProcessUnit:IsInZone( self.Zone ) - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ROUTE_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ROUTE_ZONE:onenterReporting( ProcessUnit, From, Event, To ) - - local ZoneVec2 = self.Zone:GetVec2() - local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) - local TaskUnitVec2 = ProcessUnit:GetVec2() - local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) - local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km." - self:Message( RouteText ) - end - -end -- ACT_ROUTE_ZONE ---- (SP) (MP) (FSM) Account for (Detect, count and report) DCS events occuring on DCS objects (units). --- --- === --- --- # @{#ACT_ACCOUNT} FSM class, extends @{Fsm#FSM_PROCESS} --- --- ## ACT_ACCOUNT state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ACCOUNT **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. The process will go into the Report state. --- * **Event**: A relevant event has occured that needs to be accounted for. The process will go into the Account state. --- * **Report**: The process is reporting to the player the accounting status of the DCS events. --- * **More**: There are more DCS events that need to be accounted for. The process will go back into the Report state. --- * **NoMore**: There are no more DCS events that need to be accounted for. The process will go into the Success state. --- --- ### ACT_ACCOUNT **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ACCOUNT **States**: --- --- * **Assigned**: The player is assigned to the task. This is the initialization state for the process. --- * **Waiting**: the process is waiting for a DCS event to occur within the simulator. This state is set automatically. --- * **Report**: The process is Reporting to the players in the group of the unit. This state is set automatically every 30 seconds. --- * **Account**: The relevant DCS event has occurred, and is accounted for. --- * **Success (*)**: All DCS events were accounted for. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ACCOUNT state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- # 1) @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Fsm.Account#ACT_ACCOUNT} --- --- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units. --- The process is given a @{Set} of units that will be tracked upon successful destruction. --- The process will end after each target has been successfully destroyed. --- Each successful dead will trigger an Account state transition that can be scored, modified or administered. --- --- --- ## ACT_ACCOUNT_DEADS constructor: --- --- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object. --- --- === --- --- @module Account - - -do -- ACT_ACCOUNT - - --- ACT_ACCOUNT class - -- @type ACT_ACCOUNT - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Core.Fsm#FSM_PROCESS - ACT_ACCOUNT = { - ClassName = "ACT_ACCOUNT", - TargetSetUnit = nil, - } - - --- Creates a new DESTROY process. - -- @param #ACT_ACCOUNT self - -- @return #ACT_ACCOUNT - function ACT_ACCOUNT:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "Assigned", "Start", "Waiting") - self:AddTransition( "*", "Wait", "Waiting") - self:AddTransition( "*", "Report", "Report") - self:AddTransition( "*", "Event", "Account") - self:AddTransition( "Account", "More", "Wait") - self:AddTransition( "Account", "NoMore", "Accounted") - self:AddTransition( "*", "Fail", "Failed") - - self:AddEndState( "Accounted" ) - self:AddEndState( "Failed" ) - - self:SetStartState( "Assigned" ) - - return self - end - - --- Process Events - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onafterStart( ProcessUnit, From, Event, To ) - - self:HandleEvent( EVENTS.Dead, self.onfuncEventDead ) - - self:__Wait( 1 ) - end - - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onenterWaiting( ProcessUnit, From, Event, To ) - - if self.DisplayCount >= self.DisplayInterval then - self:Report() - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT:onafterEvent( ProcessUnit, From, Event, To, Event ) - - self:__NoMore( 1 ) - end - -end -- ACT_ACCOUNT - -do -- ACT_ACCOUNT_DEADS - - --- ACT_ACCOUNT_DEADS class - -- @type ACT_ACCOUNT_DEADS - -- @field Set#SET_UNIT TargetSetUnit - -- @extends #ACT_ACCOUNT - ACT_ACCOUNT_DEADS = { - ClassName = "ACT_ACCOUNT_DEADS", - TargetSetUnit = nil, - } - - - --- Creates a new DESTROY process. - -- @param #ACT_ACCOUNT_DEADS self - -- @param Set#SET_UNIT TargetSetUnit - -- @param #string TaskName - function ACT_ACCOUNT_DEADS:New( TargetSetUnit, TaskName ) - -- Inherits from BASE - local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS - - self.TargetSetUnit = TargetSetUnit - self.TaskName = TaskName - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - return self - end - - function ACT_ACCOUNT_DEADS:Init( FsmAccount ) - - self.TargetSetUnit = FsmAccount.TargetSetUnit - self.TaskName = FsmAccount.TaskName - end - - --- Process Events - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, From, Event, To ) - self:E( { ProcessUnit, From, Event, To } ) - - self:Message( "Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." ) - end - - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT_DEADS:onenterAccount( ProcessUnit, From, Event, To, EventData ) - self:T( { ProcessUnit, EventData, From, Event, To } ) - - self:T({self.Controllable}) - - self.TargetSetUnit:Flush() - - if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then - local TaskGroup = ProcessUnit:GetGroup() - self:Message( "You hit a target. Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." ) - end - end - - --- StateMachine callback function - -- @param #ACT_ACCOUNT_DEADS self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, From, Event, To, EventData ) - - if self.TargetSetUnit:Count() > 1 then - self:__More( 1 ) - else - self:__NoMore( 1 ) - end - end - - --- DCS Events - - --- @param #ACT_ACCOUNT_DEADS self - -- @param Event#EVENTDATA EventData - function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) - self:T( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - self:Event( EventData ) - end - end - -end -- ACT_ACCOUNT DEADS ---- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. --- --- === --- --- # @{#ACT_ASSIST} FSM class, extends @{Fsm#FSM_PROCESS} --- --- ## ACT_ASSIST state machine: --- --- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. --- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. --- Each derived class follows exactly the same process, using the same events and following the same state transitions, --- but will have **different implementation behaviour** upon each event or state transition. --- --- ### ACT_ASSIST **Events**: --- --- These are the events defined in this class: --- --- * **Start**: The process is started. --- * **Next**: The process is smoking the targets in the given zone. --- --- ### ACT_ASSIST **Event methods**: --- --- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. --- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: --- --- * **Immediate**: The event method has exactly the name of the event. --- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. --- --- ### ACT_ASSIST **States**: --- --- * **None**: The controllable did not receive route commands. --- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone. --- * **Smoking (*)**: The process is smoking the targets in the zone. --- * **Failed (*)**: The process has failed. --- --- (*) End states of the process. --- --- ### ACT_ASSIST state transition methods: --- --- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. --- There are 2 moments when state transition methods will be called by the state machine: --- --- * **Before** the state transition. --- The state transition method needs to start with the name **OnBefore + the name of the state**. --- If the state transition method returns false, then the processing of the state transition will not be done! --- If you want to change the behaviour of the AIControllable at this event, return false, --- but then you'll need to specify your own logic using the AIControllable! --- --- * **After** the state transition. --- The state transition method needs to start with the name **OnAfter + the name of the state**. --- These state transition methods need to provide a return value, which is specified at the function description. --- --- === --- --- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Fsm.Route#ACT_ASSIST} --- --- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}. --- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour. --- At random intervals, a new target is smoked. --- --- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor: --- --- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object. --- --- === --- --- @module Smoke - -do -- ACT_ASSIST - - --- ACT_ASSIST class - -- @type ACT_ASSIST - -- @extends Core.Fsm#FSM_PROCESS - ACT_ASSIST = { - ClassName = "ACT_ASSIST", - } - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST self - -- @return #ACT_ASSIST - function ACT_ASSIST:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS - - self:AddTransition( "None", "Start", "AwaitSmoke" ) - self:AddTransition( "AwaitSmoke", "Next", "Smoking" ) - self:AddTransition( "Smoking", "Next", "AwaitSmoke" ) - self:AddTransition( "*", "Stop", "Success" ) - self:AddTransition( "*", "Fail", "Failed" ) - - self:AddEndState( "Failed" ) - self:AddEndState( "Success" ) - - self:SetStartState( "None" ) - - return self - end - - --- Task Events - - --- StateMachine callback function - -- @param #ACT_ASSIST self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST:onafterStart( ProcessUnit, From, Event, To ) - - local ProcessGroup = ProcessUnit:GetGroup() - local MissionMenu = self:GetMission():GetMenu( ProcessGroup ) - - local function MenuSmoke( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local SmokeColor = MenuParam.SmokeColor - self.SmokeColor = SmokeColor - self:__Next( 1 ) - end - - self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) - self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) - self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) - self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) - self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) - self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) - end - - --- StateMachine callback function - -- @param #ACT_ASSIST self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST:onafterStop( ProcessUnit, From, Event, To ) - - self.Menu:Remove() -- When stopped, remove the menus - end - -end - -do -- ACT_ASSIST_SMOKE_TARGETS_ZONE - - --- ACT_ASSIST_SMOKE_TARGETS_ZONE class - -- @type ACT_ASSIST_SMOKE_TARGETS_ZONE - -- @field Set#SET_UNIT TargetSetUnit - -- @field Core.Zone#ZONE_BASE TargetZone - -- @extends #ACT_ASSIST - ACT_ASSIST_SMOKE_TARGETS_ZONE = { - ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE", - } - --- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor() --- self:E("_Destructor") --- --- self.Menu:Remove() --- self:EventRemoveAll() --- end - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Set#SET_UNIT TargetSetUnit - -- @param Core.Zone#ZONE_BASE TargetZone - function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone ) - local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - return self - end - - function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke ) - - self.TargetSetUnit = FsmSmoke.TargetSetUnit - self.TargetZone = FsmSmoke.TargetZone - end - - --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Set#SET_UNIT TargetSetUnit - -- @param Core.Zone#ZONE_BASE TargetZone - -- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self - function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone ) - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - return self - end - - --- StateMachine callback function - -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self - -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit - -- @param #string Event - -- @param #string From - -- @param #string To - function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To ) - - self.TargetSetUnit:ForEachUnit( - --- @param Wrapper.Unit#UNIT SmokeUnit - function( SmokeUnit ) - if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then - SCHEDULER:New( self, - function() - if SmokeUnit:IsAlive() then - SmokeUnit:Smoke( self.SmokeColor, 150 ) - end - end, {}, math.random( 10, 60 ) - ) - end - end - ) - - end - -end--- A COMMANDCENTER is the owner of multiple missions within MOOSE. --- A COMMANDCENTER governs multiple missions, the tasking and the reporting. --- @module CommandCenter - - - ---- The REPORT class --- @type REPORT --- @extends Core.Base#BASE -REPORT = { - ClassName = "REPORT", -} - ---- Create a new REPORT. --- @param #REPORT self --- @param #string Title --- @return #REPORT -function REPORT:New( Title ) - - local self = BASE:Inherit( self, BASE:New() ) - - self.Report = {} - if Title then - self.Report[#self.Report+1] = Title - end - - return self -end - ---- Add a new line to a REPORT. --- @param #REPORT self --- @param #string Text --- @return #REPORT -function REPORT:Add( Text ) - self.Report[#self.Report+1] = Text - return self.Report[#self.Report] -end - -function REPORT:Text() - return table.concat( self.Report, "\n" ) -end - ---- The COMMANDCENTER class --- @type COMMANDCENTER --- @field Wrapper.Group#GROUP HQ --- @field Dcs.DCSCoalitionWrapper.Object#coalition CommandCenterCoalition --- @list Missions --- @extends Core.Base#BASE -COMMANDCENTER = { - ClassName = "COMMANDCENTER", - CommandCenterName = "", - CommandCenterCoalition = nil, - CommandCenterPositionable = nil, - Name = "", -} ---- The constructor takes an IDENTIFIABLE as the HQ command center. --- @param #COMMANDCENTER self --- @param Wrapper.Positionable#POSITIONABLE CommandCenterPositionable --- @param #string CommandCenterName --- @return #COMMANDCENTER -function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) - - local self = BASE:Inherit( self, BASE:New() ) - - self.CommandCenterPositionable = CommandCenterPositionable - self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName() - self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition() - - self.Missions = {} - - self:HandleEvent( EVENTS.Birth, - --- @param #COMMANDCENTER self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - if EventData.IniObjectCategory == 1 then - local EventGroup = GROUP:Find( EventData.IniDCSGroup ) - if EventGroup and self:HasGroup( EventGroup ) then - local MenuReporting = MENU_GROUP:New( EventGroup, "Reporting", self.CommandCenterMenu ) - local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Summary Report", MenuReporting, self.ReportSummary, self, EventGroup ) - local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Details Report", MenuReporting, self.ReportDetails, self, EventGroup ) - self:ReportSummary( EventGroup ) - end - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled! - Mission:JoinUnit( PlayerUnit, PlayerGroup ) - Mission:ReportDetails() - end - end - - end - ) - - -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do. - -- For these elements, it will= - -- - Set the correct menu. - -- - Assign the PlayerUnit to the Task if required. - -- - Send a message to the other players in the group that this player has joined. - self:HandleEvent( EVENTS.PlayerEnterUnit, - --- @param #COMMANDCENTER self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - local PlayerGroup = EventData.IniGroup -- The GROUP object should be filled! - Mission:JoinUnit( PlayerUnit, PlayerGroup ) - Mission:ReportDetails() - end - end - ) - - -- Handle when a player leaves a slot and goes back to spectators ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.PlayerLeaveUnit, - --- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:AbortUnit( PlayerUnit ) - end - end - ) - - -- Handle when a player leaves a slot and goes back to spectators ... - -- The PlayerUnit will be UnAssigned from the Task. - -- When there is no Unit left running the Task, the Task goes into Abort... - self:HandleEvent( EVENTS.Crash, - --- @param #TASK self - -- @param Core.Event#EVENTDATA EventData - function( self, EventData ) - local PlayerUnit = EventData.IniUnit - for MissionID, Mission in pairs( self:GetMissions() ) do - Mission:CrashUnit( PlayerUnit ) - end - end - ) - - return self -end - ---- Gets the name of the HQ command center. --- @param #COMMANDCENTER self --- @return #string -function COMMANDCENTER:GetName() - - return self.CommandCenterName -end - ---- Gets the POSITIONABLE of the HQ command center. --- @param #COMMANDCENTER self --- @return Wrapper.Positionable#POSITIONABLE -function COMMANDCENTER:GetPositionable() - return self.CommandCenterPositionable -end - ---- Get the Missions governed by the HQ command center. --- @param #COMMANDCENTER self --- @return #list -function COMMANDCENTER:GetMissions() - - return self.Missions -end - ---- Add a MISSION to be governed by the HQ command center. --- @param #COMMANDCENTER self --- @param Tasking.Mission#MISSION Mission --- @return Tasking.Mission#MISSION -function COMMANDCENTER:AddMission( Mission ) - - self.Missions[Mission] = Mission - - return Mission -end - ---- Removes a MISSION to be governed by the HQ command center. --- The given Mission is not nilified. --- @param #COMMANDCENTER self --- @param Tasking.Mission#MISSION Mission --- @return Tasking.Mission#MISSION -function COMMANDCENTER:RemoveMission( Mission ) - - self.Missions[Mission] = nil - - return Mission -end - ---- Sets the menu structure of the Missions governed by the HQ command center. --- @param #COMMANDCENTER self -function COMMANDCENTER:SetMenu() - self:F() - - self.CommandCenterMenu = self.CommandCenterMenu or MENU_COALITION:New( self.CommandCenterCoalition, "Command Center (" .. self:GetName() .. ")" ) - - local MenuTime = timer.getTime() - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:SetMenu( MenuTime ) - end - - for MissionID, Mission in pairs( self:GetMissions() ) do - local Mission = Mission -- Tasking.Mission#MISSION - Mission:RemoveMenu( MenuTime ) - end - -end - ---- Gets the commandcenter menu structure governed by the HQ command center. --- @param #COMMANDCENTER self --- @return Core.Menu#MENU_COALITION -function COMMANDCENTER:GetMenu() - self:F() - return self.CommandCenterMenu -end - ---- Checks of the COMMANDCENTER has a GROUP. --- @param #COMMANDCENTER self --- @param Wrapper.Group#GROUP --- @return #boolean -function COMMANDCENTER:HasGroup( MissionGroup ) - - local Has = false - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - if Mission:HasGroup( MissionGroup ) then - Has = true - break - end - end - - return Has -end - ---- Send a CC message to the coalition of the CC. --- @param #COMMANDCENTER self -function COMMANDCENTER:MessageToAll( Message ) - - self:GetPositionable():MessageToAll( Message, 20, self:GetName() ) - -end - ---- Send a CC message to a GROUP. --- @param #COMMANDCENTER self --- @param #string Message --- @param Wrapper.Group#GROUP TaskGroup --- @param #sring Name (optional) The name of the Group used as a prefix for the message to the Group. If not provided, there will be nothing shown. -function COMMANDCENTER:MessageToGroup( Message, TaskGroup, Name ) - - local Prefix = "@ Group" - Prefix = Prefix .. ( Name and " (" .. Name .. "): " or '' ) - Message = Prefix .. Message - self:GetPositionable():MessageToGroup( Message , 20, TaskGroup, self:GetName() ) - -end - ---- Send a CC message to the coalition of the CC. --- @param #COMMANDCENTER self -function COMMANDCENTER:MessageToCoalition( Message ) - - local CCCoalition = self:GetPositionable():GetCoalition() - --TODO: Fix coalition bug! - self:GetPositionable():MessageToCoalition( Message, 20, CCCoalition, self:GetName() ) - -end - - ---- Report the status of all MISSIONs to a GROUP. --- Each Mission is listed, with an indication how many Tasks are still to be completed. --- @param #COMMANDCENTER self -function COMMANDCENTER:ReportSummary( ReportGroup ) - self:E( ReportGroup ) - - local Report = REPORT:New() - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportOverview() ) - end - - self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) - -end - ---- Report the status of a Task to a Group. --- Report the details of a Mission, listing the Mission, and all the Task details. --- @param #COMMANDCENTER self -function COMMANDCENTER:ReportDetails( ReportGroup, Task ) - self:E( ReportGroup ) - - local Report = REPORT:New() - - for MissionID, Mission in pairs( self.Missions ) do - local Mission = Mission -- Tasking.Mission#MISSION - Report:Add( " - " .. Mission:ReportDetails() ) - end - - self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) -end - ---- A MISSION is the main owner of a Mission orchestration within MOOSE . The Mission framework orchestrates @{CLIENT}s, @{TASK}s, @{STAGE}s etc. --- A @{CLIENT} needs to be registered within the @{MISSION} through the function @{AddClient}. A @{TASK} needs to be registered within the @{MISSION} through the function @{AddTask}. --- @module Mission - ---- The MISSION class --- @type MISSION --- @field #MISSION.Clients _Clients --- @field Core.Menu#MENU_COALITION MissionMenu --- @field #string MissionBriefing --- @extends Core.Fsm#FSM -MISSION = { - ClassName = "MISSION", - Name = "", - MissionStatus = "PENDING", -} - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param #MISSION self --- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter --- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. --- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param Dcs.DCSCoalitionWrapper.Object#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... --- @return #MISSION self -function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) - - local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM - - self:SetStartState( "Idle" ) - - self:AddTransition( "Idle", "Start", "Ongoing" ) - - --- OnLeave Transition Handler for State Idle. - -- @function [parent=#MISSION] OnLeaveIdle - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Idle. - -- @function [parent=#MISSION] OnEnterIdle - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnLeave Transition Handler for State Ongoing. - -- @function [parent=#MISSION] OnLeaveOngoing - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Ongoing. - -- @function [parent=#MISSION] OnEnterOngoing - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Start. - -- @function [parent=#MISSION] OnBeforeStart - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Start. - -- @function [parent=#MISSION] OnAfterStart - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Start. - -- @function [parent=#MISSION] Start - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Start. - -- @function [parent=#MISSION] __Start - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Ongoing", "Stop", "Idle" ) - - --- OnLeave Transition Handler for State Idle. - -- @function [parent=#MISSION] OnLeaveIdle - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Idle. - -- @function [parent=#MISSION] OnEnterIdle - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Stop. - -- @function [parent=#MISSION] OnBeforeStop - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Stop. - -- @function [parent=#MISSION] OnAfterStop - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Stop. - -- @function [parent=#MISSION] Stop - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Stop. - -- @function [parent=#MISSION] __Stop - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Ongoing", "Complete", "Completed" ) - - --- OnLeave Transition Handler for State Completed. - -- @function [parent=#MISSION] OnLeaveCompleted - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Completed. - -- @function [parent=#MISSION] OnEnterCompleted - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Complete. - -- @function [parent=#MISSION] OnBeforeComplete - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Complete. - -- @function [parent=#MISSION] OnAfterComplete - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Complete. - -- @function [parent=#MISSION] Complete - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Complete. - -- @function [parent=#MISSION] __Complete - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Fail", "Failed" ) - - --- OnLeave Transition Handler for State Failed. - -- @function [parent=#MISSION] OnLeaveFailed - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnEnter Transition Handler for State Failed. - -- @function [parent=#MISSION] OnEnterFailed - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- OnBefore Transition Handler for Event Fail. - -- @function [parent=#MISSION] OnBeforeFail - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fail. - -- @function [parent=#MISSION] OnAfterFail - -- @param #MISSION self - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fail. - -- @function [parent=#MISSION] Fail - -- @param #MISSION self - - --- Asynchronous Event Trigger for Event Fail. - -- @function [parent=#MISSION] __Fail - -- @param #MISSION self - -- @param #number Delay The delay in seconds. - - self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - - self.CommandCenter = CommandCenter - CommandCenter:AddMission( self ) - - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - - self.Tasks = {} - - -- Private implementations - - - - return self -end - --- FSM function for a MISSION --- @param #MISSION self --- @param #string From --- @param #string Event --- @param #string To -function MISSION:onbeforeComplete( From, Event, To ) - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if not Task:IsStateSuccess() and not Task:IsStateFailed() and not Task:IsStateAborted() and not Task:IsStateCancelled() then - return false -- Mission cannot be completed. Other Tasks are still active. - end - end - return true -- Allow Mission completion. -end - --- FSM function for a MISSION --- @param #MISSION self --- @param #string From --- @param #string Event --- @param #string To -function MISSION:onenterCompleted( From, Event, To ) - - self:GetCommandCenter():MessageToCoalition( "Mission " .. self:GetName() .. " has been completed! Good job guys!" ) -end - ---- Gets the mission name. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetName() - return self.Name -end - ---- Add a Unit to join the Mission. --- For each Task within the Mission, the Unit is joined with the Task. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. --- @return #boolean true if Unit is part of a Task in the Mission. -function MISSION:JoinUnit( PlayerUnit, PlayerGroup ) - self:F( { PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) - - local PlayerUnitAdded = false - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:JoinUnit( PlayerUnit, PlayerGroup ) then - PlayerUnitAdded = true - end - end - - return PlayerUnitAdded -end - ---- Aborts a PlayerUnit from the Mission. --- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @return #boolean true if Unit is part of a Task in the Mission. -function MISSION:AbortUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - local PlayerUnitRemoved = false - - for TaskID, Task in pairs( self:GetTasks() ) do - if Task:AbortUnit( PlayerUnit ) then - PlayerUnitRemoved = true - end - end - - return PlayerUnitRemoved -end - ---- Handles a crash of a PlayerUnit from the Mission. --- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. --- If the Unit was not part of a Task in the Mission, false is returned. --- If the Unit is part of a Task in the Mission, true is returned. --- @param #MISSION self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player crashing. --- @return #boolean true if Unit is part of a Task in the Mission. -function MISSION:CrashUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - local PlayerUnitRemoved = false - - for TaskID, Task in pairs( self:GetTasks() ) do - if Task:CrashUnit( PlayerUnit ) then - PlayerUnitRemoved = true - end - end - - return PlayerUnitRemoved -end - ---- Add a scoring to the mission. --- @param #MISSION self --- @return #MISSION self -function MISSION:AddScoring( Scoring ) - self.Scoring = Scoring - return self -end - ---- Get the scoring object of a mission. --- @param #MISSION self --- @return #SCORING Scoring -function MISSION:GetScoring() - return self.Scoring -end - ---- Get the groups for which TASKS are given in the mission --- @param #MISSION self --- @return Core.Set#SET_GROUP -function MISSION:GetGroups() - - local SetGroup = SET_GROUP:New() - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - local GroupSet = Task:GetGroups() - GroupSet:ForEachGroup( - function( TaskGroup ) - SetGroup:Add( TaskGroup, TaskGroup ) - end - ) - end - - return SetGroup - -end - - ---- Sets the Planned Task menu. --- @param #MISSION self --- @param #number MenuTime -function MISSION:SetMenu( MenuTime ) - self:F() - - for _, TaskData in pairs( self:GetTasks() ) do - local Task = TaskData -- Tasking.Task#TASK - Task:SetMenu( MenuTime ) - end -end - ---- Removes the Planned Task menu. --- @param #MISSION self --- @param #number MenuTime -function MISSION:RemoveMenu( MenuTime ) - self:F() - - for _, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Task:RemoveMenu( MenuTime ) - end -end - - ---- Gets the COMMANDCENTER. --- @param #MISSION self --- @return Tasking.CommandCenter#COMMANDCENTER -function MISSION:GetCommandCenter() - return self.CommandCenter -end - - ---- Removes a Task menu. --- @param #MISSION self --- @param Tasking.Task#TASK Task --- @return #MISSION self -function MISSION:RemoveTaskMenu( Task ) - - Task:RemoveMenu() -end - - ---- Gets the mission menu for the coalition. --- @param #MISSION self --- @param Wrapper.Group#GROUP TaskGroup --- @return Core.Menu#MENU_COALITION self -function MISSION:GetMenu( TaskGroup ) - - local CommandCenter = self:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu() - - local MissionName = self:GetName() - local MissionMenu = CommandCenterMenu:GetMenu( MissionName ) - - return MissionMenu -end - - ---- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param #string TaskName The Name of the @{Task} within the @{Mission}. --- @return Tasking.Task#TASK The Task --- @return #nil Returns nil if no task was found. -function MISSION:GetTask( TaskName ) - self:F( { TaskName } ) - - return self.Tasks[TaskName] -end - - ---- Register a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Task} object. --- @return Tasking.Task#TASK The task added. -function MISSION:AddTask( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName] = Task - - self:GetCommandCenter():SetMenu() - - return Task -end - ---- Removes a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Task} object. --- @return #nil The cleaned Task reference. -function MISSION:RemoveTask( Task ) - - local TaskName = Task:GetTaskName() - - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - -- Ensure everything gets garbarge collected. - self.Tasks[TaskName] = nil - Task = nil - - collectgarbage() - - self:GetCommandCenter():SetMenu() - - return nil -end - ---- Return the next @{Task} ID to be completed within the @{Mission}. --- @param #MISSION self --- @param Tasking.Task#TASK Task is the @{Task} object. --- @return Tasking.Task#TASK The task added. -function MISSION:GetNextTaskID( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 - - return self.Tasks[TaskName].n -end - ---- Is the @{Mission} **Completed**. --- @param #MISSION self --- @return #boolean -function MISSION:IsCompleted() - return self:Is( "Completed" ) -end - ---- Is the @{Mission} **Idle**. --- @param #MISSION self --- @return #boolean -function MISSION:IsIdle() - return self:Is( "Idle" ) -end - ---- Is the @{Mission} **Ongoing**. --- @param #MISSION self --- @return #boolean -function MISSION:IsOngoing() - return self:Is( "Ongoing" ) -end - ---- Is the @{Mission} **Failed**. --- @param #MISSION self --- @return #boolean -function MISSION:IsFailed() - return self:Is( "Failed" ) -end - ---- Is the @{Mission} **Hold**. --- @param #MISSION self --- @return #boolean -function MISSION:IsHold() - return self:Is( "Hold" ) -end - ---- Validates if the Mission has a Group --- @param #MISSION --- @return #boolean true if the Mission has a Group. -function MISSION:HasGroup( TaskGroup ) - local Has = false - - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:HasGroup( TaskGroup ) then - Has = true - break - end - end - - return Has -end - ---- Create a summary report of the Mission (one line). --- @param #MISSION self --- @return #string -function MISSION:ReportSummary() - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetName() - - -- Determine the status of the mission. - local Status = self:GetState() - - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - if Task:IsStateSuccess() or Task:IsStateFailed() then - else - TasksRemaining = TasksRemaining + 1 - end - end - - Report:Add( "Mission " .. Name .. " - " .. Status .. " - " .. TasksRemaining .. " tasks remaining." ) - - return Report:Text() -end - ---- Create a overview report of the Mission (multiple lines). --- @param #MISSION self --- @return #string -function MISSION:ReportOverview() - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetName() - - -- Determine the status of the mission. - local Status = self:GetState() - - Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) - - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Report:Add( "- " .. Task:ReportSummary() ) - end - - return Report:Text() -end - ---- Create a detailed report of the Mission, listing all the details of the Task. --- @param #MISSION self --- @return #string -function MISSION:ReportDetails() - - local Report = REPORT:New() - - -- List the name of the mission. - local Name = self:GetName() - - -- Determine the status of the mission. - local Status = self:GetState() - - Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) - - -- Determine how many tasks are remaining. - local TasksRemaining = 0 - for TaskID, Task in pairs( self:GetTasks() ) do - local Task = Task -- Tasking.Task#TASK - Report:Add( Task:ReportDetails() ) - end - - return Report:Text() -end - ---- Get all the TASKs from the Mission. This function is useful in GoalFunctions. --- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. --- @usage --- -- Get Tasks from the Mission. --- Tasks = Mission:GetTasks() --- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) -function MISSION:GetTasks() - self:F() - - return self.Tasks -end - - ---- This module contains the TASK class. --- --- 1) @{#TASK} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK.AddProcess}():Add a @{Process} to a task. --- * @{#TASK.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK.SetStateMachine}():Set a @{Fsm} to a task. --- * @{#TASK.RemoveStateMachine}():Remove @{Fsm} from a task. --- * @{#TASK.HasStateMachine}():Enquire if the task has a @{Fsm} --- * @{#TASK.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK}. --- * @{#TASK.UnAssignFromUnit}(): Unassign the task from a unit. --- * @{#TASK.SetTimeOut}(): Set timer in seconds before task gets cancelled if not assigned. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK class --- @type TASK --- @field Core.Scheduler#SCHEDULER TaskScheduler --- @field Tasking.Mission#MISSION Mission --- @field Core.Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @field Core.Fsm#FSM_PROCESS FsmTemplate --- @field Tasking.Mission#MISSION Mission --- @field Tasking.CommandCenter#COMMANDCENTER CommandCenter --- @extends Core.Fsm#FSM_TASK -TASK = { - ClassName = "TASK", - TaskScheduler = nil, - ProcessClasses = {}, -- The container of the Process classes that will be used to create and assign new processes for the task to ProcessUnits. - Processes = {}, -- The container of actual process objects instantiated and assigned to ProcessUnits. - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, - FsmTemplate = nil, - Mission = nil, - CommandCenter = nil, - TimeOut = 0, -} - ---- FSM PlayerAborted event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerAborted --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he went back to spectators or left the mission. --- @param #string PlayerName The name of the Player. - ---- FSM PlayerCrashed event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerCrashed --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he crashed in the mission. --- @param #string PlayerName The name of the Player. - ---- FSM PlayerDead event handler prototype for TASK. --- @function [parent=#TASK] OnAfterPlayerDead --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he died in the mission. --- @param #string PlayerName The name of the Player. - ---- FSM Fail synchronous event function for TASK. --- Use this event to Fail the Task. --- @function [parent=#TASK] Fail --- @param #TASK self - ---- FSM Fail asynchronous event function for TASK. --- Use this event to Fail the Task. --- @function [parent=#TASK] __Fail --- @param #TASK self - ---- FSM Abort synchronous event function for TASK. --- Use this event to Abort the Task. --- @function [parent=#TASK] Abort --- @param #TASK self - ---- FSM Abort asynchronous event function for TASK. --- Use this event to Abort the Task. --- @function [parent=#TASK] __Abort --- @param #TASK self - ---- FSM Success synchronous event function for TASK. --- Use this event to make the Task a Success. --- @function [parent=#TASK] Success --- @param #TASK self - ---- FSM Success asynchronous event function for TASK. --- Use this event to make the Task a Success. --- @function [parent=#TASK] __Success --- @param #TASK self - ---- FSM Cancel synchronous event function for TASK. --- Use this event to Cancel the Task. --- @function [parent=#TASK] Cancel --- @param #TASK self - ---- FSM Cancel asynchronous event function for TASK. --- Use this event to Cancel the Task. --- @function [parent=#TASK] __Cancel --- @param #TASK self - ---- FSM Replan synchronous event function for TASK. --- Use this event to Replan the Task. --- @function [parent=#TASK] Replan --- @param #TASK self - ---- FSM Replan asynchronous event function for TASK. --- Use this event to Replan the Task. --- @function [parent=#TASK] __Replan --- @param #TASK self - - ---- Instantiates a new TASK. Should never be used. Interface Class. --- @param #TASK self --- @param Tasking.Mission#MISSION Mission The mission wherein the Task is registered. --- @param Core.Set#SET_GROUP SetGroupAssign The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @return #TASK self -function TASK:New( Mission, SetGroupAssign, TaskName, TaskType ) - - local self = BASE:Inherit( self, FSM_TASK:New() ) -- Core.Fsm#FSM_TASK - - self:SetStartState( "Planned" ) - self:AddTransition( "Planned", "Assign", "Assigned" ) - self:AddTransition( "Assigned", "AssignUnit", "Assigned" ) - self:AddTransition( "Assigned", "Success", "Success" ) - self:AddTransition( "Assigned", "Fail", "Failed" ) - self:AddTransition( "Assigned", "Abort", "Aborted" ) - self:AddTransition( "Assigned", "Cancel", "Cancelled" ) - self:AddTransition( "*", "PlayerCrashed", "*" ) - self:AddTransition( "*", "PlayerAborted", "*" ) - self:AddTransition( "*", "PlayerDead", "*" ) - self:AddTransition( { "Failed", "Aborted", "Cancelled" }, "Replan", "Planned" ) - self:AddTransition( "*", "TimeOut", "Cancelled" ) - - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.CommandCenter = Mission:GetCommandCenter() - - self.SetGroup = SetGroupAssign - - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are invited for the task: " .. self.TaskName .. "." - - self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() - - - return self -end - ---- Get the Task FSM Process Template --- @param #TASK self --- @return Core.Fsm#FSM_PROCESS -function TASK:GetUnitProcess( TaskUnit ) - - if TaskUnit then - return self:GetStateMachine( TaskUnit ) - else - return self.FsmTemplate - end -end - ---- Sets the Task FSM Process Template --- @param #TASK self --- @param Core.Fsm#FSM_PROCESS -function TASK:SetUnitProcess( FsmTemplate ) - - self.FsmTemplate = FsmTemplate -end - ---- Add a PlayerUnit to join the Task. --- For each Group within the Task, the Unit is check if it can join the Task. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. --- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. --- @return #boolean true if Unit is part of the Task. -function TASK:JoinUnit( PlayerUnit, PlayerGroup ) - self:F( { PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) - - local PlayerUnitAdded = false - - local PlayerGroups = self:GetGroups() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is added to the Task. - -- If the PlayerGroup is not assigned to the Task, the menu needs to be set. In that case, the PlayerUnit will become the GroupPlayer leader. - if self:IsStatePlanned() or self:IsStateReplanned() then - self:SetMenuForGroup( PlayerGroup ) - --self:MessageToGroups( PlayerUnit:GetPlayerName() .. " is planning to join Task " .. self:GetName() ) - end - if self:IsStateAssigned() then - local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) - self:E( { IsAssignedToGroup = IsAssignedToGroup } ) - if IsAssignedToGroup then - self:AssignToUnit( PlayerUnit ) - self:MessageToGroups( PlayerUnit:GetPlayerName() .. " joined Task " .. self:GetName() ) - end - end - end - - return PlayerUnitAdded -end - ---- Abort a PlayerUnit from a Task. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. --- @return #boolean true if Unit is part of the Task. -function TASK:AbortUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - local PlayerUnitAborted = false - - local PlayerGroups = self:GetGroups() - local PlayerGroup = PlayerUnit:GetGroup() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStateAssigned() then - local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) - self:E( { IsAssignedToGroup = IsAssignedToGroup } ) - if IsAssignedToGroup then - self:UnAssignFromUnit( PlayerUnit ) - self:MessageToGroups( PlayerUnit:GetPlayerName() .. " aborted Task " .. self:GetName() ) - self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) - if #PlayerGroup:GetUnits() == 1 then - self:UnAssignFromGroup( PlayerGroup ) - PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) - self:RemoveMenuForGroup( PlayerGroup ) - end - self:Abort() - end - end - end - - return PlayerUnitAborted -end - ---- A PlayerUnit crashed in a Task. Abort the Player. --- If the Unit was not part of the Task, false is returned. --- If the Unit is part of the Task, true is returned. --- @param #TASK self --- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. --- @return #boolean true if Unit is part of the Task. -function TASK:CrashUnit( PlayerUnit ) - self:F( { PlayerUnit = PlayerUnit } ) - - local PlayerUnitCrashed = false - - local PlayerGroups = self:GetGroups() - local PlayerGroup = PlayerUnit:GetGroup() - - -- Is the PlayerGroup part of the PlayerGroups? - if PlayerGroups:IsIncludeObject( PlayerGroup ) then - - -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. - -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. - if self:IsStateAssigned() then - local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) - self:E( { IsAssignedToGroup = IsAssignedToGroup } ) - if IsAssignedToGroup then - self:UnAssignFromUnit( PlayerUnit ) - self:MessageToGroups( PlayerUnit:GetPlayerName() .. " crashed in Task " .. self:GetName() ) - self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) - if #PlayerGroup:GetUnits() == 1 then - PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) - self:RemoveMenuForGroup( PlayerGroup ) - end - self:PlayerCrashed( PlayerUnit ) - end - end - end - - return PlayerUnitCrashed -end - - - ---- Gets the Mission to where the TASK belongs. --- @param #TASK self --- @return Tasking.Mission#MISSION -function TASK:GetMission() - - return self.Mission -end - - ---- Gets the SET_GROUP assigned to the TASK. --- @param #TASK self --- @return Core.Set#SET_GROUP -function TASK:GetGroups() - return self.SetGroup -end - - - ---- Assign the @{Task} to a @{Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @return #TASK -function TASK:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - local Mission = self:GetMission() - local MissionMenu = Mission:GetMenu( TaskGroup ) - MissionMenu:RemoveSubMenus() - - --self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - self:E(PlayerName) - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end - - return self -end - ---- --- @param #TASK self --- @param Wrapper.Group#GROUP FindGroup --- @return #boolean -function TASK:HasGroup( FindGroup ) - - return self:GetGroups():IsIncludeObject( FindGroup ) - -end - ---- Assign the @{Task} to an alive @{Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local FsmTemplate = self:GetUnitProcess() - - -- Assign a new FsmUnit to TaskUnit. - local FsmUnit = self:SetStateMachine( TaskUnit, FsmTemplate:Copy( TaskUnit, self ) ) -- Core.Fsm#FSM_PROCESS - self:E({"Address FsmUnit", tostring( FsmUnit ) } ) - - FsmUnit:SetStartState( "Planned" ) - - FsmUnit:Accept() -- Each Task needs to start with an Accept event to start the flow. - - return self -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:UnAssignFromUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - self:RemoveStateMachine( TaskUnit ) - - return self -end - ---- Sets the TimeOut for the @{Task}. If @{Task} stayed planned for longer than TimeOut, it gets into Cancelled status. --- @param #TASK self --- @param #integer Timer in seconds --- @return #TASK self -function TASK:SetTimeOut ( Timer ) - self:F( Timer ) - self.TimeOut = Timer - self:__TimeOut( self.TimeOut ) - return self -end - ---- Send a message of the @{Task} to the assigned @{Group}s. --- @param #TASK self -function TASK:MessageToGroups( Message ) - self:F( { Message = Message } ) - - local Mission = self:GetMission() - local CC = Mission:GetCommandCenter() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - CC:MessageToGroup( Message, TaskGroup, TaskGroup:GetName() ) - end -end - - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK self -function TASK:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- UnAssign the @{Task} from the @{Group}s. --- @param #TASK self -function TASK:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:UnAssignFromGroup( TaskGroup ) - end -end - ---- UnAssign the @{Task} from a @{Group}. --- @param #TASK self -function TASK:UnAssignFromGroup( TaskGroup ) - self:F2( { TaskGroup } ) - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - - self:RemoveAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Wrapper.Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end -end - - - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @return #boolean -function TASK:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - self:T( { "Task is assigned to:", TaskGroup:GetName() } ) - return true - end - end - - self:T( { "Task is not assigned to:", TaskGroup:GetName() } ) - return false -end - ---- Returns if the @{Task} has still alive and assigned Units. --- @param #TASK self --- @return #boolean -function TASK:HasAliveUnits() - self:F() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsStateAssigned() then - if self:IsAssignedToGroup( TaskGroup ) then - for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do - if TaskUnit:IsAlive() then - self:T( { HasAliveUnits = true } ) - return true - end - end - end - end - end - - self:T( { HasAliveUnits = false } ) - return false -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:SetMenu( MenuTime ) - self:F() - - self.SetGroup:Flush() - for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetSet() ) do - local TaskGroup = TaskGroupData -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() and TaskGroup:GetPlayerNames() then - if self:IsStatePlanned() or self:IsStateReplanned() then - self:SetMenuForGroup( TaskGroup, MenuTime ) - end - end - end -end - - - ---- Set the Menu for a Group --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:SetMenuForGroup( TaskGroup, MenuTime ) - - if not TaskGroup:GetState( TaskGroup, "Assigned" ) then - self:SetPlannedMenuForGroup( TaskGroup, self:GetTaskName(), MenuTime ) - else - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup, MenuTime ) - end - end -end - - ---- Set the planned menu option of the @{Task}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @param #number MenuTime --- @return #TASK self -function TASK:SetPlannedMenuForGroup( TaskGroup, MenuText, MenuTime ) - self:E( TaskGroup:GetName() ) - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - local CommandCenter = Mission:GetCommandCenter() - local CommandCenterMenu = CommandCenter:GetMenu() - - local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ):SetTime( MenuTime ) - - - local MissionMenu = Mission:GetMenu( TaskGroup ) - - local TaskType = self:GetType() - local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskType, MissionMenu ):SetTime( MenuTime ) - local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, TaskTypeMenu, self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ):SetTime( MenuTime ):SetRemoveParent( true ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime ) - self:E( TaskGroup:GetName() ) - - local Mission = self:GetMission() - local MissionMenu = Mission:GetMenu( TaskGroup ) - - self:E( { MissionMenu = MissionMenu } ) - - local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MissionMenu, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ) - local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MissionMenu, self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ) - - return self -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK self --- @param #number MenuTime --- @return #TASK -function TASK:RemoveMenu( MenuTime ) - self:F() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - local TaskGroup = TaskGroup -- Wrapper.Group#GROUP - if TaskGroup:IsAlive() and TaskGroup:GetPlayerNames() then - if not self:IsAssignedToGroup( TaskGroup ) then - self:RemovePlannedMenuForGroup( TaskGroup, MenuTime ) - end - end - end -end - - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:RemovePlannedMenuForGroup( TaskGroup, MenuTime ) - self:F() - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - - local MissionMenu = Mission:GetMenu( TaskGroup ) - - if MissionMenu then - local TaskType = self:GetType() - local TypeMenu = MissionMenu:GetMenu( TaskType ) - - if TypeMenu then - local TaskMenu = TypeMenu:GetMenu( self:GetTaskName() ) - if TaskMenu then - TaskMenu:Remove( MenuTime ) - end - end - end - -end - ---- Remove the assigned menu option of the @{Task} for a @{Group}. --- @param #TASK self --- @param Wrapper.Group#GROUP TaskGroup --- @param #number MenuTime --- @return #TASK self -function TASK:RemoveAssignedMenuForGroup( TaskGroup ) - self:F() - - local Mission = self:GetMission() - local MissionName = Mission:GetName() - - local MissionMenu = Mission:GetMenu( TaskGroup ) - - if MissionMenu then - MissionMenu:RemoveSubMenus() - end - -end - -function TASK.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:E( "Assigned menu selected") - - self:AssignToGroup( TaskGroup ) -end - ---- Report the task status. --- @param #TASK self -function TASK:MenuTaskStatus( TaskGroup ) - - local ReportText = self:ReportDetails() - - self:T( ReportText ) - self:GetMission():GetCommandCenter():MessageToGroup( ReportText, TaskGroup ) - -end - ---- Report the task status. --- @param #TASK self -function TASK:MenuTaskAbort( TaskGroup ) - - self:Abort() -end - - - ---- Returns the @{Task} name. --- @param #TASK self --- @return #string TaskName -function TASK:GetTaskName() - return self.TaskName -end - - - - ---- Get the default or currently assigned @{Process} template with key ProcessName. --- @param #TASK self --- @param #string ProcessName --- @return Core.Fsm#FSM_PROCESS -function TASK:GetProcessTemplate( ProcessName ) - - local ProcessTemplate = self.ProcessClasses[ProcessName] - - return ProcessTemplate -end - - - --- TODO: Obscolete? ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK self --- @param #string TaskUnitName --- @return #TASK self -function TASK:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key Task@{Unit} --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @param Core.Fsm#FSM_PROCESS Fsm --- @return #TASK self -function TASK:SetStateMachine( TaskUnit, Fsm ) - self:F2( { TaskUnit, self.Fsm[TaskUnit] ~= nil, Fsm:GetClassNameAndID() } ) - - self.Fsm[TaskUnit] = Fsm - - return Fsm -end - ---- Gets the FiniteStateMachine of @{Task} with key Task@{Unit} --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return Core.Fsm#FSM_PROCESS -function TASK:GetStateMachine( TaskUnit ) - self:F2( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - return self.Fsm[TaskUnit] -end - ---- Remove FiniteStateMachines from @{Task} with key Task@{Unit} --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:RemoveStateMachine( TaskUnit ) - self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - self:E( self.Fsm ) - for TaskUnitT, Fsm in pairs( self.Fsm ) do - self:E( TaskUnitT ) - end - - self.Fsm[TaskUnit] = nil - - collectgarbage() - self:E( "Garbage Collected, Processes should be finalized now ...") -end - ---- Checks if there is a FiniteStateMachine assigned to Task@{Unit} for @{Task} --- @param #TASK self --- @param Wrapper.Unit#UNIT TaskUnit --- @return #TASK self -function TASK:HasStateMachine( TaskUnit ) - self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) - - return ( self.Fsm[TaskUnit] ~= nil ) -end - - ---- Gets the Scoring of the task --- @param #TASK self --- @return Functional.Scoring#SCORING Scoring -function TASK:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task type, the Task name. --- @param #TASK self --- @return #string The Task ID -function TASK:GetTaskIndex() - - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK self --- @param #string TaskName -function TASK:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK self --- @return #string The Task Name -function TASK:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK self --- @param #string TaskType -function TASK:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK self --- @return #string TaskType -function TASK:GetType() - return self.TaskType -end - ---- Sets the ID of the Task --- @param #TASK self --- @param #string TaskID -function TASK:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK self --- @return #string TaskID -function TASK:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK self -function TASK:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK self -function TASK:IsStateSuccess() - return self:Is( "Success" ) -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK self -function TASK:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK self -function TASK:IsStateFailed() - return self:Is( "Failed" ) -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK self -function TASK:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK self -function TASK:IsStatePlanned() - return self:Is( "Planned" ) -end - ---- Sets a @{Task} to status **Aborted**. --- @param #TASK self -function TASK:StateAborted() - self:SetState( self, "State", "Aborted" ) - return self -end - ---- Is the @{Task} status **Aborted**. --- @param #TASK self -function TASK:IsStateAborted() - return self:Is( "Aborted" ) -end - ---- Sets a @{Task} to status **Cancelled**. --- @param #TASK self -function TASK:StateCancelled() - self:SetState( self, "State", "Cancelled" ) - return self -end - ---- Is the @{Task} status **Cancelled**. --- @param #TASK self -function TASK:IsStateCancelled() - return self:Is( "Cancelled" ) -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK self -function TASK:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK self -function TASK:IsStateAssigned() - return self:Is( "Assigned" ) -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK self -function TASK:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK self -function TASK:IsStateHold() - return self:Is( "Hold" ) -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK self -function TASK:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK self -function TASK:IsStateReplanned() - return self:Is( "Replanned" ) -end - ---- Gets the @{Task} status. --- @param #TASK self -function TASK:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK self --- @param #string TaskBriefing --- @return #TASK self -function TASK:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterAssigned( From, Event, To ) - - self:E("Task Assigned") - - self:MessageToGroups( "Task " .. self:GetName() .. " has been assigned to your group." ) - self:GetMission():__Start( 1 ) -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterSuccess( From, Event, To ) - - self:E( "Task Success" ) - - self:MessageToGroups( "Task " .. self:GetName() .. " is successful! Good job!" ) - self:UnAssignFromGroups() - - self:GetMission():__Complete( 1 ) - -end - - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterAborted( From, Event, To ) - - self:E( "Task Aborted" ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been aborted! Task may be replanned." ) - - self:UnAssignFromGroups() - - self:__Replan( 5 ) -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onafterReplan( From, Event, To ) - - self:E( "Task Replanned" ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Replanning Task " .. self:GetName() .. "." ) - - self:SetMenu() - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string From --- @param #string Event --- @param #string To -function TASK:onenterFailed( From, Event, To ) - - self:E( "Task Failed" ) - - self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has failed!" ) - - self:UnAssignFromGroups() -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onstatechange( From, Event, To ) - - if self:IsTrace() then - MESSAGE:New( "@ Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() - end - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - self:E( { self.Scores[To].ScoreText, self.Scores[To].Score } ) - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onenterPlanned( From, Event, To) - if not self.TimeOut == 0 then - self.__TimeOut( self.TimeOut ) - end -end - ---- FSM function for a TASK --- @param #TASK self --- @param #string Event --- @param #string From --- @param #string To -function TASK:onbeforeTimeOut( From, Event, To ) - if From == "Planned" then - self:RemoveMenu() - return true - end - return false -end - -do -- Reporting - ---- Create a summary report of the Task. --- List the Task Name and Status --- @param #TASK self --- @return #string -function TASK:ReportSummary() - - local Report = REPORT:New() - - -- List the name of the Task. - local Name = self:GetName() - - -- Determine the status of the Task. - local State = self:GetState() - - Report:Add( "Task " .. Name .. " - State '" .. State ) - - return Report:Text() -end - - ---- Create a detailed report of the Task. --- List the Task Status, and the Players assigned to the Task. --- @param #TASK self --- @return #string -function TASK:ReportDetails() - - local Report = REPORT:New() - - -- List the name of the Task. - local Name = self:GetName() - - -- Determine the status of the Task. - local State = self:GetState() - - -- Loop each Unit active in the Task, and find Player Names. - local PlayerNames = {} - local PlayerReport = REPORT:New( " - Players:" ) - for PlayerGroupID, PlayerGroupData in pairs( self:GetGroups():GetSet() ) do - local PlayerGroup = PlayerGroupData -- Wrapper.Group#GROUP - PlayerNames = PlayerGroup:GetPlayerNames() - if PlayerNames then - PlayerReport:Add( " -- Group " .. PlayerGroup:GetCallsign() .. ": " .. table.concat( PlayerNames, ", " ) ) - end - end - - -- Loop each Process in the Task, and find Reporting Details. - Report:Add( string.format( " - Task %s\n -- State '%s'\n%s", Name, State, PlayerReport:Text() ) ) - return Report:Text() -end - - -end -- Reporting ---- This module contains the DETECTION_MANAGER class and derived classes. --- --- === --- --- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} --- ==================================================================== --- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. --- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. --- --- 1.1) DETECTION_MANAGER constructor: --- ----------------------------------- --- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. --- --- 1.2) DETECTION_MANAGER reporting: --- --------------------------------- --- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. --- --- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. --- --- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). --- --- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. --- --- === --- --- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} --- ========================================================================================= --- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. --- --- 2.1) DETECTION_REPORTING constructor: --- ------------------------------- --- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. --- --- --- === --- --- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module DetectionManager - -do -- DETECTION MANAGER - - --- DETECTION_MANAGER class. - -- @type DETECTION_MANAGER - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @extends Base#BASE - DETECTION_MANAGER = { - ClassName = "DETECTION_MANAGER", - SetGroup = nil, - Detection = nil, - } - - --- FAC constructor. - -- @param #DETECTION_MANAGER self - -- @param Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:New( SetGroup, Detection ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Functional.Detection#DETECTION_MANAGER - - self.SetGroup = SetGroup - self.Detection = Detection - - self:SetReportInterval( 30 ) - self:SetReportDisplayTime( 25 ) - - Detection:__Start( 5 ) - - return self - end - - --- Set the reporting time interval. - -- @param #DETECTION_MANAGER self - -- @param #number ReportInterval The interval in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetReportInterval( ReportInterval ) - self:F2() - - self._ReportInterval = ReportInterval - end - - - --- Set the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime ) - self:F2() - - self._ReportDisplayTime = ReportDisplayTime - end - - --- Get the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. - function DETECTION_MANAGER:GetReportDisplayTime() - self:F2() - - return self._ReportDisplayTime - end - - - - --- Reports the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_MANAGER self - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:ReportDetected( Detection ) - self:F2() - - end - - --- Schedule the FAC reporting. - -- @param #DETECTION_MANAGER self - -- @param #number DelayTime The delay in seconds to wait the reporting. - -- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:Schedule( DelayTime, ReportInterval ) - self:F2() - - self._ScheduleDelayTime = DelayTime - - self:SetReportInterval( ReportInterval ) - - self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "DetectionManager" }, self._ScheduleDelayTime, self._ReportInterval ) - return self - end - - --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. - -- @param #DETECTION_MANAGER self - function DETECTION_MANAGER:_FacScheduler( SchedulerName ) - self:F2( { SchedulerName } ) - - return self:ProcessDetected( self.Detection ) - --- self.SetGroup:ForEachGroup( --- --- @param Wrapper.Group#GROUP Group --- function( Group ) --- if Group:IsAlive() then --- return self:ProcessDetected( self.Detection ) --- end --- end --- ) - --- return true - end - -end - - -do -- DETECTION_REPORTING - - --- DETECTION_REPORTING class. - -- @type DETECTION_REPORTING - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @extends #DETECTION_MANAGER - DETECTION_REPORTING = { - ClassName = "DETECTION_REPORTING", - } - - - --- DETECTION_REPORTING constructor. - -- @param #DETECTION_REPORTING self - -- @param Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_AREAS Detection - -- @return #DETECTION_REPORTING self - function DETECTION_REPORTING:New( SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING - - self:Schedule( 1, 30 ) - return self - end - - --- Creates a string of the detected items in a @{Detection}. - -- @param #DETECTION_MANAGER self - -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. - -- @return #DETECTION_MANAGER self - function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT - if DetectedUnit:IsAlive() then - local UnitType = DetectedUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) - end - - - - --- Reports the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_REPORTING self - -- @param Wrapper.Group#GROUP Group The @{Group} object to where the report needs to go. - -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. - -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. - function DETECTION_REPORTING:ProcessDetected( Group, Detection ) - self:F2( Group ) - - self:E( Group ) - local DetectedMsg = {} - for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea - DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) - end - local FACGroup = Detection:GetDetectionGroups() - FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group ) - - return true - end - -end - ---- **Tasking** - The TASK_A2G_DISPATCHER creates and manages player TASK_A2G tasks based on detected targets. --- --- === --- --- # 1) @{#TASK_A2G_DISPATCHER} class, extends @{#DETECTION_MANAGER} --- --- The @{#TASK_A2G_DISPATCHER} class implements the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of FAC (groups). --- The FAC will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. --- Find a summary below describing for which situation a task type is created: --- --- * **CAS Task**: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter. --- * **BAI Task**: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter. --- * **SEAD Task**: Is created when there are enemy ground units wihtin range of the FAC, with air search radars. --- --- Other task types will follow... --- --- 3.1) TASK_A2G_DISPATCHER constructor: --- -------------------------------------- --- The @{#TASK_A2G_DISPATCHER.New}() method creates a new TASK_A2G_DISPATCHER instance. --- --- === --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-09: Initial class and API. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module Task_A2G_Dispatcher - -do -- TASK_A2G_DISPATCHER - - --- TASK_A2G_DISPATCHER class. - -- @type TASK_A2G_DISPATCHER - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Tasking.Mission#MISSION Mission - -- @field Wrapper.Group#GROUP CommandCenter - -- @extends Tasking.DetectionManager#DETECTION_MANAGER - TASK_A2G_DISPATCHER = { - ClassName = "TASK_A2G_DISPATCHER", - Mission = nil, - CommandCenter = nil, - Detection = nil, - } - - - --- TASK_A2G_DISPATCHER constructor. - -- @param #TASK_A2G_DISPATCHER self - -- @param Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #TASK_A2G_DISPATCHER self - function TASK_A2G_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER - - self.Detection = Detection - self.CommandCenter = CommandCenter - self.Mission = Mission - - self:Schedule( 30 ) - return self - end - - - --- Creates a SEAD task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount > 0 then - - -- Here we're doing something advanced... We're copying the DetectedSet, but making a new Set only with SEADable Radar units in it. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterHasSEAD() - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a CAS task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Tasking.Task#TASK - function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem ) - - if GroundUnitCount > 0 and FriendliesNearBy == true then - - -- Copy the Set - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a BAI task when there are targets for it. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Tasking.Task#TASK - function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedItem, FriendlyCoalition ) - self:F( { DetectedItem.ItemID } ) - - local DetectedSet = DetectedItem.Set - local DetectedZone = DetectedItem.Zone - - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem ) - - if GroundUnitCount > 0 and FriendliesNearBy == false then - - -- Copy the Set - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Evaluates the removal of the Task from the Mission. - -- Can only occur when the DetectedItem is Changed AND the state of the Task is "Planned". - -- @param #TASK_A2G_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission - -- @param Tasking.Task#TASK Task - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Tasking.Task#TASK - function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedItem ) - - if Task then - if Task:IsStatePlanned() and DetectedItem.Changed == true then - self:E( "Removing Tasking: " .. Task:GetTaskName() ) - Task = Mission:RemoveTask( Task ) - end - end - - return Task - end - - - --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. - -- @param #TASK_A2G_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Detection#DETECTION_BASE} derived object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function TASK_A2G_DISPATCHER:ProcessDetected( Detection ) - self:F2() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - local ReportSEAD = REPORT:New( " - SEAD Tasks:") - local ReportCAS = REPORT:New( " - CAS Tasks:") - local ReportBAI = REPORT:New( " - BAI Tasks:") - local ReportChanges = REPORT:New( " - Changes:" ) - - --- First we need to the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Functional.Detection#DETECTION_BASE.DetectedSet - local DetectedZone = DetectedItem.Zone - self:E( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count(), tostring( DetectedItem ) } ) - DetectedSet:Flush() - - local ItemID = DetectedItem.ItemID - - -- Evaluate SEAD Tasking - local SEADTask = Mission:GetTask( string.format( "SEAD.%03d", ItemID ) ) - SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedItem ) - if not SEADTask then - local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - local Task = TASK_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", ItemID ), TargetSetUnit ) - Task:SetTargetZone( DetectedZone ) - SEADTask = Mission:AddTask( Task ) - end - end - if SEADTask and SEADTask:IsStatePlanned() then - ReportSEAD:Add( string.format( " - %s.%02d - %s", "SEAD", ItemID, Detection:DetectedItemReportSummary(DetectedItemID) ) ) - end - - -- Evaluate CAS Tasking - local CASTask = Mission:GetTask( string.format( "CAS.%03d", ItemID ) ) - CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedItem ) - if not CASTask then - local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - local Task = TASK_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", ItemID ), TargetSetUnit ) - --Task:SetTargetZone( DetectedZone ) - CASTask = Mission:AddTask( Task ) - end - end - if CASTask and CASTask:IsStatePlanned() then - ReportCAS:Add( string.format( " - %s.%02d - %s", "CAS", ItemID, Detection:DetectedItemReportSummary(DetectedItemID) ) ) - end - - -- Evaluate BAI Tasking - local BAITask = Mission:GetTask( string.format( "BAI.%03d", ItemID ) ) - BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedItem ) - if not BAITask then - local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - local Task = TASK_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", ItemID ), TargetSetUnit ) - Task:SetTargetZone( DetectedZone ) - BAITask = Mission:AddTask( Task ) - end - end - if BAITask and BAITask:IsStatePlanned() then - ReportBAI:Add( string.format( " - %s.%02d - %s", "BAI", ItemID, Detection:DetectedItemReportSummary(DetectedItemID) ) ) - end - - - -- Loop through the changes ... - local ChangeText = Detection:GetChangeText( DetectedItem ) - ReportChanges:Add( ChangeText ) - - - -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedItem ) - - end - - -- TODO set menus using the HQ coordinator - Mission:GetCommandCenter():SetMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not TaskGroup:GetState( TaskGroup, "Assigned" ) then - self.CommandCenter:MessageToGroup( - string.format( "HQ Reporting - Planned tasks for mission '%s':\n%s\n", - self.Mission:GetName(), - string.format( "%s\n%s\n%s\n%s", ReportSEAD:Text(), ReportCAS:Text(), ReportBAI:Text(), ReportChanges:Text() - ) - ), self:GetReportDisplayTime(), TaskGroup - ) - end - end - - return true - end - -end--- This module contains the TASK_A2G classes. --- --- # 1) @{Task_A2G#TASK_A2G} class, extends @{Task#TASK} --- --- The @{#TASK_A2G} class defines Air To Ground tasks for a @{Set} of Target Units, --- based on the tasking capabilities defined in @{Task#TASK}. --- The TASK_A2G is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: --- --- * **None**: Start of the process --- * **Planned**: The A2G task is planned. --- * **Assigned**: The A2G task is assigned to a @{Group#GROUP}. --- * **Success**: The A2G task is successfully completed. --- * **Failed**: The A2G task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- # 1) @{Task_A2G#TASK_SEAD} class, extends @{Task_A2G#TASK_A2G} --- --- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units. --- --- ==== --- --- # **API CHANGE HISTORY** --- --- The underlying change log documents the API changes. Please read this carefully. The following notation is used: --- --- * **Added** parts are expressed in bold type face. --- * _Removed_ parts are expressed in italic type face. --- --- Hereby the change log: --- --- 2017-03-09: Revised version. --- --- === --- --- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- * **[WingThor]**: Concept, Advice & Testing. --- --- ### Authors: --- --- * **FlightControl**: Concept, Design & Programming. --- --- @module Task_A2G - -do -- TASK_A2G - - --- The TASK_A2G class - -- @type TASK_A2G - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - TASK_A2G = { - ClassName = "TASK_A2G", - } - - --- Instantiates a new TASK_A2G. - -- @param #TASK_A2G self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_A2G self - function TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, TaskType ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) -- Tasking.Task#TASK_A2G - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TaskType = TaskType - - Mission:AddTask( self ) - - local Fsm = self:GetUnitProcess() - - - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "RouteToRendezVous", Rejected = "Reject" } ) - - Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" ) - Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } ) - Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } ) - - Fsm:AddTransition( { "Arrived", "RoutingToRendezVous" }, "ArriveAtRendezVous", "ArrivedAtRendezVous" ) - - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "Engage", "Engaging" ) - Fsm:AddTransition( { "ArrivedAtRendezVous", "HoldingAtRendezVous" }, "HoldAtRendezVous", "HoldingAtRendezVous" ) - - Fsm:AddProcess ( "Engaging", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, self.TaskType ), { Accounted = "Success" } ) - Fsm:AddTransition( "Engaging", "RouteToTarget", "Engaging" ) - Fsm:AddProcess( "Engaging", "RouteToTargetZone", ACT_ROUTE_ZONE:New(), {} ) - Fsm:AddProcess( "Engaging", "RouteToTargetPoint", ACT_ROUTE_POINT:New(), {} ) - Fsm:AddTransition( "Engaging", "RouteToTargets", "Engaging" ) - - Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) - Fsm:AddTransition( "Accounted", "Success", "Success" ) - Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToRendezVous( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.RendezVousSetUnit - - if Task:GetRendezVousZone( TaskUnit ) then - self:__RouteToRendezVousZone( 0.1 ) - else - if Task:GetRendezVousPointVec2( TaskUnit ) then - self:__RouteToRendezVousPoint( 0.1 ) - else - self:__ArriveAtRendezVous( 0.1 ) - end - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2G Task - function Fsm:OnAfterArriveAtRendezVous( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - self:__Engage( 0.1 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task#TASK_A2G Task - function Fsm:onafterEngage( TaskUnit, Task ) - self:E( { self } ) - self:__Account( 0.1 ) - self:__RouteToTarget(0.1 ) - self:__RouteToTargets( -10 ) - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToTarget( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - -- Determine the first Unit from the self.TargetSetUnit - - if Task:GetTargetZone( TaskUnit ) then - self:__RouteToTargetZone( 0.1 ) - else - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - local PointVec2 = TargetUnit:GetPointVec2() - self:T( { TargetPointVec2 = PointVec2, PointVec2:GetX(), PointVec2:GetAlt(), PointVec2:GetZ() } ) - Task:SetTargetPointVec2( TargetUnit:GetPointVec2(), TaskUnit ) - end - self:__RouteToTargetPoint( 0.1 ) - end - end - - --- Test - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_A2G#TASK_A2G Task - function Fsm:onafterRouteToTargets( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - local TargetUnit = Task.TargetSetUnit:GetFirst() -- Wrapper.Unit#UNIT - if TargetUnit then - Task:SetTargetPointVec2( TargetUnit:GetPointVec2(), TaskUnit ) - end - self:__RouteToTargets( -10 ) - end - - return self - - end - - --- @param #TASK_A2G self - function TASK_A2G:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - --- @param #TASK_A2G self - -- @param Core.Point#POINT_VEC2 RendezVousPointVec2 The PointVec2 object referencing to the 2D point where the RendezVous point is located on the map. - -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousPointVec2( RendezVousPointVec2, RendezVousRange, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteRendezVous:SetPointVec2( RendezVousPointVec2 ) - ActRouteRendezVous:SetRange( RendezVousRange ) - end - - --- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#POINT_VEC2 The PointVec2 object referencing to the 2D point where the RendezVous point is located on the map. - -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. - function TASK_A2G:GetRendezVousPointVec2( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteRendezVous:GetPointVec2(), ActRouteRendezVous:GetRange() - end - - - - --- @param #TASK_A2G self - -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteRendezVous:SetZone( RendezVousZone ) - end - - --- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. - function TASK_A2G:GetRendezVousZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteRendezVous = ProcessUnit:GetProcess( "RoutingToRendezVous", "RouteToRendezVousZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteRendezVous:GetZone() - end - - --- @param #TASK_A2G self - -- @param Core.Point#POINT_VEC2 TargetPointVec2 The PointVec2 object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetTargetPointVec2( TargetPointVec2, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - ActRouteTarget:SetPointVec2( TargetPointVec2 ) - end - - - --- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#POINT_VEC2 The PointVec2 object where the Target is located on the map. - function TASK_A2G:GetTargetPointVec2( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT - return ActRouteTarget:GetPointVec2() - end - - - --- @param #TASK_A2G self - -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. - -- @param Wrapper.Unit#UNIT TaskUnit - function TASK_A2G:SetTargetZone( TargetZone, TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - ActRouteTarget:SetZone( TargetZone ) - end - - - --- @param #TASK_A2G self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_A2G:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - -end - - -do -- TASK_SEAD - - --- The TASK_SEAD class - -- @type TASK_SEAD - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - TASK_SEAD = { - ClassName = "TASK_SEAD", - } - - --- Instantiates a new TASK_SEAD. - -- @param #TASK_SEAD self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_SEAD self - function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "SEAD" ) ) -- #TASK_SEAD - self:F() - - return self - end - -end - -do -- TASK_BAI - - --- The TASK_BAI class - -- @type TASK_BAI - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - TASK_BAI = { - ClassName = "TASK_BAI", - } - - --- Instantiates a new TASK_BAI. - -- @param #TASK_BAI self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_BAI self - function TASK_BAI:New( Mission, SetGroup, TaskName, TargetSetUnit ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "BAI" ) ) -- #TASK_BAI - self:F() - - return self - end - -end - -do -- TASK_CAS - - --- The TASK_CAS class - -- @type TASK_CAS - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Tasking.Task#TASK - TASK_CAS = { - ClassName = "TASK_CAS", - } - - --- Instantiates a new TASK_CAS. - -- @param #TASK_CAS self - -- @param Tasking.Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param #number TargetDistance The distance to Target when the Player is considered to have "arrived" at the engagement range. - -- @param Core.Zone#ZONE_BASE TargetZone The target zone, if known. - -- If the TargetZone parameter is specified, the player will be routed to the center of the zone where all the targets are assumed to be. - -- @return #TASK_CAS self - function TASK_CAS:New( Mission, SetGroup, TaskName, TargetSetUnit ) - local self = BASE:Inherit( self, TASK_A2G:New( Mission, SetGroup, TaskName, TargetSetUnit, "CAS" ) ) -- #TASK_CAS - self:F() - - return self - end - -end ---- The main include file for the MOOSE system. --- Test of permissions - ---- Core Routines -Include.File( "Utilities/Routines" ) -Include.File( "Utilities/Utils" ) - ---- Core Classes -Include.File( "Core/Base" ) -Include.File( "Core/Scheduler" ) -Include.File( "Core/ScheduleDispatcher") -Include.File( "Core/Event" ) -Include.File( "Core/Menu" ) -Include.File( "Core/Zone" ) -Include.File( "Core/Database" ) -Include.File( "Core/Set" ) -Include.File( "Core/Point" ) -Include.File( "Core/Message" ) -Include.File( "Core/Fsm" ) - ---- Wrapper Classes -Include.File( "Wrapper/Object" ) -Include.File( "Wrapper/Identifiable" ) -Include.File( "Wrapper/Positionable" ) -Include.File( "Wrapper/Controllable" ) -Include.File( "Wrapper/Group" ) -Include.File( "Wrapper/Unit" ) -Include.File( "Wrapper/Client" ) -Include.File( "Wrapper/Static" ) -Include.File( "Wrapper/Airbase" ) -Include.File( "Wrapper/Scenery" ) - ---- Functional Classes -Include.File( "Functional/Scoring" ) -Include.File( "Functional/CleanUp" ) -Include.File( "Functional/Spawn" ) -Include.File( "Functional/Movement" ) -Include.File( "Functional/Sead" ) -Include.File( "Functional/Escort" ) -Include.File( "Functional/MissileTrainer" ) -Include.File( "Functional/AirbasePolice" ) -Include.File( "Functional/Detection" ) - ---- AI Classes -Include.File( "AI/AI_Balancer" ) -Include.File( "AI/AI_Patrol" ) -Include.File( "AI/AI_Cap" ) -Include.File( "AI/AI_Cas" ) -Include.File( "AI/AI_Cargo" ) - ---- Actions -Include.File( "Actions/Act_Assign" ) -Include.File( "Actions/Act_Route" ) -Include.File( "Actions/Act_Account" ) -Include.File( "Actions/Act_Assist" ) - ---- Task Handling Classes -Include.File( "Tasking/CommandCenter" ) -Include.File( "Tasking/Mission" ) -Include.File( "Tasking/Task" ) -Include.File( "Tasking/DetectionManager" ) -Include.File( "Tasking/Task_A2G_Dispatcher") -Include.File( "Tasking/Task_A2G" ) - - --- The order of the declarations is important here. Don't touch it. - ---- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT - ---- Declare the timer dispatcher based on the SCHEDULEDISPATCHER class -_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER - ---- Declare the main database object, which is used internally by the MOOSE classes. -_DATABASE = DATABASE:New() -- Database#DATABASE +Include.ProgramPath = "Scripts/Moose/" +env.info( "Include.ProgramPath = " .. Include.ProgramPath) +Include.Files = {} +Include.File( "Moose" ) -BASE:TraceOnOff( false ) +BASE:TraceOnOff( true ) env.info( '*** MOOSE INCLUDE END *** ' ) diff --git a/Moose Presentations/TASK_DISPATCHER.pptx b/Moose Presentations/TASK_DISPATCHER.pptx index d7b02926c69dc61df17fbc8c4fb6661dc98b8852..b3ca1f566f049b1f008b4693db69438ed6440710 100644 GIT binary patch delta 1351224 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%0DJJmu(@E;Q_8EXw9REFtL_#3==kNh|w>Jt6r}28G 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

8uuVR@}ZI{ebrfYS8Tw&}3!%PyCW7 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

ixa&;ojm&E>SbrG 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ɢ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

>V$IpEqj~( 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�XTcdinuKNx$Hpx4TIi+ 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

`>Gt3fL{}rAVzw+pZ`vz)CW;S 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%0Ac6V_s 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"z`pws{a1_b z*lJ)!#;HEN7aDOiEi5b3N0WpR&u_wZA2Ca(hRmx9uzUXD$!x9;-A>*fx=^oMsjC8g zNWAEY&IO7!kL#>kD~1N{UC|%IhIJo`uYOLRWF(>?s#ns-(Ovcl<`E@FYM@Z}XCi#rBA#2W;SYpns$0=#4LX)kunN zmN<*=AJX5#B=qwO0^3>rqVpEe?XwV5ZtfH9^qDER9Z)MeR?=b)trvO-$I54o+-wLX z={Gs1Nk2M7@>;)>HXIHV+X7qDub&(jgZGi?EaLhuf)YLHDE#Xr;Ls%6?4Kd?wHK;m z?qC!z2Bi>x?4@v0R{*Ix3Gw!g6eP9w)$}WH`WFye%}V$u)ggCbGKI{AEl@7#+3Fb7 z8kRTDE4a@0DKHMc@hn)4gt9;8hO(5tCrlH^}LN(E?iXSuJRXKn!GCTc_x5*E%1xFr8L~s%-DA=K;~O~t{^x)<$hMW_0p)Ue z6KzqWCnq0T4vaXICi1JLbU$~S*i&mkF8m(N2gth49{YQL+xjm~cj9i|7jH{nF{tv9 znB}OPpA7<=mHzEoNSYMC6WXQsyS&6N;sBiJ%ut=9ew?j8b5B^3$4iFxmcBgcDM)E6 zJ?Ul-ptC>XzLj3cM)a$e2B)ln2MmY)$ih>8`W?C1zjyQU+@(+Y#z`9%wYcV(a4`J* z@5-V_pu`;NFGB@aVdKAy@;#6cBbg;gJAG%V?=SOgQLs+*6DzSE)rTfi=d_ImtF=s zA=$~}CP>#uT0m~Ss2HPW7)I6(%|9GM&ekxn?W2?G0DTmWR^($Nw$(}7u#N&p0Im1C z{18S_q`pjx)B|+{N-|+8x=bp%5s(HYHPxC_UV(CxMlB+8cHOOT`ym zpG=)iIM!0|`^3k?a%94HA;YPqy+!bgzdN|*;kxQFiN!T!**!kUG%TFQEyo9SRFHPP z!9Dqe>hrA{*^03vn7@DKXt<p5V{-* z*Q-5jeXL|Q)4BFlaHC!~ zT1_Upx8w)M>a$Hnsb0_ltz#m|Fnr2u@|=-JUU2Z~{3dy%#o!oF06ZIvgB}7TdGt=r zWOi>O$ONw12ZPmCNCkJl)~llJJb>zes?_rGe8j~(nfiq_XhMf9G2ecS6b!4$eI}cH z82iq%%^*>U(QBTAF8*cjj&~iL=w<{~&OH#w*QGxc217b1A6bb($KI&^%$me_>@U=t zEYb6MwxbtW>L!^1f^E@xO~1Icty982%}3wQ+U)1G`zE~HNg06cysL`cXpt0ZZ+%#V zaBjsxU`>P7Mv_&ALUO$3ysBw!E+*prlQs1D^iK_Ii#WSM;0Yo3x~|wGrfX%0)M7*g zHVWm?G!T{WuVCkV;E~yIAA`mv=ujF4KzvoFOgZWgYda3iX{jlBE8bEr%aEP^4%Aj& z;9jZbL%~rIriJ7M3z7wsDuOW7_6b0|(U|X3`V3Zs|Fe_1+z(oUQkh81HH%Ympgo9n zdi+!s9cktKkBz({WZn)?hpH+VRL*!`7)%DWs+SrV5CVp5gH{JUV~CQrhk7G+YQi;u zZCgy^$E_{AaX@`N^Hx)#ob@k=>fAw#8DSJ2bIzgS75Woup%J|v`To}nauf-1=FB}{ z%9tQyf$02)GB>AOvpljAX*Tj-?yjOMLK&umy1155vf&Faez-p9FwlJ2xGdqcU~)Ra z>$rYz#t4U-|9i}0WI|2mt;Hj&oi-T*!f^Zh*Luza@DU1uYmrH3a`*8Dq8wmh$dJuV z3)Hrv)S zl%F>=&MoUIqhaG(Vn6^4a}157rLcG2}#cFb2kF$CaQodOjzUXk~ z_UvOa!*o0P#d-~krC1Gbgpz;=cf`?5Js+l76qWQtp<-WIa}a2qVul1Yj2X`Jkw@hu zXp}6PijSG;CMfMEH#(jGM!w@Z|1Eg8XNbS zS)ByQqU*Zgu1ST~0|I~;fj-uv_QQjg)=8~&U1$ju5Ek4MexLXHx)y3VKb-_{hhUuI ziMJ{neK{Fx7%Kzm%z@S}`PvZt2B3e$P%#df)O@B5$1dQ=T%8QtsE8|R4^tgx=$&Cw z-F>*DQv?tqTe{JvErFTtc(gP$z7E#R8h2LyWFr4Y&V;+|v#*Ep0E9J5o4$svX5+5G z+(AI3*TWqrEGG1Npuqq<52A>g!2pKIM{!5dh=XoxDai! zZxRi%u?0I<{x{kif+8}Lb-A^g%Pkj2gADp_V7-(su_yWj!*LLQF~-+9Z3)tu^MU>) zR2o3W_*rvsW4L+XEzb3uaC8!<@T~>7@d@6AHJX-pZOEy^xs~?9gPI|n~5w#kZ4rudU#}}*L9p3_Xe3!}d zD%d=Rve%5VzB0xqc4e+SZDLTNnL}Xm%{;sv(?R|s$L__sSG=@uupP3_qvlAQKM%Xu zN~Yg>hLCE9ESG?LQc3c<2xS=`Q3{Vb2`Hqrx=XvLi$1790D=(ew3V+H+{;0iMylBV z^LgsOX7+vqEIad}8wr3+Iu58&J>Em*zWRkH8|`5E!0Q0Dxb-LWReZw&nm3lZxe7lA zVC+;-Gu8}L#fA)wD1)B;3C~u+*)uYgi8~{o0F@&(EJ8js1hq;AKV)nxT77{4X;hfi zL@=g>+JwwCYrP2`j|X5oIfmZj^<_5r>N4Q{ zSn0^xyp!{~iq?4yitx@7xe>wYTahXibQrCi%i24(hCGt-6x1-TOrn)I{qg8cv*}K? z*;cd5?oZPqRA~UZ{id6v<@xMEh zP4~{%-7NwJ=Dx0{+~v>yz>*vsTqk!-+Dmg1*TAR@T4%Wz45YR$4w|)R?KmO!W`u&j?xXoroQemg#ci%rIXj z`9m|GXM7Dx2~JOLLm%IJ$b5KMyfR{H=2MK38sk9^8me{rtJHpM3ZEaccr9WoUV>V_ z4DVZ&$UI?J3mR%-0@#-uuA0OpY~$>a#M1R3tMTJSAeze7rGg{|{c4`arlXx%M}rnS z`z}I0esx_8pOZybmldCkzK9`7fhMWnx%J_aH1NQ*NYa%C_JhV0dYkdHXH5|!&B@Wh zi`S;(1FIOQr$5e%jUh-GehI?QE}!qJ=;|Qb)`D8@0wuEouMyeVyi+MQRO7yhA5(osK~pQ5-pZxO^$CC~1^wyh{oBIXXWS}t>vPZml&M^D1X1Ai0S zT1I&8&YTR`{3@kRwWrz!Y4%Vt70hj7DyO#nks$)v*ym!EHjF^(b5O+*&0Qr+|a{!@9p06INjV8FL_wI zD&l~7*=STXHtBYrooIDf1p{v@z%yv-cnLI>y@Hq|kQ(&rB_^|vm90h6{7pAkGTXs zQ_2{@aFIXzzOtuC892mL{Z}9<;rbm>H@H1ugqq)i(&%mn^~cSkRZwg2cwS>mYhzzT zKJmA6B5|My4%@)G2DrH>4@Y6RvSV!?>w-~|n ztNWjCZ|&?*Q31ULg)87yz7~Hq1dX5qiL&nf5~}{B>rI;d;;|8siNSHU|0Ds9hF$X| zXHgJv8J)Wa+~z>6b>Tj%jA^5s_~NQQ?{#-+A1GRw1yi2{)9p@O=)F9~kAOhDfxYat%B;zwL^5pz$tK@e-gO_#!07|0tW+hi( zOrKA~9N+2@S3tn*EY9p_KOLLt>2)FXOR;59bb4)`6fn@X&GQQOnypQ1I#i#p7RU*! z9t$S*jj`={qd@y_pVmDQVv7jgsmXZ<5>Ua8SF>9}z>bpH`o~hgAwo&-DVsH^x^P9!*Vro~r0PxJx#*F)4;I@wb2?k~YZK zT@g-+B)k0gK2Q7%H&N3Ty5vjLCAd`taPBhfj8O9$Q0tJwqm@fCoks5HE93qoGg&hq z%HoI(lr6yy*z*T732xNSCA)dk+?aeTrkvnLJ0+#zW%Y+h-~Z0g zjsC9=VO4B?6XgFCpFL~(Q?Ed|Jw*eYL7>tOti(9p-{ldvep{(d)7&B<_?gnDls(Qm z%_v}In=^JPf(Qp&O@po7RLmPOQv|s=LP-apuw_Sy%iT>jX@oM z9>J(SLl)i@-xPtqccsK@P0?86Q}VR%Rh_<2$FbL-YJPDs$Wi@8&i{X#sbRJ67oJzx zyS0Gt1{yLETtt}puHKOF-kV{%WhutIMX!$o#_@IvCZ17km1a4%nN&gdaOx=kLmEfv z8O`Fu`M>6))?rEP8zMYp4ZT8_W5Qq=_J4*#@PWWFtzoxKnK2z zviawcpE_%;QJkw(x4`Svs#(%0M@+1)S; z*64^iu1J-YfHeQVoccxo_w<@oQ7DpJ@4tb%WgpBUo+$(~1khio2dfspUl50=iy_U` ztC7EavNY;RqkMV};x5=$Q1TN{9gXqz(ag|Zx;Pa4G>~eXPNn7z;WXy7v%%38^@9gH z6mvRe^nbnxwF%`os8Vq~MntvA^r7Hr;?uHhzliD$a_sN&`@gWjd?1CS4EAJ*P&>$$ zOv#rd=d!mOo`Ve^#m-40D063J6x{F*+6Fg8Qt-HAe(e$%+&3Sz*T+r968=$z&k2Ul z+*k2YVWJOU3=7I;8_urj&-6SmwQl{|2<+d5V!P3ekcfY9w=NnHCs$bFDHR!@aPX!2 zs!ESw#Mg~Ui{uDgyyP+9GuMF2CSpU}oV@t@2~2?F7(q~sB|(FGKm0`PK~IkMd&(c@ zemdLR!)zOkDJ8rJY++uT{mFQ2;s$8)nqYrKr9XSZ)>5<=j5b|!_D&0S7|t3P{XZ2_ z^`_I*qV!A7z^<^Pmq7t$Wfz#6n=dr!H|ndT=NYTAd8PAV>a9s)_q@e9<*86m6BYfn zWl(`={~tJU4`W zCKt{05t_TY)>ZRN!C(rfJjiyR%6tdTJK&Xn)AyH4RQgZc+%*YKb2uIbet9$u?EG5$-6OmD#*D$R=MZfAO2PW)M1X%>Oqi+j76l+H#668OgNa|p^kFN8%v<~ z&v*;oaWYo>pzteyUcRR77_iAo#!-5^z}xkosJQkL*T*UIZb$1OsV*&Z{dQM=Bt8(M zc%k?ml`^19NsTKzOUKOV~;$qd3XlH-R(N4YDJ0PCeMNT|r=*P~_ccm2!{cwgsygN8{DK#A5qVG#@vHAYwBE7Eh~B_7 zmv?}(;Aa5%2a5_L)l9_Z#nW9S9z69uGiUxxpT-ZI_H8HUb}cxGsX9WRH_(bGUvuWl zZ*br3V5!%VKHZgHQvg3dgmzuJF}Gz#;$Xmyz`;|zEDM@8B?+3>4~7FKbwvsm^u8(P znj!k@MWCvs6d~kYtE5#L?81GitrzLqcDVrJctuY|nN>O-t%iT<7A?lzIA&b&=B$=V zP8)KcH3`i{Z^QKiw1BRvzeY@$e)Z|C^yQZWgKykF3TV^Iet#rWPsLC+l3xx+1LFak z9TWPrI5cb6)QmI210{x^#(C{Q33QlZhBk+L-M$8734mGw4;mK>gAqf|2fh$vW@%IM zG@>Kw&MT-%xs6n>DKKZo<)UR#@-FZQ8$@Z_mS<^-=6%mYd@c3;;@m)sNpIcZnt;0L z*ZB6}?EUNv4?$`c#j?D@5gS$?kS$}TTmbtuj|z~C5+AwEpkzH;86os+j1ouB7M{nn z%X}#iThv(BCcoAI$vPy=-i-dx!a)%R)8jH()8td>voNKa?Cfo)w`5u0_t2U1*O-`Z znt&2_a0Fjj$EGcB$Bxol1j5Fd@rpMCVOKQ1mCsjitnm{jVzoiH!U^xKscvI9uT)C+Pw&<>S?6&2=CfEo0V zur`yv%E&S*IEX-L`1#$Pyc~k2V`MAh%Y*KgVJ=k{ALq{UnpggW z%Rhe)8xz0mPc_l*rGu8a3=iD^(42b9y{eB^x8H7Lq{a4IiB|A1FlYc-Ld`qs?U?!6 z?7B; zElx)CGfI)D_9-OSj%OG_4HUMUP+%ihaP-yGNBy-8o0HJ@EhVcKb6FKtI}*Ev;FN!c zF{1xGGH8|PYB&I>6ZgeNFG@Ta3{tA=Y^7YC%jUy(I=o}Po|C{2RlkYYjW<_Z9p2Xa zViL}Bx2f7m%1vk-DcOAeU?Pe?jhw%J zT@d!Q3#vpH&%fUsquHK}?>R&g&B}{oxtuXXW=cU=M98h}l>g2f%Najlz}G8$|JgV5 zYFyP2+2MEVGNLly$4BNClhs);>Y5Xtw*&F86bUZQJ_flot4|M$j~CA?SvC;U*Y4WI zwpvFcwgyWofU2n`#Y2W=BHu51ts0{f5Nhb|ZiLoIJ`!B|bs9?R7`V}q8D&AQRiN&$ z*qYb`3uwyeD>lIF&?hohmnASJ_xsNF83(+nBqyB zH4_V?ob4&g;A4R@%v?ly#9{3Tg$` zM(~(p)$R5Wuw_rBeOEs+T36pyRm}hQYrhAIvWOBOZ0tKmSk{=f=-Qqi%h7_?1%aYS z-~J?DQu0eK3v^WQ{0>;UMO7zxL*7#bHYA0Ugy>|8+U$l^g!wOX>^_q_;w78$SeKYm zxRjR&?y^PnHG|j%W{enYP72gHIL#XL5@n^A}bJY zB5cd(;0P7F!1n&GFBDQUI|}BodtJQ%e<&-Q=IWkYT;%qmhMsSn1KkdKABfCSOR0%p zrOlq#t;aMvO*kG!$NzccJuh%p^=;VsC2rK;pr$&k+esh1!As&<@VI6n=B-In`?q23 z)7q4Afo_7M0{idi_;<60w+kb@OOX9|TZ&;M03i#MWN7u`(uX+U_6LtbGUdaTVOe(aH0w`Mg%^qAYc=nIP}mNBU1T z1^Uc|y*{oFFb5zV5Y3t0x4LrwW_GdEXk>LJ?)+_n-NZMkOZXRkEBH=h^{1E2HC}SG zy8<#HXW0skvP}Xqfms;gY}nJ~MToM)aXu$Y#xESIq;>ctzG$vjTT!E)(*IsHk+O~h z8;N4tqd5E5P`sKd5X@i;Myr52lxQuooT_DFl+zgqn#fe=H3iJC=X%Au~ zS-*+lZEFfl0gG|crqjI0pF0d^0*B6Yn~c$>NjvnW0RSUG%-PJZuiDhGiiel*5g>rs zF=bIGE^KZqkzHnB&mpDuEwnhA7QM{evDt|=q1~q%2abTsexCvj1?`dTI$m;R9tyDT z@4}tfrX6cy#w{bSs!$oj-5njk!C-pds;Wm^o%`W&&LnclCr?lpmSwu+0D?o2>a&1f zghhrT+)K}aqaLHC2SONpMn=hGsHu;QM{a$$jmAgz*8WQGwWNfS1?-gS>A171P*8

a>si+#C5FKVPU$N}R5%7;D=~-reZIn_6}W^3oa;VdI1~6kO3JvtO6f zPkx%TP;Tcamv2%&ZAZ>WQ@G?L+OCqkq3bLXJal@1dhSwf-KaA{(?0bOs5Q^VfS)Rh z=b+2&Ywy5@b=z=>P*c%5RR$A=?wZc`4;+PnhzM^7>I+0Y9qg})^_-lIiJF8j0j30S zVK($lXbm^76Qph0k9&|gex&V_0~azQ4!Y;3=^EL?-uSbC>wu&Gwh-9ns^%aC4e8b% ziAa8O;(lv4P_s2}V*Gc?n@X3V>N&0hRznzj=!0D!|EvGz>DE4mav5+>R1ngn^}a

b$KQwAk(31|oS`jG87jUj27ck7$sn{HQ!X;Pk^A*)nZgm7p+2LhcM81!e7(3@PieUQ zu%QhMX&db4B@gO>wEN_Sey;!A>x@S=S-u=ROlb)N+o$1Coq4P|bEr2T$% zQ&tC88U!_?N6`#1jR_9;HE7oJnf6}6^?TN6huW9)_CBy6iM~7qSb46aCp8^>31WWu z%a!Sxz^?L#>wp3WSQN3?Kdv@S;aU|101JsZYyAUxh=@{aGBz>a*5BA3F=Odc0CsP> z!|{k|7@kdrbHQKzZ{QaJh_*ohgOA(Z>4?~Vhl2z#9;Sg{LOSu-Y{q{0@i%hm*}vL%9e^lO_YfNY7~cT z++xv80?=77^i$1k0#sO0@5WB}NA?IEgzT9<2kq zzt$}=h+OpKSWn}PlAC$khn&J0mUo9XTg?Lp(qW^@9N*2(uwPz5cHIN{999o0 zr4VQW&}!7F)vQu0-KrlXN!_v?ZJFRQ)qY%~x5D!;_v1m*HX2hJW8D9TN5jniV}owfNtk2D{K9v}Bw z+qSEf$hpn@S=*n{Wea(AleH6=ZHupDCs(9_x)AtK4_w$Rgx!mD??`oQZNYsZ@m=hTR`1#Ve@M<4uP0qOMh&owQ4xATvaV%)wLYrV2N#dkJ3OTHxdY&m%sGju#X%D%U`YKA zAZOQPUEIGirCX@mlQXsBp*h7i4qR5wgPKK7Hzmcr3a0ddy-}6&veR$d88deq5y$@k zyW6A1MR550KL{_0$(u}lOS?2(kPKr&{X6=4&A??_>!|p#8sTBqR%-d$uJd`C&Z&bE zl4xFumfG(l{?#H0hJ1~f`)CP!buO?iO46KiYEXI-{0>NACmRq|utDB4{J<*ov7Od6 zeC)-kT93`FXl_l8vF%#E77su{YwhRH8;xG~FDGrm_a<$^gxMj8^hT!sJeWW!4mi%g z`uop;;xK{)fNwT@X^R@Vawc-*y}9{}T3O(?j4SRGUFZ)}q3>4@XD26N6hSi38j)@IPgZ)szRoSG zDs8TS^`@56C~6&e8A7U?kI00`Z69Xo-|G@g3yQ(3K)WyKJeSo>;W)Jz^w0g);9Qw8Kbs8y}1wlVxxPa)zF4bBq}g z$$dKM9COsut^~lkUQssD8N^^b(5lkS^g9@K_u!jM#f_})yJ>m#(dEtrNz|!Tk%Cv& z<@!{|w*X1t=LZ*{f6!$etM7y`1|vmu*cGPxlEM_32pCX1kAvOiv2Q%lU6_ll3^`K&Xzv zc0LvSy3nt3BUQboS~Lh9^$Q6fUvdqY!9Q`Ux4Jfrye~3UWMqQjD9Y#aJLK;($3QMp z1_$*`()LELgZjAdMj=Sk|5Y8k7OaB&K+SI~P><0GSdxTAu5iP$!JM1}XL??rWYrGm zXn{7Py`B)B?_cfTI|ODUzhl)>TruYT_t8B(L*@ z^F3b?fn+l-94?+b^s}nOFKb!EX^Wv0@HzrJ-nHr&%<*Tk0j8o|PKaJ{ZRwu8C#oU` zD8`qZHKUGRZ!tYO)oM3GQ;pqySP4ci0AQ#gFrsi6iw;>u_IT~`Nm&v6!rf5w8ihlt=a-R{N{-Thf3j=;fT_7-=rd#IJil7Ur*TB#v@Z(NyY$UY(W zOfQsS`E73Tt86*X1nQ4LnR}%^zuR&le~3EI3tSr1m-m7j8r1MRdD8lBE}CHus7AeB z$XOHPa4-{?g)bD1HX|k#Kiq3jZSGtOgCNuNOc1vl8nfe3XWk87Ho7+M68z<2`%T*K z3^9<=jsQUJWP|e2In9FN=<~rL=QH420(tYnM)0xT^GSamvEYlL4JxjPa5FAVt*-=i zp{e_J0JvXl=lWT-Cm8X+{ybu;42~ONNj0P%)qV^1tk>5;WkB+Lmb*@7Nxf(K* z>!8A9mHX!1b+m(ko$$FC&Txvj!(d$gw$cCH(@yQ`fT6@TuE^okpaT2ZK_0URsPWH* z4C|31EX>Iz1|?q&t{vi^^%Bop?ubUBPS+#A#f#!oEfF3Sq)>a$#sqsP@PPviQX;fbL5 zs$UIuc_Q;?7cDWme!|GKRtC2%m`_HPTy67cu0oq_7SEs~U3^+=# z1*6S3X7_~O@ElJCsW$BT%Frc2i$vi6crPh{l_jCM^QkGD5a*y-wJQLKjwGX1pDY=^38{7?|7Fy z1BD3aF&mDB>9!Ya?c(Y{B>5yD6_1f!y^|ZAQZ|z$l7rLLF^18WZ7zQvF&bKPRHXfR zB=@E8Tlux=4#|tknfZX~$j)ALPo0T|Qrn*)1H<<+`nbE|&9>v~|8!5$PEUp3d@8s& zbpwwJa|8|nv_;){C{fLna%kA&cB#8f6i!?=qAdB{Flyhrvvq2EAkSSCdn*J(&NwUt zzieWyLlJJZRtqj~^Mk`<`j&naodObQ>HG8aSF9KfLX3PETQlL&hIGhn_wLjOtzQUG zDG?f7x9UTG`cyP}qX;714hq&yT@&PC0KWZJ;5!Qi+q*IiMn%7ZOzx+rKVsdqccF)? zNi|+_VD*15u>TMenbM0+1m-ZoL98f!iq_n{T!TY3^I08)G3p62CH6`dcxg04Yho|i z*Vrsv??~VR=c+a^g7-TgUtZOjc|RmYnV8@Anct*#SD8^>+Vi{cgqwnYVU9(g3n`aF zjY3PS0&upNLO(adLf1rcmF8BKb4EQk%HG>PsiikVfO+K^fM^5)&Vy+&SU$iT0Pfvb z*fnjjt#P|!UU%X;1%5!?kP6bAckkQK=o(x*K`ap8*EIa4_uTbbpUXyV0d9lD&ep)< z<|5z^DczJxK6drY!P?=Hu{H>^v#s;oVNC_Tm|u;WYJ#m!RrI(vTY(^aGPgBWGupGS z$Xxg7?2hT8TZ_PHeK_CN50SnIRyMG{K5?d0ZK!>~s0!Lt@b=R(_Z=QE`*SXu8buro zuO;*_CeFO~^(Ie69E|S&P)UeOW@~`gIhyt#&$AU^!R5vqo(jrzrhpXism`mV=Lu?I zq|3Kp_~zsBV)<}PhLXl<_G;5gK8MnO6H#7EZs zEc1DA{T^$cY|PQT)8q08@15U)Rm;87`O!y;6$P~AJF1^xUDd>jHG!>l&uJW3?FihR z5ypIH2rR|_CBIO88x}Y5n!*r)Fb1Y{2AHLJ3b@3I7e9>?{L8+l!d5{OBTDlf?c+SR zG!-TT&hUe)>#TIyMWC(0s}gh1hFSi3Bm)kFo<)-{>N~61Po_O15V|FCuYxkAAjLFo zKgC$iKU9Fx3Ft0n{I>B6s2hJmM+916To~|G{Da9aDi~sDz1s1T1;Zk|(>YjkuW(ST z+BeFX4R^q;4QQ0Kt&6}_5D8ZWc@f5*OrDlNrv|zoC4Cc2>;gGwt})vCMexCy+2--{ zXs^+gLYhg{4ds-Aw>|sQMp}7}UOq<1L3KM78I;JIfjs_A@MX~5>b5on{p?-)B#*-m zP?y-FnPL9D?^&Lg2C}PjfUUnX?L2a5yr<*zC2|4xHsj?R*Qznu4_Lc3Ey4cfH|7 z`=y29|H#lozUq9Rf)oq1M@v-soT}b#7=zy}C>kS0U@SX)BgZD!x`JCBGSQ)H@}a6N>KDa2S%6Bp3m0}%FAxPsX@x-aY_gd z03&x(wti1IKzM&hncB8%g!Z*WlQs+sPH_~23E%=ZBdzci*s+DFfAD20yYF6QZzFa+FhK5Rh&o1L_tL`IA4=rTb z@EsqRrpey5>wy9sggY$V+-?1yuuZ8t!f)g$>jPZ*mgY zO(>NRz$#Ffn@-?BgpuPq4wPj!b~(2(5V~u>$h^q;>x(y2dLiZ0h|t+AYCGL9qjX0f zxTR}@HYou7&Sa}_Z%+m6(c97~cEIhH?olECG5OgbDD_k>?+Vie{vX8YKtJL?Mv>F; zDOdPzXMm#*RnZxxI{_Rz2;l)CBH=w~98@T}$p9J8TNOzsv&(P!3^=3lH#*L?Q`+-D!1I&PgYg78%>duju`MrJi!l|lk9U02H(BgJF z<&ZR9B4`Lsn5fRfUAzY}h)6I3+dm!9Tbl<_WE-e=3-fRE(sJ%}0Pjkr6yNf~x_H8! zX)>}W$9>y}rm$!Q%75`j=di~Q06)8p-F&PdFf9$?36d`iJ_}r0yLyO-yF9F<8Fa(0 zN9X&->h7Hkj85|kzEqOY%QR5z<(EH-({G!TP+I#QPDOL#xi@l*c>sMIUxjM90@Tk5 zLRX3+Pl1JdZ+X1|wzEA$nwtabrbUOock48O>O}WVKYXH+4&UM&|3Jm~_D*a2f9TkQ(-PPqT-k-VdkiGGdERpgwQ?($bLq671u1sd_G5u zeN4~z+w%Cu`)y93OlpeqJt%t=WjtGk9RWuarkhJAFYhd4_VS*`#e!cWsTS{{H_6$; zJDm&>s0E--kF#Sjgl8Yl^oK^U8W)Bn_1LdGfJiZzKSRndJ9(b%T@yO_ys?fs(C?{~ zKce2?T|0IHIzg{RjrkllSXK9l&1$7R$VEM}Mzpr4)c==k`8nWN2#h>MH;Gdql7Y)0 z5{}wnYlR+H+k`Sgs@t|&@^i6=&f&kGjOsgfZ{)$bEPN-tKR2NK3h-X;iTN_>CG=Xv zwjzPQK##_G+mUtFLu~Tr5im4tqy6}UxrbdBPitpI)Ubjs4tU;c>&}0^e?ARu75WSN zI51zZ8R!vVuxC04C4SI;Sye&kyGY4}^O60@x?i+;=4w4Goh2S-gpws2{cA(SmxAglKhl&4DFLxM0D>{rso13LH zQeDWcFybuc07R-}y5u|3>%^v8!pO4b%1>eHT!=T!T^!)~bvGC+VW~4?aBlfLP|mSh4y$x211% zp&w$_gh0K9K(b&`%xl@?@9_7VHs;Xv)iJ|{Lx_k7*jUawko*8fnBo!nv)r(@b|MVv zEb4qyhTfq-3k}^C^u^K_^=p76Iy_T!yJU;ffARho0b_AH<3BV2iO#<_pa!vZ81Wqu zT$npIxyMFse_m~A9}3J|8TP3?kj106U|g$e4=nP7?Vk6taBrkM;*cWJ;1{K9^Dt?> z9<&kvGud1tccN&_^-GH{0DFULUzem*MDad~3ie9`6QP~*8$I8P59=7Ik^LXNHbK4{ zt6M+FnV;0vc8^t`m>F=4ZnyrKIgJgt+eyCM`}j~gbut(gxaiOUaRVwu_vQ?C`co?< zt6oW@69e*(fb%bJ-UnP~ouGeCR>!Zu0g-}Tdx>%X@gr*E!bp$YxwOxupYIW4ltnh` zs>+6n!Fei%6_ zt#E$9Q4|iLDx+f7qjv-QMr`dm4#~LkJ()e{D7-sw_$lCAEg(fPL=V621%X#kebVW5 z7o@&{sGPXMt~(pn-cA~(lYy!8xCu-Z>=4nk`Bj53vUeV+X9Qae?i8E%@I~#fOTe+C zyH~AL`sq2D&DdV>g5DnPqGn?DK^`MQ2j;CM8u6MSrr2!8!S)1HedHwo09or0Uk04W z9IXvH+skC~l?Rh1AKxUA-?h}B7B&;&=;G=mARxL>fKmP1*W#MZP4iMvSZA36@v7w* zvxAvEH;3e6tT7J*q`U9)u7#m~eDpl_;|1R3taAyJ+aY6+8J*`Z)nc@d|$7=wBatJC8`ZaD; zI?wFMw7Xu4XhrK_^EJ`0|6E#QA6OvyQ40vesscE-uAH0unCPAtQ+y$#iE(qlXwg|fU_=8K1tc$81tS+BSu(P9vieKdcjl4 zPL_D1g*J!JadC&K*G}*+&UQldDd;~C&VyB#1H$CYx%sK>C4tAB1YJ0EIC&%H^O8H` z`8i`%)w)rUb_up5g0FL|cGDoUOpZ`l*r_v`u7h6||oj3>3*;tp^!8-V$KX+zp#o zK$%EWIc3`iLhB@%0VKB+)U@OIBE4x?oPr1N8E0MQHV2_>tclh@M_sF-7m{f&~J#r;T%>NC`0l37&9$RPO|YRET#a@jv@ipeicA~(%S%g*H+ zbhL@TJ*J|;@aZ$U9GJmq{2`?RrzP8Kl64@=d*GaOUT^**%SEo9E}@VR1;wa8OWS5* zeS~&5B&}Pb8X|c5{FxYe#&tAtQUF@cva(S0r+vfMd3X3YbTkEQ$pqHDao>97NuD@f z;5*%1Gxs&g{G}e&Hz6nprIISogND`RReLe)xS|8p*1Mzo6D(bHH__;M7!T7AB$r5D z0o4^wzx2%XJZp}C2zohFV*~(|fD{me%K(1b@frlpC}qWp6fGKtn}XaWp9xMeZ^DDpoA+CLhEZTm2qz;l{}uN>z0A&G4xI zCQ;8SV;r}8G+|T2!|hmoj-vl?`?4#?+%GB}*pBD|Fz%q{BlpqFn^G7T?NA_#3?+p{ z0j{yiZ|=NT)&XEE1h88f_s;$%s_f)C)ieziA-m|J>TKJk_HR48ve0|=f4#&j)KW9? zt={ZW@tpzC&)gJ1PJ)|>HEiH`X{t0oIrU5yQu9jOCp1MS?fyei|)@D8Bk6+v{ zUjxi*6v;%TBwyCGy1eF~rV0n9uoQ3$8RvTf88z~2y@$;gz-xV{SH6f#^m(?@VA5Hj zU$A=7ks}29SmN2I1y5tSvD;@znV?Av19nQB&v4L(Z3kp7l0IZX8$Yyygc&jk4bUBB zsU78|tOTH^ZO|u<^UG?zmbbe;%lp@&dOd1}K+7MpM>dG_(Ky##KyD2PC$1S_-0U z&x{}A1xMSv{Dm@58}sV!T!AfkL7=zvliF|J%k;&X1UMI8C>`X3uCl^${m}f`5lIKa zOTY!Ug)gH|jM$l{udB4INmiD`P{qAJG&u^eoUV@?R7V_BRJ^WX6F8qGp_j zcUjZvdKeDm%%tnUuV4rm;rx8Jq-DZ-c$g1GmCtg51^qpe(Q<%CGa3OOCvGMYNGLZ+#C8Rvy&vk+aV`{5doKfA0F35T<# zNKk8Ty!pG5a#>U^UP6k+K8EDtI(GSp1-+JF#5OiFP_)&~FZsWMCiu3XMSsD(c4u)Q z{$iyO)I__|?1B9p!tZc~LCH!aOqVC-*JyjBUX<0iVKP!AN&VvVr~N?*lW1@M4rxG7*N* zkVJdvM0yUu^F$keY{(;Id>L3ID~t76S$N}tkNJTr8fDn??Zdm|Pvy0=d+2ZF#D zI}zGig$s1SXI1*N_in3$YNiItBv6x}vT}Z@rap~{$+>d^Ou#{BSw{GkQy7#k##wpQ zs@S(;iyXD2lh;I0LRFwq6r}gQ9;|q}46>|}lblS;JuxS^#=R;D{2;<-$71^;blC(J zo)&HQX z#zunN+vB|hTh3+^x&C2K0W}%==tG`O(Z;~Slsr&U880_MdIjtr()PI*6WU;!eVM2F zyJ+H!Ac=~k(b^s>jY9PL3>5!q(eQR z1EWAYv)S3k*cgFpJ(b@}{0F#Qg-~Oy!1CZJ{axDQW;=>}r(;h`eSSlAk*Mh~MgU#h z{Gg8~a&F1+!{>UoH(xUgZg=+P=;e)qSOIu@g1w-;YeK(E|N z1l}NQUU&Lt26!syEf0j` zX`o}tcCu6)Y}I{rMj3(c|e5tYcL zKBGFSh8bH|1{4X^?^zGQ|omkn=}%V=o))8a!)S{ zCRuCWOZzJADVo(KUcND(n-+&*Xebpm8D&UI@0+bc@$A+0<5wew1XT5#nAde-E}VhzY(fapz#})d<%> z|N6>Xvo^_tBz6&4AMmoc7FN~6GtLYdZdBI(T~q8TAD6AG2BTO}H9=8kGS5+Cp_qj+ z80S|^cR81kZ%(+lSmum2=U4^HSf+0&(!o>{iq58u9z?1 z6}bT4pDo~8-8B&RX48DvZ{*OA+Z;-nMR{faj58jMnI?AFux$e~(9Zbfv)?`j71%YP zzg``&nTmapT(W~p;@A*3Rm2~N&QFb~1h)isf}!|wq9Zw##xS8qXmGdtU!78+ zAWi?NcEl|-w?eCsEPQ(@Md$&a0T0V=N=}d7JV3NRr-thXfLU^wPe0esodkD_Lt%WlJ2Ylt=}Bs84qb9KP90$#m?DRkJk zCNNm-d*m}uS_TX=fV$ebkE5?E1|qzH=K9N$2d;&sh5s%}f170Wb@wfz^x~}&i45Y= zd^gnsSz1?dIpW!Yp!>OA0Dxs9<-xs2r`Julo&vHa0kUH*AwsTCY~-B=%QY@j zd0tZ_Bq?Kj2Mq?G>fdc*Zp1jBy42^UdEF?c zSy&lhqAQjfDgB3(J%?OO2+?O9C(NZG?4_w%V!yb=T#0PCAj%3B)ZF z%9!TK$}aNB!oP+fPt)FSywV$q*tCNDPPaMo9ghNsHTTOCf)n%_R)s~=>`5*g*0%#{ zhKH+7Uv*8Mg=TOsCQetEH?8Q^r=D-&! zV8yS#KNRGE-p%p4OOi9M zuFK$Aj76j;@FI}_mJ~>j#7y5q@W0$e=wSH#k-G3=fg~@Po@*diq0yF=;o=%`2ul2X zW!AWw-j!=jO9l{aPh;cqQ*OaQ^Aen;cX6=q$x5(ALklO!4eZ>=(7M=l)wkU}UE&n5 zy|rp6NrNn=Ozf`4JlM@pQX&B=$0*4=pZ3(r2Vpl|H!$OZad7EM`TnES9?D-{w$@7m z>trGzMgt>kkfH2ehr|z`=y_?B#s1Yp#7fRf?FgORW8LSc{ck#W-HF)hI~a@d+&5>P ze*vKmJN-I1GT!r*`Mz=Fx$QS46|ScnH6U&k5^t>wJ`61?8pg750A44{+74EtFOeS z;N`Ybcphvpd&ok9;JRpoVigU9i4WR26}J3rxM-{+(Cg;??V5+Z)2VMf>NUp*o)o8R zm831ome1ww3s9wuZpT%TB#4kDuT$X5Z*G3S?@iYHbOQXWG3fisu-`qsv2(KitGMuB z)`OO?& z#|hbOE>_MbuJ%ODe^7k^$7x1<yVwd`7u0q=bkF(9{+##pu4D>R<37q zK`PM#Y^31+Nz3Uqeo6S&Q;gMP?XD8m9C(=ae;!fp2cIrC_q(muBDz~>X+Y9ipt*pZ z&vY;t2i@MBp#qgT&y>ZkLckult|+ZEsqABYqWQ}KzN-eo2Fwy?rz~u7km4GQ33fK5 z0P~g_=GS!og`6h)VyTo86Ap=e2y6fl$vmO73Qfek#W8CQ%jxARMlM7prjt20YpDgIh-vnMh8;GShnT)eTT#^6eeryeHyceYIR)?o`ub;Xrp zdwxs^0ygua^915};UPdNx-@%PPX_(e_eJIy*_VxBSb@RN%hG>Tk91m;hX@E;kq!{7 zsZ|Ew)R(QQ_k3(F*s8al35~{n@Lw=2U;HMB%zs>4l zdU(ICt0`X6FImGiD-T%Hy*dxLkJ@?tpdo=4cmb(ekE>r=eFu7-AyF)3dblsfblDA? zkyJ|3o|m6s9&|I_R0U7qlQxacS-GZNr2IdU{yVP8bn706=Y7sJ+c*})h8&e5O^!&B zni&yk0@7>DD9z9!-O%puGovUVZ~y~BgbZD}lu$y+GXsbLLZnO0ND0ysNq`Vi=C?EN z-})iReP7q!Ywx}GTDwS7?!SR&TmS1^x4H!t8C(auu2PW}`dN#3QXDQ>cB;=H_UA2N zvIgNo5*XiZ%%$ZFe92`7Jl<3kqN|4)(sRtE{-qP9_8K@d5~(->O(tYEFq?lpDE6Vx zEdku*g({UAK9(5>#(ZSn83llAzLihfie_%dRT#_BigC@|ErZ9*o!E;QWgHP<6;I51 z7>cuoT5|XYRLs;tao6gCh2ue8(ti5 zDd`Gub%+qg>#h$qX9r7M%X<++V_wYwxhwujZq0zHw<1Hm~oGLO2rxTp5(n2~-o zDoI4d1F_IwnRxH1&VdW@?oR)^KCO_QmzX0RJ-BkLZ7$lYd zlhDm$f5#S>zb>#<4g9u~Y#CR#nBr65KU?PIQNl+`N9^`johq$_xs`XVcCRlDJD_wH zegro!4@X(k_B8f{TL~z13}$+e56jAP-(jTqI?bh_n%bG@#?rYrajVjzm8PD#(Q5I4 zlD;IK@8bcPCO6ZwqWK>korU%M48MjBUc$H@?Ghd|EjpD}Iaf=^*ouwGYv1nb&c>qi~x9)MvZvQ znyXhlGMW@_N!D_)kFo;8q#%O2Jn@#8-1g!V{qy5jqmgnK*yf4uMKBto$wSGib2o2d zpMAzJ+aW5Bz?uB-Di^UKFNhaKq>k!ye}8pklk%~_bXg29YZYzZTXRA`;4dBiy=PA? zk8CTmqz2D1j*_W2c&`EJ$-J{Z!}`LXuHMzCJ`UZ(Y(i4#r}xwb-xi@Jg9RX=IZaE; z-{@^vveC0}GLz$*M>)Fr8xAY^O*Ri%F6RXPp8hAd2Z>ur!(SGX+6C(e;#9&l*+qkO zaH{rdUjDWJs?zzspA*{svPpQicCA#)ihO?}nB62WntIS2QJvd#=qd5Oc!LyP>X2pD z)j||IpGuL{aVvOQyf*AbKd#>82tfdNjcC$z;Wea2dOQs&2u>hk zf3bGO<&HL9*Uv^+H|rlt*b&_)w8`dmeR1n2t8+{OfS+PjrqKb$RYl-0rC&I8RjxieqY)(ynkipwprM}Us$aME+=q{F=uQp z6fX(xImyFv3;m79O!x7Qz5%!_$iB?2$vkiLI~u?WWRMF!8sy57JbuQDvdKRgx1gPr zsygjN;pz3c{da8>_a-~hZcb1@$j2R>Hkt}9T}BP9SuS!zB!kF^)c2v$)u8lrBNDWf z@p%|s-3{XrxpX;yjCr{@XE}#Y+8$}to;wShRoQ{ob!4dy0!uk?c?`5Kfq_?j*-YR4 zdYSWU@5N8;xapGTE6$ILPN?@e4Vhhs_ZdsntndxPcy?ZJbE(G=%;4t z!#q?5M8qw0;thFowfPWld7^4Wxn!_T!}EwtAsuDlm4R-L%_ zksje2>lXYAoatJ9CmWBx0Egk(fez2uc;(F_nnZqOdG5&%{hv|M4r?*!7f6=bt{SHa z4ywfx=pC{A8pbXryQMvdsHrf!k?T~aun{q_`-SPwNp$m-7*tMp)3YRd;+5IzP92u)EC8a~CSY&-CAQp!g{?j|hhG)XsE?2R`C$^#mZk z+XA{v>v(EiGTW0_<&-7%gD_Vdx?ose8{akW7A*A_Twc)3BtKEqIFnU%j&}(!vMR9S zBrJsmRSxfED}nc%41Lx{Tv#QW!x0eIPK}!|qPf$EfVw@>l6FBQD2ySawibN~OpNp; zWA*q72y|;3uIZKZ%Zg1(cGuwbLH|JmcRa9xXZ8A5dFR^uO+}4Sbfo0`ci8yLkqS;EaZEniL=)F%zd=mVH z&V<-VTxcoo8s61edug=sHsNf}pYPRMy79{a^?!f$9mnW>rQO@!$Y@DA-()EPjk+x~ z8h*cDeJ=>>p7GHpux22%2yL@m&R7;Rglk#Z)v4+!6#g@EyP;dHMvtCK`g}OA&&>n1 zo459(gwK~?pclz=9p{iQHy{}0K|A3hOLCSzT&p;`gKG*n^>#qmf7#{U&|t-Ay7B1Q zX~KX3deKpY;Gy|5GDkAERcfQCp3rB(vr)vjg(3V}b=2g1J zM8-1Z`#}|lV^NP0*iP4Ab#mk06p29^g=Z3Tk4_FqEC8;`LtK1AA7TjZhdguO2c7lX;!Xft^DYB~YnW za!2%4ktdHjTtdC@9gx^G)H1VN+ppuPT{6YEg)_z3ZX{Z^Ug<{3E5gIFhh7%kqon6+ zu@Ba<4cG)^AL~qXu%;U*v~Z&#VQE#H?(?yRcyx7dZT^Ec|2f|~@>kbHO`-qY za}{~GM$N3_3dq``aEBbIF z%6mF&?`_T8w)7Q@6nA8*opU@S0WLES?tNqc?ULj@M3r!FBSR$mJXIl`kNl;R8q4Z? zGoamdrvmaiDUb4i7^OcQ>ZLPHJoy&?bpKOue5yeGXI%G4uHL4I%r}nJ#NtvgdZ%ghJjstbckSO@#FED0#eD2j7*^IS_WqgO)X0|b)B?pez9^O^y}?+w$oMd1(435UI@!Q zud65P(*B?T83!EYs(^&$>OHG2;bv{#^Q6{^8Y_slU5%g@41p=&CnbI0>m=AoYw zw5nTM@eH0wr=-4LK_Jio>nEV$PUeG{u8O4Cz-O5ws1`0QwrypHb7#;<73 zF1^5;x<9v5r{FSq40Z*V6-X$KD(qPz#hlATqMfO(fcmR3%rqR?4t*u>>hpw~@qG zE6#AF%?L@PC7udbyDCtUBx!`cU}Y8g`R=ihm1~!@dRH!TGpjvqgt_i0CQo&t-%XTZ zBWzuJl5od=h$xA1swg9$XoxvY zvHfU|N9WDj`ItUa#LLi(Yt3J+&dvq$n|wGTBEOlmz1_&mWF7bg5444={n3^umoIt~ zUuA;Lasv1DR!K?&%sPFYFtfc^1^1_*T`)F0@G7d8&s-_&ZMf`ct)+62yU`5HRk)6! zqZff4Xm}2_Gm=8=YiZAxO(99weKd}jHb2`F)ghWbfaM%KymI{2{_O)lQ&`sqL+dvl zhww$b*WGa3xC}`yTe4$nlzw>Fh9$5U_b2MtOsvP!j)KAV+8E~Mp)V5ll{{P*v9cty zdN_znT`rgCOX&C9(4|T9nnJ!4`?9-9lz0OXD&BPcnt4+xe^PwmOx!e~Y>vNx0EvRc zsrjkC17+B&+b39rJiaw;B)Eqz_=i$khEX8I9HY;AUspTbHNaJxl%N*M|G*uD>So6a zVR*DH_0Kyf=R@M#U$O7(0o=Rw#STKK(PuUPFnBhO^$R?QN&(5cDfxm)%W;bNi zm9jk%V>e*oP79KkL&Pl!a8xm4?}$ywe|0Ix>4tqgh_DE@>i>s>_76iZV8#89l!P8K57{=j9P(RxY=+LS8nbmIYI8|$VRNHknTG*Yc!CR#D0TwXIsE)S~lG9#G~Ofw*PmgG)_!IE7DwYkf@1SE1!dQQkj{ zp-Qt!pT}97cjZCMRb)-FY<4aN*3sb@sokCmQ39j1gst!PDSy4rn%(&Y$t0&G`guN& zT9j63JC$nRz?j*2P&=8GtBv_FP&d3D+gQXnVqMuqB?(D z<{43`a`r@LZV1AME%M*^kzM_yPN8{LBTNuA^ULW~c+Efn5DZJB%2pfWL0{QpZ`VV* z!v%#VD^hil5HhvJ8%v!KIN!5Tvr8ONw$oMZp3qomZ_4q>!gDTBb0N@z8q1rzPYRxg zI3L%M?a@uucIXagZOl2&2FRYsA2tL3B$MSw$OJ`s%J<`gff7;&#B>6C`E|ou4cfk#9GxHZ$~EC(j8zRJ!PTxh&W# z9bSuuC3wCWJg2WS&86_sA<+1$d&m0o-;-brkn0|fy?zfy=fC$qfU9XF5qrFjhq>sdU%;ZieMOT z$LgCGUJbAdOP%X%nCdBT(xTfWPJKONRQLa%sTL44r!HcP@Qr7LJDRx_<&6ooaVqm=Z!ShYQ`_4UHW;g9*jX`b1EpK*e&AUFY=BQWQas-pL?B$ny8F zjeGyVk{WA(FlDYcZ6EX+j=ngK3%;B}v&N|ccbCLQT#k%PW6a2ql)TnsMsAb|u4*8U zVLR*h;M4A+bv|&3hYu+Hilx2W5E(~bC}yqZI0hjFc1_4RDG?x%QOs|q zkeo%(g026(Yl7GV$6(-!JdQ1@**THwk@q>+Hez+7);$*0gihe|>lBD0DsL6-hSp)zSt-%~wM_Ovib^rl7B(uU)sZAG_RrDWXYeaD$53IpC4ErHAG8aIf#vA694o>~ph z3#*!U&%Q9I+gBW;Dlx8FtW!I>ApSkEn($3NdJQZkCptm~=Y z8>e|Jhag5D>NJUwfS#)}>#!4>{?H&}>guCba{wVUdb*wsZdfy1XcLChq(iS|N4Ezp zVg_d4-^R&S_v|yZu@y2l!*eD3I4}VXT0ZS9bdA=M=-uT~R>4$Ya1#+|wzw8f{fxSE^Ft=YKCFr#txuwPdTjcycfHBKll|EkX%YyfdK z_~?qfpzw!W+HPQONi^K11RS55Sa_T0y_;p{SQ+>drL++T$h7_ znyOowGd5D6#qmA~-6MB%GZ+>5PkH7p|HUP=yrQT|DakvdWDGR|F7=8h%>72Kb|sNc z?xk6h$1ud0jUflkN6ywOvq=*CNOtwXsOC+js^iccFQ1zpkehv5t2ou%Txr5W>42*sveP&M(XX6q@5wJYjy1fP{CzDrp%w(YCt7%q)Mm5a z9SA9#kz~lj?lS4<$L`Q0J3TG;yuS2N2P8*?Ydw?lZ9;?i{c@k}>zMf~bX+O(eJ~i7 zO1vds%p?tO>*B5jA>-C2`{Wbx;0fC$o`k910KTx5NQV)@a`5JP^q}!;#6SM1VaEqW zGK8?sN*5RT;4xkLN`K%e=Tou;BJE1TW=tFS`(gI@*@3uyEFU#hv*@z6@uyovX`y0o zb#M{hQhn^iKS>Yan%f$Z(BeXiayOjVs=$tL%USJM3f6*MBdfu$qG zO&z19oyeR834u6Dd1l{>4#FP1MlwCutsAALoCOH)GPIeqNs7@y^Iq3(HQ?_;nVr`5 z|L#kMbNJ5fiq!nTTwf?1BKz#;%3@B|$oPcsQ;8QqPfWhlr|)EQ%o9g-5d58o#tc-I zLLKZq&spvIH(4t`Hx3G}jEYuuj>$0c!=(CSRufl0=&x|B27hzN-_bC*5m#MgE@^1p z^9fd_6Eq%Vf7;wNXIV>XDaMXNo}FL*{;EPq>Zn!P51f*iC(Elt{|P$$CnH_-t7v9( zXR_|_PgtwZ+wDVtqP;TX&rL48i_(lZny=rFy_XzTg(g!xnl={g|9XyL`X%uxQr+B) zv3qGH!R)u`UUFDJ5+7Sl7Z++0FUijVW;Z~dZAzHsXd_q5{RWE}SoTP0^BiC*-_4t! zVPrrd19f_>!t2VA=;mVEKKJ0W7T&fD2eg_1LlKYRB(-f-cE`kN8fkG5K34N1rd=A# z=L4=OGV_D~1^K~lu@t$YT^o4d9ae!Bfi_u~SRd;A`zxz-*d)irKK@h1r1#|3MW&Fd zCkDwa-2zv3-2(ImUc-^72sSe`V*F?F6_!eJJFOvg}TkEbS z$@oJhCQDGqhr&Mp5x*hiH;E^XyX&fg{ICvM3{?Y|c_e{1@TNTR1*syeCswD(czz6@ zcZYPJn*g~Q&$4#Xr3|Rek>F7bp#I{nzYC6xx3f+Oz}NxxW&ImW*Its|t_|p)k#@`B zLWKssaXYg;Hq5#k>eWBEO|1ItNw=s{4Ys2}Ft*4Uun2titgktna;F*9)zxmd$8SwX zmL5xxPk>U|cNQ>KlSMAlyeH^V}PuyhR)%8>gE;8`A&I{cUWl;xmJ{_HzdZRe) zYSaA64IN^tElXQ+RJc$(Gc`j;H22L)v^Huf>(&j=Y?IYuF%T&REKxI+F1yGUND)~j zA2=~EC8TDpOmWFKE6#Zh-;Ext_#p?(@}_=C0mL8qdou;yam{+y?ScGGKiO||xA&B2 z#&WqlM=VfX(zQQ?VJ3hcrmNA~YTM|+gZmfr;>U^TX@NucaUek;tL#rLd6>BbSU|K} zto1WgyWvaNPWY%DDWBza0VxKL#3cT& zCtu|ZucOxjywfEd@s3?aA+M$5rsf<>gq&Mg) zWTZt@;6p%2@#j&~TB>Ht>7uYYQ=K*eME{cjI;vRO>5Y3vOd;RWlIP{nqLIs(b5kH) z2JDC8l79;=5uCM~f@d}F8vba;S71jJg(2Vg&0bwyZpFa0|0d#QNv8hV{lA9$`YOG! zE@3kzNI0gxz&zlkGzLu=VN0gp<7-nUAkZ*X-IBGty zCUrS(Cv3s`3~D@5db%c|eHcuLo6EVRLGY9F5WMhIx)vlbm}vOFmM^ctsOjoN{bs?v z&!t=t)eJikVDMW&*BxD|lt=oKV$#xL%gu&NH6UKe^*A<^a~-b&66>oL*$we&N z-W1Op)A9u~@YJd^Q5ox~_btynli4bfD*oWBA|Q|b-{f(*%z;vRiRNG8MXvG%)G9nJ<0QYsn&f}jc#Y+8gGMha_c8hWXV16 zEb}f!rgQQ&Fh`8~hw@%T=)8G7$2PrR4Hxa9H8wQxaFphS{mw&*UB#Dwj?{xdqh4Gk zd#sZ$YIrul9Y#f;>c3yly-)a$?%k!@8zY&~7tVSr zujHzq`!|vsr_^t|!b>Jt*8EQoy7U^bJZi0;1!@wYx`WMxcdDxIbFEz4b!(MI+1n^p zHyAi`#%20x3iboM6@-~-@wsX@YO3*!Q~Ql`gf60VMkUrb@;(BZ-ut7X~-xZc^mZ2P*Hqk)*(jvnu`%~VRE+iA6EGzH>o zq6N~y^?<=Zx;r=cl6h51%||lxCHKe@1(c827|#TzZCpA(=0^1vh7N5*cY1z1(SRN#w+rgd@i-mqi?p+e3Q(M!tRzyWh;% zlnChZ1kv$?wVKXMoARDjl7=Lv?Cngo;nlB_peMh2e_TTyT(GnF@;ejtFPr$b1m)f4 zZ`0hCfs>wk0@`^8+vq?0`K83&b75rao5ZXtj0e9M*+|CPgLkICsR0s@m-xPjh~&U= zqv)ko_t&Y7DCaZI(V~Tmb}dc~DG1@P=Xz=T2qX8Tat?$N!n2CKdxAWtCSd`H{A|9+ zy5VaT8vL=LOQ{84)Y6uSMZ=;`WbEcBy5~HM4)-Z5o1!C+>aBHp8AA{{IBHEJM*5FF z^we7)u-jj^LW)aeA`I-Sl>MI9u#X`V;=XCFxnwiIyrRPq&j^TnDYzlUVP8}|=~~X? zB~j@EaD>zsQ^aUbt_kDt-4>2F9Q8=LYR1FO7fQ+3n7yar{~MYt{YuhH+NfrItUu!@ z)#$w`(I(kv`E#ONWG4QfB*eqA85Qv6W_~U5r?YCWyIj#;vVI}TU%Ets>h(aT>53(_ z4OxxT9qV>!i#DVK!m--scNXN+zSodyyMl)ND;mLtrK-GaVHy@3$g?65!HVTKd3O|T zkqPAg)_RDGf)kQy$AVkUCgfvpc5m00kN;PIJ3NCKlF52QJN(}ukTDcerTZvIF8TYC zbMu%wc2!vfAVITu^Eo#e~? zJ!W%zMlaA|64l8>xXx8j=*|3T!VrZ*s7cEqhw^W_MJf*tc}tEFquQq)CP1~r9c3HE zu5Hhs9lZ>dowK&13F9h?kIjzO#8u`x>du%T*Y^PDs-`$?8B^Is__%UFV9sV^xX>%Z zRvU?HYk{6PrPEzj%XKpyr|sg`8Ee*~Nj5NbjscLas*L6e>0<6S8vsCD)SG z7{a?c>~U}dISjZST8TH3?E`i-zjLu{ni{cu6L3V`(K=ElV0vzb!D^y*R?j>{Bi~}+ z%fLkN)x?L|V%E&NIBDG_S;n)k1v-v95a+VlGj{IH-TZmm@#-9xte<7d2`n0v%fMeO z8-3aXJY`0fR4!qsvyehM}3Q>!F1q;72ZRw#Rjc0CFWb6b2GsA_@kJf3 zKTWnADi^pOv(W_Emj57wOds%mPmWYoY_(0*?Kk*3YHQtIqX)ws;3E{KOXSgSKh_p2 z&Xv#N_4*XAMapSeS3Jmbi|g;0IGYbox{aq1C!J^_;=GZtm*~;@mzQm-LKL64o{iJ$ zk+#y+*VQ)or=lI`vRN>rSf#Wds;a1M`dq_)@NCddu!6_};n?eknrh>M;-%8+c~4QM zrP&!>cH+p35bTs^d^P8S9m%zXY-fm*q*O$tZwONkWn<>h@%yFUOe2B8#Qq#f8?fsg zuloHFHl?edGeCSG*pfz}h?Y~-&yqVyx=Y-p6l7@J{r>H7Q8`ov7Z^Lvx^>3yQq}MF z%EJDEw0HP(`_zv+w&pUGdyf2>6MTobRb|n)ZDt=z)3Eo2&M{Z(BeJmebezI4+6Y-a z1{lnMi*^b26#m4?*;^m~eOa-}sQQ+-Y<<+E)nrL~(&pb^jogfrcp${6M=}Oy z_!%=B<8|R>%}ryAO+wRN?FyD+fW@0;1ehydf!xpyA4uLLW z+C#Xg;9cZA_&cX!{G1zEZsXGEP%=gbiYKt7bX5O!k)F3;&vI3AHyN)t+T3OZe)LT$ zYs+(lY{QJWp>(`1m~oR#v3?w7Dr5>)SWc-&O8bb{CF}rRRj~WSRa7kcNLtm&|B4G% zhEhfSLlGXxxZJUx-dYzgwCW6U50@n~321Pof4(Ff*P8{)bMPBW|pebRIuob8$wy0p`8-K&8od zXdzqE>^O1dyHy>>m8`69{Z!xX5oDPVAy^cTd*^S}{|vc0$`bATXG6DaJZW7mH$NYD zDMuW10gbhp^I}!l^xf1~@5n7}P1c;n$1YV9tBON)XApx)018*nvKfJ6gCfdK=-$PnldO|a(3D7C%K zCCS0Y;&sXsJ;bN2jYvZ>?KJ=XDycf)m!)03(pObe#Un_hoC!9B->v+9drphzT%R{R z${&H<2w=5{D?c|0kC+6y)n3ZFj*q>ZC0@&NZEPJa5=M4lu)o1nsul^zX&?LSqmgG`>^7(SW%|4dr6MXgovXDprpUeGd={s>OfsK$27oIRuAdShAxh9`uV5)4SHuM zGCo9gY!g1%a7Whj!$avZnW84FV3y^dQBKvI?4VCg%2QjEw zo?5^SzhWO>%!!k1zOkxJ!l|)Kk2@AGLG*5iV!G0@VkWn=4YXR2olMsP`r@Hef=elI zn5%edj(5hjgKv&_#=hVPw`hncbRzAywCJX@UA%q2a^OnM?q z8`_idOd)fw)^FyfWa^neVHx?3qgpdNGMi*XGSFX(m*E6oL^5g>i3}d81yXK42GBv0V^H1r?<CIHFdvFh?%sk^=njGfCUP{5w)x5XmvGFrpG>#1V>cG z)V%ozrXraa$YSYKC+az^P3SR)ldw2D2qO8$ssY`BS(DDuX)fNB+wIw!f*5I%MsZXU z#*efy^*Bexe4F{6o2tvL_PAh0^W?{wjUmChb3GaU!kyuVImZISHd>N{lVHiYdX3`Y z`F&OqoK~ZCMND>F&02(ko@)}lNlzXu3J^;!Dpy)mc2zzMZWg71H?(bwiyG@DX?G-V zM8PA8sjg>{A>nZ*k_I=f~XcroZ_Ik+WrUmPIJ`=@|(e|Wfxpb+BqbDMFh3lWT9n5UTH~!SxYu) zdsIjmSm&W1Cq67i@Cl>muSxIQZlJj>HTK+%nqZI=UMWd%<)}E7mDFaJI%d6S`OxyU z>vTLcwtbtqSsVe0H>_rWzhLctPhJU4;*E;yzSJ;Kq2dkd?nvX}|DMuRLxtH8juG;w z9PI6W)~01N<%9n()17A)G#|{I>ahb{up}h-mRchy)j7Ca?^gLS;aS0rw6@g6LY+_r zd(S}2!lW57fNN%?;!tkDyer|n);|Nz%DP>ri2|RBq$ro6j7K~)UN5T41FBuBz@I`6 z549;%#fE#__DML~^e~!!=0yk2tGTp%8tWSS!gL8%w8?83jeYnox3cV`0)KZ`#2~VC zR?AHN-UFx69;^KvhvdLpVJ;b~qu-acq<}{R$A}@oy4E%cqmoq;Mwe0I>k{$9Kknn? z*$x*Q*-nnwM%2sSz9r_*4X6CJmV9igD4t+4;ycv>=-cltPl>rK2jF#Z;`OOVjq*G5 z4;-D0wEB~-Br!pZF=52iJ_&h`UTv7(E4UoGH0J>`;Iu4sYrQ9R``rCg{TtbOt)l?- z3RWd`Y!~Ob{|(f?x1dSofr~UEWRaf+I}ds}xR{-j3m2NmRmkS80zF1Ei&c)+0k@NB zmr2wrLzx4^)t#hghc*mf_2h^nUf<$2r#++SO}& zewvMvRS^{oNSI|MZ|BkdTh=TIyY1CtbKZnvEJh7PAyHJ9>h02MO&UaHtVOGrp5 z{d#KVU}L~ss)|vrhAxU-A}!SZH~di3ltoz<>=jzdb=2`*bxb~nrlxCQqP*Hl$_8V3 z>|Id`(rOU*d2#djr~Axh=k<1#kfM|uX-LL;DfiPex|l=w`>UWXtMtK^`((vu63Mt~ z8s9Ya+*KE|a!kO4R^MSQd^VLkI#83V=t4}mLzQ|jI-eGi48=j$kZ-hp+0dhYv7|~U zE;5(OINp^cN~*lhSq=7WhA6CLHZ}Tc|01GEep#QwZ!kT4f!fD&ww6COZ6nW)`^?Cd zG}4RLYHAKoC|!#(bpK>W#@C0fLay$zqAFC^18jxSHpqhaFMXPDQ1QJ5{2t)Pdyf_6 zv6Z=9%gP57nyzYquEt}YiB-I+gHruku$b1ceWPNGi@Yo+%pxU6&`6Bm%ag9%NEP*F# z;^0ci13OaC+g_Mqh{u19*J0*LTIgA*wKZ~n`gE!M!%UnB1^bSsJE$6{rVy~&psm$Q z(bT&y%|gIqYo!V!p8{#Azfw0zr^6m6K?P(Ro&?^!85nxFTn`Ov*L~tE^MEpK+{a)E z0Oo3_ITzX)(ky?;kx(4^d_~vIo>^t<%yB`x6U!5TFfHc|!8-4qiiZ8a6xNT@mXH|~ z+p!3zz~SAOEGr4(=GX}SR%p95PqNO2@TU1!byMRZwH$QvK<7WsX+~?kBlw9-1ScLA z63{jGhu)nqYh0<^JUJir9yo}5A_`FnTWvM5|4n7LTBv$DpgqN^V|xLb4fw7g6MwL0 z=$GtxwiMiRgu-*R)WopGtpj>x*%{-pRw)(sPYnqt?&O_oEydr5dERn)u1tj9kBP}D zzA5|ykei~=+S>8|7Vu;d_(S6U`tPPsnjhV-5K-p@kuR4u=oe3`JaiBAK3pFp9+7I5 zR?WFplMLl|QPCdu)-0O_OxyCvtk>yxQ2)70Yb-_Q7Vi)XayTK&N}|APbl024<^Hf& z#BJHA75H&kOknqmmmOP!4J=D%)J-6&4p`00%nijfAufEQ{!`iWxW*D{_}^dsB-xCZ zn9lD>aH`|4Qm4LMKsWU20T9tp3t?L21}n6HVaGj+uY<<<;U`%XHS>zW547jh{)v*a zj2tYl_Xnx+B;1QdvP+<6V61+^`)?UkmZdD?+@ySkWpr?WZ#WOCHTcSnE&(T3if)e} z#Kh{=TAg#UiB8FG8qx5@EH6p*rFGwR-;HhaYtNRy48429GOatkwU=sl^@C;aWclYG zkE5}ihfL`1r$-Y;U2v+mg5wyo1Ipqs$);*|#6-gYF8J$uL97u&wPRUh2kapftvR~# z`&gMsk8j3z3aV6%C*S|G0fIpk!>x$P6oXY~S8}y~z=a^ai%RKqR6^mepU#drKV&g- zVP(IzdAEOl-`zJEqr$}PGupg%Ham+x2IDU#`=p3{BCC~3PreA~?R}zlm_)G*Rc1<7 zMBg?Ihs5RckQsx8ZDB;Pd7Oh}%LJLX7Ef<(N=;%r7`op9=%6Du9+uyn&581h+U3ppqd6cQlUM&BdHQ|<+qpLK@{Tu)(wlBLl7 zJPE}RFC$f5r^&-QZ#v%S=-1T6k&$_@g)dmQzv}wK{XcFnve#HU%@sZq)V0Pg)I40v zMyGarvV5kSqIo*PUeBeBXa+5}xekHv~*Mno$+ZWzdv`^89hX=2DH0YbGcJ0+5XgY3ASuT;6Hlm~?-&v#}OTQ`;!~ zr5T(i2UOv#ANvZ`E21@tR8Z{?#B|>fJSfnEN2l8Rn-#Dfd2syUf|cr7PP5|HUsE5^ zj*x^Th0s2?DE@JV`de`hI~cAdxhmrG+d&6dA8X_j{JtNh9X*;Qa}4_vqQEi!-lrq+ zij`59_k{1PiO)tX#^ab*eFiHs4DQu>*oBGO-L7ML=v zyAf!Ik5<6=!)6<@_|it);*Y;#(KX%ydQl{f^S7g$XK~r?3xIGkMv(>YKy^!8ydF)` zxHn?Qe;A7XCVFh1Jh?m9llQbPr%NgV6IzYUU~-T^k{YBO>4iM9l&(D^s?!m55(ej| z%BQ;?ml1f*XMZFl5%6!OF0}Ypcm&8(nzGM8F0-n@S53R2Epi%6smoSN+N~}_&?s;R z(Kv(xXUlesp!6FZye%_e&v(;*C!#bYv9=?x?ekr6(x%BIR+A0VBNJotAvu6u``fel z0KhfW7~UxZ=}0c$1b;cLrKx>I=w-`AHg;aWY0|+I8(z`aWOt1B{;bn7Wjp2F@7(HR zSzUyv@ss2^Mid){0*W^Q-4n00kWJ$>j=${Jn-w_~-)CyrFNn)gMz@Gj^pJ-Zd{Ulj{O~~_egX{E04NdYIZiRehfU_3qusZI2MSjIF$=1 z0hWKcyqR#q`b2paj{})dRas)_vZ>7^V;u%V;yywP9C2C3_6|DNs0z@xS{;ETeIny)FOD~9W4#?#F-j=qOJ?@m8)xg@(Fu=j8S z$g?a~f@rh2I;a`gz8;oxHd;juGN-A6B7ONbJfin>XFq<7Th& zFXIHh=+w^MrtHjVqkBQ=T(HwcSYM}&YN(rl7mRRQrRrmg%}lP0(s;;DyvdCajkvl- z^9HLlKfF;14~c>eW460}akQt8;sHv^)%e|5s)tc61EHb>=F(rZJd7cZ|^*TQn$$6Az!)l+ikumnq zwlSm;#ka0WID=CBTC5$YJ#$=>U6KjnQ`OWB0)-caXDY)iido5WkiGob(znw$$G1sK zx}l^R?_R?pqdDhIV!#URU}L$5QMBhNO%?Df#1rf=sVw49Yx0mD3f`b(^NMBoM`PmM z>)V~*AUigO_MQ;aq%6ikJjD-qBC#r#ufqebtk&sRtHs(ho_+vZrXFy?SCqDb zU&<@EyhVeE4|FDt$N!{7u2{D*II79-Goh-Xs-WwC&*(F{(`;@Yx^TL-POrPnRQLVb zF3J1IHn@nzKg-_|WR74{y${219tVCGOf5=E;DA{U-{mci2Y(^j%F!N)k+sTtS^G}& zY0!sWNs7*-Seq9Z($`Ii)9rAJ16D|X!Kka9nhCV@#7mLIxTsLtLr4HroGDvWbUhD)cFKky_ttoneR(o$^i5ee0``GpFJc-Y(#;+^CCjnvvk4~4E zZEZ`;gPdKnl2OnP0Y?yCx)_ZfT@Y%p7&{fq8<{1k_EKYd7vUOGKhvay^%a~DV{$FW zf{)x*`J7bD+!b#Mdmd9)2tQwVXLwvwO%mgPOsD=oWWY_@al9jQ3L95RU!&)Bg!rFw zCa?d%*arbV%d>bbO2Vp$GV^f6+HMD8x$D6Nr3-BUBbI^1vj{DL@JHWDSTd)ivOKXfoN;MlDUB$iKz8M^fff{MFKGyYl} zsWXQBxc$tPX129L0_G)>k-#+>=FvRKwt!%i_owN}|8~DB zk;dpr-_hM7>oDW=nu%>2uKE63Vg70_3YDbzbsw~?Uim~n))<`bk&a` z)jl?~P`TbZ24BLh6Oxla)WEi_1gllrRSrjaz*1!&)8ifz>FGZv%Jqkj$Ta7F-gSP^ zUaU%}xHlt*d6qZoDSOv6T&I>k4=U;V&7|n_BU8X{A?o1^e4IauDH_(cvJ)BciMT9@ z@d9Y@b+iYXn8Au7X7dmUjL@gt-Zio&O$&!RG}_*~t?u8sX5H1-Uzi$gihTY`^UGWHY?`z}3k9US(kGs$=Gd!t+>`C6xv>`%0;) zLa4d&%Pmt)`E}Gxk)^hGE87dPxnxCw|9oD;x0-5<6>sJxik5hD=z!YwLR=*{v zG^|M5bV_z}y_)%v%gCffWz8ltYx(kk-abRH>>|Sy8mM5zrW45;rzuI-v_Jb&+6ckR z&kzvW)` zZxJ%vkoyFMY$cvaMAeZ{O%4(%9i)f37$o`d9O3@dhK`%HX3xS@FfR$h?dG^uKJA4d zTC?ed%@2ZNu5{smD%3CxfRd-Aa}w&z6%qsF3C8b-Z9&tmEBkA~l6c(2^2pNGC9qtA zUtk*c^W$xgJZ6y4wnk=Ft9uu5f|4nQKBjP+l6)@?dQEa#hSA~;duvHubdInxxu5+` z=NfuIlApf!jplzQCKP@w$u1qiNPVw(0m>ci*9CeBcTCwX%i|Q&!*;qx&tU5@rb3R~ zvKvXK64kS)`~B(IW0Wv*@}ucf*=vgf#648>GV+a&*Vl=Unf*k>KSxNJOYAp%?CbOr z`qNWxbqXK4c?10QvJ=5muZGTd9&Ywbsd`A*@cWOn4pe{}pmTd#=EbA_R|T%{8`V6R zcSn~J_1wS9Wd;3X4-*?ORE%KH+vD}_4p3nTn(=z{!$9T{^y|EtY4zco-0tJR&Zyem z>WwTHm>^!Lb(IDWEEgZFN00?Wxcus1?E#B|bFX~nk(IiAD!@eqGY8OCBd!9L z9CAJ6f5~IJm9_TN0cnWZ&JPT@c3%l09XGDtV#2ycCPw^iC3(<=dI>*l4tWEbrj@kG z_O_y8--@S^#l{;mMpM-7gTu*xY#wgrA@cAvzN%^W9I(^%j7>5bBq4*DnwiAi6vK*U zPx{yGCcp<1_-&UuplvSO%GuufXy#pkJoNBWL9LV;Sn8SWCJJG%pMoT&=_?XY;gDh( z1`Mu;yYFIu>bk|GU6`UZ`mjvy5-}!W)dY9dDsP_YpU05R^CQGF##=+7YY{9L;47Zp9Hwqx}#67iZT7tq5vYW1ddh zh~~~UsyMPT6={n+@JHF*89gG`3{(e^YKcMi#E!(odwGOIP zQ4x7X5Fw0;3}xk1WP}KjJwuKlTcF4?tmkcOv4B7cCI}HyWsd}zVdp3!vSrIkDiDyB zNCLzVlKyV`pMF%#ljr-r$8}%#bvfTos(HedXdWw_E&gM+HN7LlGo`T)b|}x`v!38Clx+S|fwkr#RoAoc`A6!(qf(_@wzNB<61Rpq& zCAJ(P(E0*|DrtUqEL2Lo>$(&%s$H+MiI+XZEa8ZaEuuGiR&%24ZCjn>hsQ`C{75*| zwlJ)SN#@BDj3FGojh?1IwGC?dQ%u+%tiXh2ZU6NJVacWUxy>7A^%92Pz(HHhu+IR; z6%kbZ2ENMCV+s0rM0mH2vh@)d1ivMFhv+q>#drOa)%ILVEe0#F^omI-!&{tabMziU zgy6shf@*Y36~}ILo|z`HXMrEA`zQB{G~!MZ67|;?!jb&tww!qFy%ie(5e@2}PQM>C z?}3tJ8%m-ai{X4-<-}=76?zuj6J5KA28X3 zDKrs|zHE=Ie`YRkBn@Y7+=mDkb3_Hs&UyY!U_iIUG4%>=Dy`P-*G;CZL=kHv|N4zV zmpk7yijESQ5k${@uEJMCP8Wajgz!kIRf(=yB*L0X5NwG`d2j3Xl-6BV>Uq~oXBV{f zq5|(sNlHo0@1xR3@9yJ(aLL+r?)DvmemQOnTF$N=4q{a9l6@`z6f=_4+$PxiT%T`* zw+l5oH6wFJIL@>(aOP;t1{r%H+qK2t@8aPbUk{kUz2HSl;}NGEWuHi+7J=n z&wn;JJmRwS<9leKssls2@~KzlcUZa< zMT1{1|b&uiT%yvdrxao*t&kncB#fS#Ta&}>wg+a-s=5+I@BdT{NowfhTG4I?Ay|Q@{nfuA>(s6eT}Vq1BVKc zUSR}0z<*coP%X!%agK2o=9-?1)E?LacaWpP^>c#Hp}D3Xkhivs7{>3DLv1h(hMpM5 z-UrrX>H}(XOBp@Vo=I$_iQrf*=j*#8@f~i+%2V=y!LtZeSYyyo(r(6kzGhB*>P^#; zBzZ;KclO?VQ=)ExFDRRY{l_Fe52heh(`iWGrRO;a7*A{-#OQ~Hcx13qG!9JXv{Z6@ zkpJej2A-K89PJe*!JRCKu76Qs{ABb^OQ!F;!mP!I%a9kLJ$2CQf8*qh#L5^=t)?D- z_Ou&IA{y^Eq4UJ4yXqu(>$iR+QTQ^UzJ^Ef3zR0-Yw@(CMjdlQ?~n ztrW+j+#89sHWDUQ%HOFhc|gj2nNn(a@WR0DhNd8<_(j*AQX9Qh=dgK1DY14nCISbg z)+(}Ybg2NovP2EF-Jv;GbKmfynd#Rx9s?I4WpWrRcS8ULL{BAW&dv4XaRVksfSzzT$shV*yzHMviani+# zup3N{(HbS*f_+_NFU3vQdTl4r0h#=plc}*mZ=V~Vd@LgPW$tse?HhOlCp?SO?ik+c z+6M?L!(&GHlr@;D<-G|(vIhX-9J;2?wwOr}`Z(eCQfx7?`u2g&1#YH<>zyRAPo%0~tyST-Pzdg}kCR7cM-Yvh$;MPn=5G1EGQp6Bvw*T5 zo+d&hrcpb)Nxxhq>+mm1Tyxy&I+wh|yU9z)dP8>QX`8nzQAK1CikFI0H>nyVd;HL} zM=9+?c0)Wb&%7#?x>8cq_$sA^o|r?e%3^j-edEjZFh)P_#p-u*2D1oL21*sm)3b7< zYlvBr41c#e`)sQ`r~+foh*e5O(1Q(csE(BlyS7|I&ENwDbJkg{IH|l>r&~y_bbGSL zyP)qNtSUv@gj0yI(06FA#YMBbQDE=Y5o)k{wXvu2Bg#b=OM(WC=qH*56 z7R;5m=Sez%5)d&7Q7#~lm0@VbcV zKi9rBaAYg34+qU?Oq^Zr;lTpG`)y_4?+=yD4`R2<-@$+vAo-iI>Qf`?tns{=h84^!qG@Vcs4IPn!sKW6#C%$ zKBcV>+Zk$C*uQ%@7WOpZ6Fi(2nz)Ck{>N62Qg=g$Jvk)PsKk4R<@5sVGNWVg;tnaO zSbhv9zR{Ly!Dw8?^aCM-}ldOJy|oW!y|}1KL*ahPzw6r ze&_hB9Y`|4y{zETHr~)Hl^57Cyg2zscZnx^h$xZ{uOE80)KR@&T<%YuWq zNT0JYHBC@G`Rovjg+UfL(oFuOl5TGfF;Be^gf9Qo(|XPPeqGRkXg|b$U|xU9xx%~L z;Hh3BK~sT_A~}Ife{8XRndii8ei&!faTQjeovTel72WzY05jTM7ET5ghd*2&PAu^? zvZ|B)Kj#$gk0Q5^<=2AKJsdhnOO^Iv7cZt zDtX-Jx%g%)XOW(rZF$)INeHT*W#TnXG)T-w_MH{nJsj{iYAjNM51dD8#jKYqG-SfUe`r)n}Rt~@2LQkXb46(zpTgGVh8M9O6 zqpqC4D@uU$4S&7ho`vQ6XJW_CDEpkd_DOtcreK`(PAusCn8fwE^QhTa4*$a*Jx#`_ z9QcthH^eR^=`nMbvwMF+Ax|dt4U?5rl?4S2r3c#&X^j7v@vJiV+eMj3Zf)*hA3A zYjYW*uEo7Ak|z(XrcJFVcf#a0m7B_;J3hU3J@dfhXFM@-(mq_ji(V8X*aQvuz#`9w zLt1`bnD4M>*-kTSh0~FhSLFQ5SASz9P4pp>sU&e#N?|G%rw?^fqjS0ZuEi`+*^l5)q*J7vLti z8vFfGMd@%PPQiw+C;b5PA=LMt;4`3F6?~~#Vc7Z1n6=00VlgY)X&Zm>qOp=00*LHn z4{kOqgJl|k559TGdLJh(AlV-Y`FJd%vFw*ViB?g$Ty{?R{KJ9$?dE+Gde>w&?@#G- zj;OI5w4}w$p2)k3>shk3UaN}i+UD<5@AhQd-DmW5X%4G_Ts~$|Hqc60_|6957RLyJ zG7r9nnMTzNXNfQ=Bc|N6pW^zqqZUanU2u@#)3hfM|H_FMOP~^#LjVn8AP0v$* zKv11tWbP@`BW?QXwqa3m!81J{Y)O4`GY+_InncSX@03fp*mo&$*E0RQm`wmRnU0Z@ zLf<~UhQ+l+x!3|Nt%LV*9k4pSl2A`TNo@^f?stVZJYllr+{9ugD{g3-`FWJ8HJeR* z9hFt|z1UW^c+a`aQz|!zY|A`d^^u+TCDeprWD?CD$1%}xCrtnuu72P6pGu1>h1s5h z#2B?6Y{UgZU{)xUMZ(cuhR;$+Y#Or5X_ZvVE>?6`n9=03xwZm?*6JC5SYG{cC3Ev` z4BDqA^unDQainx;E`QsBJ-Yt$68@?MtAF1$o?*Ap?Q*$O_{1+8kr8PHx#9Dije|Yj zBkIq8`P*XH=V_30$gSv)QAT3@1CAS)kqWDf7p(d!`aI`HYh*V7%A4afW27psR68@s z#ML{re`ar=g|6sy{`k#hLxta0jVRfAp3ia?g{YzG`KCOH$Sjufw;6Y9c4y35&{7f7 z8pk|Gl|!bIhGf-#QLl=W>#wekJQZC-nSSTOtfTmi?7b%}X7|%|=yo z86EKfqyH=37vbr%>1j#-;KL6AgK7am%|Bo*J z9iz6Gm{&_9fkhkb&;GDc0(eUrKW{r|Pp%|sRqgj1+y0ZU@@$J*CkVReJ%mf|XgC_o0{T6AHgx6Pk=2bG=f&dgkXRd8gW{=Ozqy z7^SN3F?E7a!Do8n)1vzCm+b?$-D&*~7uVoxhVI|@I>}r~;AS+SR8`-O^I)2tUcX&gY{kq_un?hlL`ulA;WasJcDq~pz9Ts4H?7x{NmubiRKdUp)EXA~PTZ2ne=G~wWl6tUVssLiW1jXf z|L&8un1WUeASC}cIbdY){6%mLen98(_DaO(oLhM#my*~%H^dP2OQK)?_6j>4d7d$u%Z^i=XK8+~U(AIAK)V*l4mFBYZ1ED? z?Oy<}(aSNEkI8saY}RNZm`ARKDL1CF-P=Ufj>8S5HN&!=@@bgjZ*FqKt4?dx4>jyP z;fT~lXMQ%1J<1-1JTMy)28Rmxrh@&vWsRmH>tY)VLI( zINIuF#|X`iJmF3ckkDk~{)(2Tc*z`z#H(N^&!uLp^f-CSKLl}ZGP`%E4ZWt3zHT6z zprLuCk^(P8ZAcW9BTrb1u}O*>VvaJjztGX4?x(_N?K_WDRn?xWdAimnJ3P^mA!O&a zpZ}E>KSy*F(?q-~Tm3Ozn{;(BwhWSuI|?o~HD#_zdQcWgC62Wk-Ks*{MmB++2%9Lb z7xH*)K60mXS+cFj{pFP}=6T-K2u*cfJacTack0}JJgIYdIW-p;0Iz*WL83a2hiEcA}HS-iL*b2vQ=M_y=*IW5s@bJPT70DXKx?SP1s2Qn< zp?UvTPKU(HH%V&D%y=1WKNBAlK}R4QdYC8JNoT!dOdc|hj9RartjEo%l!U_fJ=iR5D_G~OmfuZ17W7ln zqUHXU!GATr{AmK8DFclm#V}%6NBHiFBhR^Mdip9b8St{|b}M7OQVYzJdsY$QI=td1 z6JK(dUJNI#eMZ&iQZS8%JZ877w%jV{P$#+@vC9uEjrUTs==)&OxMXX0ts{P=kRyOS z$O1oHP?0wqk3HHUodp z>a&-g2lb%4t?kHgzn$~f7w7vtZHbx&R6`lO!3me-^oYf_l*GLNso$Md;ekU)^4SCu zGNduUyry8Oefiz%mf@`p^IO74G_@XCZ3BJJq-L6Ut7}R1-sBad0SRpmIZI|1tx9fU z^*Vx~OPlbT7=B`&O>+vd^3$2-kxsSIX{stef;1k;Aq{8)%WZRaZGGD~z7mY%SImOL zK%4%4EV080vb+s+SW!#Lo9MtD+`f7`J=@**zc9L23rTL zTvGhMogq$ME|uz5z1{FwuuqZoEx`$9^xm_du%db#Za>Y9&3i+?mT@}aBWwBTy)Mt` zyGE6_xv7Es+SjTvu4i!5`h@yKiqh;`a$z)5>9sP_rB{bV{wJY;@X2Mgihd`d}oGx2aO?tNZl*x6wo1R$wVRq_Z2Ov{%P)O?*Alw`{SbHd~f{ zs>f???8m8d&taWJ5Xx$v`g?#gOE-4gP~|4%A~AsK2}9zEp7k^pjTzBtQ<2WAM7)5sA|Y(P9+qj4OE-dj295pwYkD?kx`Fztp?+u=WJF> zb{`|Seld5(ve>XiOMC?bX2CVmea*4;@}(OYi76tGuaP)EoeW(Rzsuc0Hl6f0SRBmZ z3YdFIwoT~Igk5J2O{@uv2uYR*2K}eL9ukmo+EH}W<)1m}njtjFzriaAAiXRE~;-iwdtDiMdl;Y40+%%U~mD=RRYJay%Q^oo33V zC<01ZC%#T7YOr~vKmxD)8HH2%Ec(AEGP!=; zaLz&I?%-*m-d*!pn#k;Pg~+2thg`=WKeZq+xu=}8a?B{WGkh7lY#~B(ix5unsje-# zAZ^F%i1Aok@+sP7fH`ii25!|YNvj7WS4xMR>6(NAGQBw26$+>*T440JYygZ&m1d>0 zPDP}c6t98G`ly(znY{#^0ouMr1*C5BU=Qb2pjolwgMYfjAH z>TeXXA0xEw3CRoQI|b2KvMC=hmu8#T4w;u(vHuu9t;(d_=6WGpnKEy661_A>!waid zow{6oQvEe{N$l)2dT-46;%MkeZd1&|1Vh3F_$icz*Q=M;V&&scek3OOg92njxWr&r z>9R7#J^23QI!%Ss61SH6=-k6YGudN$Yw-<@>EGo5LI5E^S@teNNu5hY;0rJf$xEG| z$F{>cU?2lKO+Z`5X7);%@G>%jnq7NGb2|K$c-T#(rb}n5g za8H1~U0Q)Dgj|6K=gM5!KyYcKymhJKc-ugoXc!qXL$}|Y9frW#hv>0hF1g&LNW!=j zPA+<-*j|EPH;m^}!X-h%i8ki@H{NSwYoUKMng^Ntmviv--LTn0x2_lcY7hOmW22d#`~ z6@ltN=`I@K<*#X2va6z%{f>oI{7IxC%;Y_-N5J~|>LTobwtpVe zA||R0%5bE86$@3PUTo%^%BHwR9PZ(2&`9c&F$d|@Cs2Ae4p}Eko|2MweGk>`uZJd5 zF^#~yS{e|kWcJ$9bR^D$Z~8(F(JeALaQ)d124aylZ6MhRw@v=ePV(`aAgu<~rff`8 zYpMb^TpK=>8WrzMc^Gs$bv`7B0qAK#j9~KU#r%WQOK+A%93(d++X-S-L3$EPif0rUvWYbwQI2)%?8OsX(L;0eCp@j~tJ(?3RCs8|_U*UP zzxg#|u`Bd^s2z$tS6&r1s3db$nWR;*v)mOZjkV44^jh%AwUEx8K;9+{3p8h&E z!ce4AZ2|2~o>m&Nj7%E+M4!g^%mCXM6!r>J{O>(W%t(@tkC5lzu#>Q2)n>E9MR^sV z#5~stS@jb3@7(e^Z}r?ObgfO8$rNl3`VZU=t6eAFz_}EkyiutCf_aACmSxqV{+tF4 zU^j@ZuXij?ngxTSD66(|HfW>>jpjj`%#tEKs0*eg0$(MGs@M5{)!%TcRM|Gdsw?MR zD(NQg9R=bYPNq>mJ>fzPfC?KAdX76D+-KuHL9uh3&hJWhSy#X)BUB%k zYfl=xLPeq^;Q-{{sgO6fI{^Q0@17olHkw;3dq3t9K1cYPRO@yle;Dy5XWkuUEh>Q% z<@A0a1;QwGl*HPdydjJ7q=_7KZzZBm$aF9s`0#Tb`+=p(RdmH_gA=^k3f`A#9_ zTABZ@87r^l;24&{F)V~EHfgzoPH)Y$<(g-V|3zx4>TDtT5y)O$(BVakhJ$& z98x=eeK{9QkQ^hrzBG- z?NGsKhwmgT6q0k+`FRo{e^pLPL3)#=mfP{0w(E}-yJwf5?(9&%d(7~4CGD;bsyr0c zs>y!BukS`rDa^MB_B{g-VkXb!=cY^yMXjbDhM!A_l7y2U^`bAZ|+rWQKbfdnj@Mn^ZyT0lFIMl&~c14#!T$m zWorO_k`rkq>46hBg|y;zzRm5@RC&y>ALY^Id$DT-QY-4JiD`t~UVOs#LQ9{Q=bSt! zTCn;mJ*<9HO?D&A6XwPSvq>!h7MdQv)LGGOOTd6;YGb2g5fKblc9wQ6Cz3AX9 zvj|`KGPS{+DRKb82m$h6_3KDWt`EX4m2SE>V{$MTw@_NG-G(GudzK85X;61?-|rRN zsD?>~MnEP+5@$FWT$)H0oDjU(*M^0i$7P$1|M}Ur#7a}Qit3;PM;X0Sp{{nHD<^9( zVOFN-vBP&chDgs@WMk}mYmxlD*PBwP8=}VYc}EOmG_=c*G|__6)`(H2$@y-)$=f~*jg!ijryZ9^DZ=@6hki5jtmJ;@aWuFA*s1Bd9t@mdX+9!3z zWq%=Gm?Z5h=g@mc?@9-Uc0+C~&3B&?21nd2R}sA01X!`E%&fmQh@+N&%oTST6{JCQ zemu?2J@Sm}Wg&!O&Mc(lSWB&L)X3*Y#f>KgeCucXg*e5jQfXSpq}t$ADd5@oyIEb) z&lS?*X)L&;@D2*5yGd%ogtAONa`Bn%X(N~Sof&MNF-{+J6%kGn-nqGH=Hyn|I;WMf zqmZ~8n;m&vALTaFt^IAMW{M)V`uKX_Ctmlj1pV1;%ik>o3FK#`pDl_g)APJFaQ78u zb)OVvN>_I(6E#kSiC9@0iJ*xYn5S?@x5)eEkLVsXsoso7TU`EEDULumXnmq|hgD{)*Q$*~z_nW|-c;N9wcq%h zdeMz)gX{zZ+3Ib5@yVrm$*00jyR)P%=sa66pykYL3gm$@a>27KdX%I2lRR;{O7JFZH5&Sn2&`O>>W}YMLnB10c~I<6p-O7AMfN6~ zwM`&V&g;Y|yOV_mD8#s&pUo;?1`1IGq zUUO17rcnj^u*r(E$8on*K*}TrOdqp-;#7R<_m#L}ax(qIYv!o$-wCHOnpldzUx2wy34&F% z?E+__OLKTxXY4YfLQ{iJsVS3skM8~8cREF35E2R0tazP6^!|)XlR;AjFLo%pw({iT z>>J3|O^5m9|GJW;MONu>JP?@MJvxf?*zwKX#}GP1XSwOzDvdh1o%eC8Rk!`aeRC zzDUWFVVU(}=3>>^S&BS68);4*1KtW1z+L=Z2rI$(6)qw&nXij z1b~c39O0gFoJ}aRj1c;PSgfAzdfKAUfJnQSi!8Rc?Jr%)_LA_oo)FogZ4S1_gD(C( z!TYj4N2}0d+O&V>nC)h_n#|N`oZO{E6lc3@So=oqA>t!dc6}-K$OMu6+rZ*bvnt$E zTGG==QvD1Zq2d7lDQ12B-MjtFZ&N_#-X*bpoU@aV5}pe$WC9~%89H3@;wK-^xa)sCG2ac`6X|T$e%nlskjs%(_GF7_d~vkaz%)j? zD^o}G6-Ra^kF9`u!o?uNr4XoM&jmB)dDzCXW%R@}=((ia>a0EOM?|8}+{Nv{1*adR zA{w`kquay90WxI67ZaGdv>}6KDg0$pq8+9r(7a3~`$k3*az(nE8pu^@PbKWGxGPPC zDn1Ly=>`5<)?VgcU;GdKl1JbYWZw4DKzd4Rd>-sa>V16f>E;7)Ko8gXv}VcCgjt~f z>HBOl7E*P@elUy029((zX7nWF#=@Dj=`o=Eip-hzAJ1ad`^2WfvFJtxxk`_DT7!qC z1ggl2)pFI@8id2r{?)BxQit6A$?;y(NUhdPrIh4K{R{`Ivb0-SZLa~In#htm`wr+$ zfVT&TJ+?v$dMuT@m1yVgLqTi6V%nY(S~50klt%SbNV=A0RV^WbkIQ?g^Zl4}2=%+>aF*LOt*~V3U#T$IBBVjWV4>^k4Ghh<1`h_78 zOPIE%wra@wMiZowe`XKQ<7*3zaU?)<>((Q*k7BAL;nbkiNM(%BtlLDCaoI@RVplnVYRc%QPL|E~%I-b3J(24tT34l;XCdYgDTyHnA;kez=^zijzwxTG-aB8UJhYI!U!M=>5R(W}2~rVj zhU3R}$!#Jm_r7_z(5NT$b9;SBh7r1EUGrp;U0?Frq9fkICYKyC9i$xn+T-WVE5bu3$vhKC!Gc=HgY9G0|Y?RH!I zFCFvd%C7&@QvCY&b>?q>vr8!1t`;GxqA5QN0%NsShQ^CPWL<&cAYK_; zTRIDV7nfk&xvPPh8!#nV9nLUxPrA>7Nc4ZMDt}f-4m(G^o0$tzdVG9f2|Wnmv+uMuihny3mx-)bN)c0Ce%v_{=ejAG_ z^*ct?Hu&2W(i*2D?X_zw;94PvzplJM0Uy4Lu4%@50|KWJcQ1?M@t-q+LyeakTI$mI zlsLq8s;Xt?qGa1jKz6B4>`Ombl3`{|Sf_664`x3`zhS$tBGbpz_-hkq^z!f{o$xFM zFhZb-1{_#5uV>JLv$XYz->b5(EhmSD5i@5R6x@r||93w}A^u9=5;Prbml9|o#eT0L z+SK4;j>tQTkhuDoBJ{rRC_!)BdJp?0LueEPsE(FNj~@dUp9U!AW)c?;W~y{T4^99< zc-W;h%Uqz_y&CbfkSLKr{{2aVRbskq{GJgdB{3jcRyhDOYwlR%uHUP!rMBxTbZNff zcY~2g`9S%{7<%cdt9SHR9TBQ4G^w$weoogI{m~8z&X3Vc$$Hp82!GiFnLsDxAP($ z`i7#mwwl`dX^7+S6APTNp{*C}oXAtpzIomys~)%y_BDT;?m*uA<%n3V94Alw2mo7H z2|_+&RR{7!_4OO(H{0tu^7%Uzc1JWej#pUPyiPAWct5ramN1;Fe1#pcX~2;wiT1%> zSq^_V$R?{hWgMn8e-A9{9R1tVf-#rIl$$nL;=0b8Em>!j3Us!kFD~aPX70!60^oLB z?s_rrT=Wf85xjp0w{_09ISJST^)@{x~x&u~`_CAalM^=N{sHMmE$6PKF@6bdQJuxBALBXOJA-`o8jC!+%Ds zy@5|uF6jtDv zYAbSa84iN`67tdc{ic!lT)?;@qaRB+jJ*0mZd!OGfh7(QBu+0h95&qu6v+u0m z^_+;er5CqnkTn2*ExivVi*zWjZCCOGg>TOmNZ~0r#@`P@Ex^QD>jL<9eRu z=H~jP>~Zwh8Z&nMrTv68lj`lqeTu66FSVcld_&9g7>bcgiEorsyKz#4DWA->rn1=O(W^)~R~4F}+)XS$n7Bgcvm z*_-YgC{%nm=GvI1_IyafoH9o8c9Fj3`PP4QF?C)?2N^n>HDTf)-e0}p>;~DU7aY~$ zo7pVp8*h!RJs69=89mTqD}2I=5Xc1Ns@SAer8#SIj{bV3to}FVo(%87?C^iMEJJTc zU9DHW%8dDbCN17Ha~Uof-a>y4dF4T@lmNkFZ1NknE3ab&$Q4<)Qv=&pd<1Y$KAs4+ z5#2t%xSrv{Ufwt+_72Day%?tvkB(eD*LO0B2w8V2ev=+HY_lRigpwKkv}<#SzcOnjVZRk(a%kNhZV#V_p- z@~AXGgsPx=%yKIh=Z3TH5B=22ON1eKv`c8i0s59WR8`oPsuQ}D5p?Xd+0tUAi_6DR zqzLj@hRYt(W969DSmc`Q)TF>xuieM>p_b6tis03!A&?V0&Q)rn{e|DaZNFRq9Ifr< z8LojPj5qRw{X*hf&sYYvzRl22#u+9MB9{gGAt9hDxLc|ux|i7NnQoyX?RTdb{mlE2 zcZoFvMo&N?j!0`yjlSTkE3*tL7A7FHr;~E?hYh926dpY8YpdO-@(U(C6R-4W*64uO zj(M@^_UwxRS-NPg<7fN2fSlhF`pD-Z4SQoD4MQZcHxC8$w?>t^%%BK6Cu;_TOHPcn zS9#}X|DtJ5a@OF>&W~>C)mWyU)hkBnMJRoG7#vb0t+F&wlZrs<5*dx!xVRA=NL#P- z-2(^YxL@bew@OeqTBJ}2op3Sh(|>Az0k$jOP!XaR|3q~8>>SNiau1BH5)k9?OEOP3H(4sJ3n_;?40! z(wkK`Ry@}VUJ@$guE9jjDL>(V7}^T1hWCKQNdK4TuVNDpN|rm}P`niSO*$&yXEh0k z7dZfRmyn++F5vfQTLY+M>_b8p@E154hz&EOqNVE`^r+ECQiDHEpL*D8l&gL0RLoT< zdVr+vbPszlXcJ9=T-=R15T1~Ie57aT4V;r4m4?uUc*KB!5<%83cEsIrZNSC2HDs`t zf^LUzx!QsjTY1kJO-{Q&Rl3wmBAUGYM<>+UK=sRBs(O>B+K`jb5srLv%xvLB_Wh6@ zIIQx+sdW_T8MEr9+peLr#W0h(Jzm< zN2>kx#ZjS)?VB7#@?IU${?Dh@&4zTyf`m;d#chyI+2knCY@OD&&F~JrV&5d@46^_F zLN$2B?09QKtI(gKNK=y>CmTgr*fd~Pd^+epyN}Iodf&u})^eIYQA*Fob2GyE-QNE< zeRFtKmz9?Q$Im#VcBBoPf6`WW_f4-GXHjTE&$M%1{_=ih_O|tS0i%0}@Lt|tOlTFX zBF;7B+fz!kI)I<6KPOLOUNmIzM%?^3a3XX*!-EDaRsZi;H0DmL`k|7ms1kC3J^n2D zQ_b^jqja^wK-UCTYbI=4%ubu@Ke8uy_HpOV@1L6u*VBKj=v+=krSV^c@3(~5+N$6) z%R5%PMp(&f_#4jYgv2R@5fbtsY>;H@vV=Bp5*AZ=#Cu4C!wehWkYCxk3?Wf4Vzl7e z^{6qPr)s<(T<(5kUudA-$~Z0^9dI|&M)?N1JSbRtIud+11mY)at@vIGEUxQO_c(Kt zl~n5oH5a<|a7sg5Xpo+{MD0c?tK;to@4(qWfWc7giMK_9Z_iR52@?%Cx7?OEI=m6) z;yA_lA3N`#3BOy+z!~n>ydm!OeehZ0fM$nOiJ`K2i~sD$*FP-Mg-i_w^OKvd*PmY8 z{yyUA1s>1r@NFH&-s228HqwVS1Z&<3W@Z37CtQE>F~kKNAIuj#cpQ6ae?hoZuhJg* ze0>M=ya{762kMflbgd(gmfM3+Zsmp#R0SQwa(rLL!+*bHX!!^Ux-a1g*T$nvuaDY& z>UYUGn)KHf`^1CDrHaid)8)N*DQs_ZUG-{6<5wCB{&k=vBhEAA{!%Jo7I3<8D41_l zojy5l-0Ywu3Bn(tmv~_yY0e;|GT=oZ4B_+GOqtB-<=m0_P}G??6$qv}(|a_XCESi^ zT+w0m)Vkp`jLR*Ym=XtI@6aTsk$I2f9vC6J05%}Beo}q0t&OE))xyEklOx`5l?BhK zZ($wDO@O=1ac|TAg znM{}W%+e1rlkNaxugl3iumh`MVqd7 zJcNN}s@9L8$7~f~9ykSv{^6+4PT~a4?y|Rj$SbRn4-QWq@1W$XoTi>l8EjE1rDeo+ z2-l2gdq%?06;X#Hd_1Ee_bcwb1`2vfaJ2LY7g5?D{DW*+WJoY8)w@GGmEIViOeR&) zp(#sYStchA@+`kf&$t$?%Q%Y2`qXa=hyS4xBSCXZG~&n8@4P38K=;vpP1`CDB8i7*K3QUtaHyIWGOo2KM*sff=QxH{6 zL-9=gHk3tY1I?24e*l3rAhgpKkpw9A~@^J;DFO z-~0HzY+Kf=Og}MVZ`UcQUMQUY!74sm(}ZKL{Yvm5$=y#n?P}{^+fXgp4(qmLO3qf% z+8HEMjnmIl@4!Pe%Nm=5CsJdLKRVuAbyl6>H6EXAW*voJ1989}$@R8)L({gZ}&HV8wJ z7219m6kOt9|L^%_)Nxl^X10xVC|ievXycl1h8ms7!_!*#5f9YO^w=FlC>wXrSe-LL zil_i4ENqjWw$$B!L!CPf>d7%wpDr0E*TvmGkm(B;ZNJEy7%*)v{PQJ&D}A2gYQm7d>#%;fj%6| z&hvrfB0)-!Mwj2ZOTP4;WZrTqjn?d>$Qm_^dpW*`_4N&s2N4>Wkx}~b(?mwJ;yGlNn?i~ z$*gu&|JtCCnRoYffnV(Cvr~I^%VgIR*I3af82WsOES1VOq4bH;SfVUbesKG}+h+|O zGqs%k_2;c`RyMYrak`v;K)#$0UUoQ;T5zU5uk~81;uV)X+*TS50C;}hnkx=hYnhGJ ztLa2!)uL8eT%Iwej;lww2ZfqP#Or}WcJGC2KR4W9mnLf1qP$8Pss3`8Xz_c%d9v%1 zRU%nSiM=D~x9yLicY%3lKlF`k4eG$r{;CTGgAM;A_r_;+AltRh2P-++TB2hDc*F?= zkyea|siG-rfGn*@@=vYfI0+5t+j+>2)5UD%k@(~5^*S3HChg71YS8S=mT#?n-KMWU z=6bR*Ru>t4P1rr`){v#8EJnwkLH>)nxQb27Y!VkgVpVIV#I{$>-0li#|WqdnqY*E>Ar237vlJ3n`-1 z-E)3n9znI$LtG+efBIHA?O(YA9h+c#&W+)NCZtVU?;dMFsBlKbV8>$|h z3odhaNVf&MR^5ngpHEi#cZ!6S{TYREwar7`-4*!7RhrgXiRP6|iFFml+Wv7he|$Ko z7J2m7`iQ$ru9;Fl*O33PT@UKO#p=-5ThHo~=R=MY&Hekeo+s;<{z^ORmt|LUm!?K^ zW}yZOSV9)YAL{h!>~h$!5?OhMQN5#k8^@khG^i|}#rLk<7NC3Z_2oTWW@)d}FmZRM4K- zd)xMUs&1*Wzx8>lJY)pn7(NnDJ{O>HTBF>=lOTfEp}BUo)4-4jqTDa0i0|p~w7v-pF0wv|9}lbEFt5v^A{`X)xx*aG|H)J->57}V` zj^1E>I)H|y{-j;b>%7@IPw{0y7zBejDdmM{XGxF`qM@`K0EVNM_H54wg(@4z8lD>6 zdE+XDV8U8Lz6|R6(3)tYo-`zRZMQk`>+&zfqiyID)4?wHSbK2w&mw%6BbkcZ9fk6I z|@YZ=F zfw!^u+kjjEY4RfJ>U^5WJM8UR$|N;TG3wJw_P-g8-6G3^zR;z;hs#wd(O(g=doM%Y zcI8gQhoqcN-)m-0eDiqb-N&68>-U%2?upA!IAwVeF`2A$L&!F3bqy^$rZn5V)WwAj zs%AICb6TSd)bL!cHM!lyWairHL`g$)d!(RKkhBJnQUNgMUwG?<>zlAp&@4m1_GdWY z6kd;X9kL9E702Mt3jOWUq0sMPe(4ueaV)j&>fC*tud;yc21U*0Zt2d{I@cHgO-6K` z?rUT;oakTCSG+Rml8ZBP#fklq&lTf(1*MvQ?0)ydJgPO_66IDbhOIq}_K;3=)WrYi z=)2>Zy4&{qKHYUt0ksa~ses6?$WT_^iU5dk5x!c0|#+Vr|M66PlwK zXjuR;BtkmhdIZ(T(-YXxIXLgembu**uE*-QQ~>qDLO_&^v`F%(ht}zq13i( zE|`K~41Iw*?F{^fd=N?i5|3-}Vym#knr8cK)Y?gvRrZW!<0r=xd(79MRWQbbLJQk5 z{1kR{Nga?8VW3ss{18M)Hv}4+>hiDVnsftJ>!Wvc`!Hy7fQhKiI^tZ5&0 z0cdjAK$1McO53|ibZltTu1inPZ?{iN{ZcdHRM|}P|060GU+%d<8bk?3-$}Lx^;d@G zR{f?6QNp3iYPRpvhmM(=y$y68Is4Ah-C7aqj8R*ie`Qgwf-B3W*KI|D1?&XgG0#vJ9&%6tKqX zJZpw%x2tsIa<>mC-MEA#ED;jN2Fx$s8Vi|A{pni9J|Hz4rF6kUV)b8+(_cq>*&bKw zkSYOXp9a%(yXJ!Dz|{v$ugvP}UgI8wEZs;_?`ms6axe^KlFpq*W39!t3<;6t6@@OyF)0wYMAOtZW3H zD3Y19g#{k_79s4fhDZbsJ1IMNciZun?__;3J8mtqW#Cui-Iqx*pm4KEs7FZ+E0CzK zpp`NnUsqqp$0!b4xy;U(7g46QcgqDjYC04J5p&J6u?`a%8OuI5$(Fo?GwP zy?PsWBwEsbe*Dh~x|HKU$V^0HXkV?&%`M`%cE-}i+H2W#GqdaR#NE7D2e>PNi7OaA z5tKrm@@zAQ=1ei}u1t?DX6!i=tU3hJ*V6IW#ot9?9}8k~B@EpLTBG3~{rv7?`sV2f zr1weA*8sXQda*ln4i(<)(#gTRUqipQ4=A_gBhkb!Yg6X3Zz5Bm*Toct^&a1xP%S=J z+n?dEB8NW7_&d|9(G<%{plmb!Hd!u(bNSdy9o2DiMyRd_v^Nv3&3U8!s9miDkEd>E zIr6u^y#v7_S9OE4+t>DJcIY!Pp|np~bN)pS{^Ce3C=*?&8&N0JxMA8Okik~@)bQxU zg+XWps{cWNIr>;ijttoWX<2wrwO4J1bZ);kaZF_=<0G^zb34|PmBlk2S`T(2LO~4) z63#o|&{SJoif3qG^`OLfQau>FX!lT9jL~ff(K&U+UQd5__4k_IgtIF-LggJZpi#Y` zf8pr0M{*8sBm}R?FdRU_3ZlqPw}!uUL6pxFhn_3s>G2scG6<|cmp8h8KhPBl$YTLc zFj9#BY?y!(biKX$lvc&$5Wcrf7l1Q+ zQGpmaw_ag6_+%rw!#dAujhP})Y6Zp#+V+s7CcN}k;P7{iTs+sHD@g8Sq9A|iOAA+J z)Gqz;n%qHC`JZ96&PhL@zDIZK_kK8>l@(njx9#o(3k|t_k`@?>?48ndca7w34Qk@! zJqfitFO5K8iYj{{*tl>76}^w@WChe0>N*}?WFxBoV^#|^@KvqXxbHkS~_N3!bX)9du|!1|ngsKZsclO*+u z!O}7V!nDzMqsn@e2PJ)Ku}fsjJdRQ(LB9wMDFWF#x$SK*_aMAv^u!lCnJcD9Fz`3j z8=V_=?q!8)&aM2=!WYb^zncDYHvoyk!x5eEB2M@h$Uo8*tsLi`h0oBIf%(X*iVaR2b6Gt4ISuL5yg{+ZpRB+!p0Ex)DSaxCnLX?EZXb;qhrJ+)cBq|NY1$4jfyJMHVx(_nQsm1Xh#KG z;sw}Nks4qml@r&t!n!975R zkaj40@0&5jtInr>9*Z2PAo}E8f{G#xK-*|~af*Y-i?Bah`*$>rci3&>Ir&~pOFO+l zXVi*!l7pI#$%{{n67BP2s-Ie6FMf-&gvsge%cmf1X)Z0Hr}Ayd7aY(op#>U@HY_zP za@aQ0S}s?lSwOBCK%$N^U>OO5N=_CC_k7wm5;x)XjwBSEs=oXSY`C5dL%w244@7C` z1_b(-`XnYj{at*2!`*Bm3B|FNZHG_fZF49BuNiwDNIXo0R(fHr6&-DVOt*#)j4BN+ zn&B+#OKIohVU8h+8mc;vR{_1}5jjU@BjbBETZ}pM1o|J`{yv)a>hSua-G&DLY|<21 z&!A=Odo#-}IYwCw@;6h!rsxbcY->-p7?(Vrv2uTd`?l=liz4aH@#**r$+vuj_Y7|} zek@59xd}PVh6mgjjXpNCjLdx{feU3|Lbyg66zorY?|$3Qy3UIHtDvITD0feO0K_g6 z`Ikh9w7>XVuA~wN?H7*%y`|2XrU^%)ddi%ZyGR^DSq;dc|Hfq_Z}q?VuX2O(zp&#@ z6;J?`h09}CF2GarJ%Ya3T&uTRt1q~$09xo?jiQ`6z8h8pey#fMv>ik4z+`g|6F6kW zUZ^L7!@-6MlyrRCLD~c-NKOF$-y3}{Qz1D44sNBB6jh>wy~53g_T;l|lsk~oHRF;x zJZN%Bzliuj+Pl}7Jy_*}gEa+ZiPskL5eG&% zgfZ0L&t|52c!S>y75sMl2Ya|zMsB%gQmd@x$On4#0O`EGdI_1H{haX*f~~q#=$YS} z7K!h?q9co$&Vu?t2v@gfulb2JATbq{2?wC~O6aAkTqy}!i(o3;*l8O@A90H*>2m2i z+5Aff`meT}#cAc;<^4*uPj!=;*Ud}FA@~VVoBd-=n+ctu?smjdQ4XecTnhB?)deRp zg^Ea>)97&iy;QA4@mb-m`Kr~^uf#WG*NHcn?u{A!NwYG6N&C)f?pL4gP(C4(!ZYQ> zS=KU%>JBFGlFI!@Cek`l=#t}U#k1Ld-xF3o=vN1WcTWN`hB zr62IRZ=3dR8HEWwhfc*JHHJV zST1%r+BsMTD5A`69onDjOKP(b4mCl2J?4E+8R7Q<^}VfZ5%LMyHojS(bE24gJ|_as zEMDEwL;smU+p$m2)}9mK0?jo@7m9lJ{q>xLnkn58i@kb8BLG?U+4K0a_LlZ^jUuv$ zvMNW!5OMz-9zDj)%}u-t*WQrNh+FJWgylURYr5V5r4G-yBc*!KPkTCJuZ8q!uT^GS z6?_|6V>*;`Ocg}6yzxheK%~Q!3!55}$ek4BR)DjyN0vo80ZXd4gKN1U{hjqqrOs2A zP>vm(#|7JHmOrmGle@C88~Chq&?s$3Ur)!L4o? z9x%&1OSmw00N*UUBWHw2=9j8c?U0DeEypf8j>#)SAefkVu|iMnBSdYxJ|;mmb-tm zx3YsU43GPte%{+Kn!DAozm4|`J*&cg%dFqu;YzeK?&1Jg?|I1zKUB}enacw9@Y&fI zDQiy*^0Q6lmykwpP5so1M{#Quvo5a-q<6xb$iijh*QS&Ir)`S*4cJo70V+`_dTM{# zO558od^r70SIjQd0bR8hgJFrVa&C zEiH{=o`fPXj2Mrx_B~Tt6MbvePPKiHnbpWzIrdl<8$=6TZ&BzW|IjhHANE8lP_H*Q z&$zkMFw;CTjmeh2T}{7<_S{l&7_cl)x+h3tp2IfQBVTY4ej>j@bLI~-99MvdSTCY~ zZ^Y3F$EzynK9@rBL_& zS*B0OS=3qdA`DDQ-R|Tu<8Nc0Q_P;#wPY)TzJdC?EbHs(1_BV(2ykGJr#_vQA0`dV(z%&N@L7MqvNLb>_}=5N12MUFwN5cStKrKM}Jn4pgKCtntp263)#WtjJB3 z^Q6oLR&*#)IjVP}-WrDhUqpEqH z;>S|hZKAP0IGXNNKqCC~Re#Zsp~lfL-1N9oZS$uSaSOTB>A+dD+j*RWhYoeQ)R)^A z789U}voe5UoM{6y8R6~!7?NH@|MFiQ*x%xOz8U4E;MgXvSE|UDtG(G7N@RO~8pwOf z&8Jt0xq{wLtF>LD@wq|%{St2xF_Lr_8svO0RcJ^z!Z zZQ01j*hk~M_P|qTlLCC|}d zoEgKY=>shOeZ7GUxAbBr&$nl9SE8p>N}DsT!#pL1)`ID3Ovh#Sk@yn_^~ez3#u`QJeO5p<%$k(B7|eoJMQ0r)u@})S;-&LDp`8t&^Q+ z^_}stw=!0xM%Cr^(@yo2L^VY{U*hcj#`z?T2-vbzrK3}1>x^b%}g$JJHV$mF9S4+ zWx*D^b|1v#NaHQBp!)zUn%VA3B=hk}6 z;}G7=HM>0qmI&x0De%_WzFXDnTY|}3$^a$zSRvl)Uo$H41x_QdjJqc%8COJ%--2q} ze^jLlL|^YP<7d)QnY*~j#VUSn8N6phD^l7pUYXIR>4})_Z(&Z@SRGlbeN)%~-j#g` z$p*Mi7#Y~co_u!0CH+O z$H*HKy9l2D1t0BoTI6CJ>#{}a4?CN{6L*Ll4-r3sbs042PKQW3f28XK-YkQ zdOtj4&sm0VOFKJMR)^Y1Sv5#Xva`>S)b1`jJF+wMHD5U8=O(pa{3>jH>XK}rl7zwp zskQhc>d;Q+&)Rz6HshVOk{stzbVsYTlUr(;IpNUg-srNMcsYP~N4F zI|cv#kyfUdcAu*P3nY6Dl_hvavz2DwqHL$kMbJw&RObkNl5iRpkusiRL1nHW&|2-8 zoq|V9sRbcwRj-mW?I;x z*SBm7B3)r8_+OLEdy1-*54Vrp>SONk+F=U=Kl;!En|*8Qq_Y1bT5}6TlALfC9yP5a z(@wGFwkvFe_shQfu(u=|oMd$c{f!LRtiJlYleOyZcoVb|1tX2r!*1*&XrZ0MpEHBU z3N~gD?Xx{DFE;(Ebk`69+y-Jb018VtYGZ@oYq$a)fV^r!#Uy|qnq>Ril zhfZ2b)w6`*U=O!Z8ZRzm`{)Se2gqH1V~8v9BE0jh^c(HLbqKmct!6xo~K$r@tp|_|#XsxpIXRtl(=LMl7DnjamdfVQ4L62m3yFqej zyb^uqafUcREw(JP*nhoLwu0k0J69BbUwAFbD?@K|OKT=<=kdeddfWA_PK&wu{@kub zBSJXj%bfO}VT7`lsan^~8^_eyhw4r;q9pxuIa;~?u{ZySGa^sc$y*(~fFiy_2}Vi= z3`Rnw=vy!c3l|SpW>nBtbM3obGMcq?jr1*UezOI#Q8_+~SP9inbRrbdJ`-FwMYIC zxRhj>Wm-3gparE7Tl5{SHyP+M%>K*T^jlOeiRo!RTT zm1niGqCZ$ZW2aLK0R?f~J39R3w>q~5HO8KHj*id_qgo#?fKK1V(t3J(da%sN6ozJm z2y5z${SqOrv2<4(W0Q8UC zZkfIlG$=s0N9i*befwyNNj$`GE{Eoe=jv*>gw%R&#`szCY~SH@J~R@QMacG0p^hX6 z1A>b*lh+-aIvt2kZ}wR;M8t30>Z2MSz|8i2&Gd-7tn=>f`O77#X9CeiJ71wSc^xqJ zL`LOcbgP@MUEt&n-(1QSOBy%JXD?6bZ7t>WfCMXM}=cC z7)amPx$fb6kpKkE;DAXgGT6DaJ5{=`$dp}ezevbV$U9*Nbia=e9f-M1(~|pnjO0*2 zG*D4W;lZ#J(Z07qf#X+#j}6I<`jFA+@y}<)UdZUBYNqeKB%KUNIov|xM+aW><76+p zytU+#8C}>}CwOs_n$9z#Lc>H_Ok!rCmz~PqJ<~r7OoK=zX;Vo zJM86cu>>V%n^Q?c&WY52K06s-;PpV7-VlY4QtF*9p9DFMSY67zacGv?rh1!rH7nBZ zc;Sj;k7Le~-2tzj!>?16D7*Ufv~Ob$jZ6aow2|>iIU(ZvPdRLmq;VFC$*NRxaN-O} zKT&|R;;#e;#Tsb-Y?>cg%_Pg38?fgBVoGj~qt;JdlWt7EEDT-G*$_E&3pWIFv`Y?o zAER$jFPqy9epe^F4IRfQN)*7cacqMz!q>7scj&3m=O(jY%VCJTv+dSQU?6v(aDso2 zlYMUg#_Q{ozjqGGsf{hc<6zpRfd2GdIE#Wlk!C~SH}$Vc$_Xk`o_VM?uTi{tt`D`j zOLIRN@pJhde_!baFnKk7LfLk*az7c$9d96S**X8)5*H&p68{)8FV=JlS^H&?ehe39 zC6AnRu$f7YNkOD?vVS!)8?_Ph%VpPK>Ar*u(-Q1tNZ^Hfjlp_3E`s!qal=u&zT255 zv!324D7HbyZ+zJPl;>x&R-bMi>ZrHU6{93xo`LSO{gJ$LL)fE!c_3uS)Qcx7Tp=mg zZd%D^qeceHzB&_*oSE2XI3`Dxc%qN|^Gk5?<+-&U_T3uKvQ zG`$wE?Ip0&42mF7sdk=0DXhGoR&JObTm*DsXQYvDpk;&1k4mqE3g)w=RlULT?q!qp z-Sv-Mo{6q%I{*8f^wjGJ>uNh4POd>+N1CMD3(#@l9%oi-2yphexVSN+n;V?s1}CNCjb7jGXFQu`pOpI3Rrv!qq#c>GKQ0CTY} z>C>t#vc(`T2q+?+w~0m4<^UJHb2^m2zv~QB;**WD75A6^QPFfwwB6P4;o!J;+a7q2 zxF@9&GlnVaQRlDPsFe=DbnLa>H3r+=Y)ZOZuo9dsLue*j)d`7HPeYBf;cbG!cI$I= zQ0!#jvjyUC@j~Kiym~(r4$DNlgQ#V%4mwp@9`J8YmKaLsdA1%nRxy{Vw2m0EMva<5*Y$L#sD3`&6B?mP`VW3eTXN z|0H@6%Bq_vKtja9Pl39Vmjs%K{D2r0E1|SM-8W6~>~NY|*TRm{;GjyuVl&eM8T42%2S%3XK3?4kGse=O>yW&IhT65%e#)rG8v$!N)O+g zhYGcKH4tAp=t%PT;S`eD6)7RU^LhibNX~f+3Il~LDBm1Q!QFIa)y2zh^9rGUsi zaK^`74_o9Zi^+IDLHGA8J z=jMyPb7E~cCa0)3I3t+tu6su*#wq*-p~D+J%{KRQ(IDm~94DVkF=Zeo z;H^U(T`L|FN;_ug=5pO>l|YtBP4-=q;x_Q_i`&~Xqs_9BiJx@{@}C|Rr<`}Vp!lu8 zrHR&g@mtgwWu6w>NT8n~9=w9qD)@iHveh_f?spmd(6a8&XF)UuMD4)(PBs{^`6(lm zyqLjp86-VfpIHj+5+n-RluVH1kCAV}LcjBBg#B7G*j1AKhD0n0>Ppx~Tft=1lLgPni9|5`bZn zaqKD7IsVnxm&KY^qaoK(_$#1zvZ{4Xy5t*qtuM%4N@i1A zbxPeo$Y;M+p(`hwiv_kfV61XI+wnJ8yKo1kM?#_-S5!5wp|p}eG1I0%PG-R-nX z|0}3ds2We5_8Q*;9Kbi(LK^!OWpUO@Z}&RKC)_Cd^}ez7z1p3$mPC z8e&flha7N>7-+#6PU8%@Q0(ngy^A@bA-3S%={-$7&c6dkKG%72J=EthlF*y)=~-QE zS{&!eZfboXHysS;{Oo@pb=p6lB~j5;>ldu_vcXP%D28Qlt3#(Q{yAfn8mRju@8Q`c z6tYy4_iJpv)5;CbieB$Gc}jbPQ02XbszIMn(OxC8TYUD>%f-dZw zzUYjoO4qZD`a(4O2alXgDc-~;kkDm}~wPc-oB$$fN7OgNg z{PbVSz$^DgPnWND?6O%4}S{7x(R2@RiXo+ML((I?RyCe<>kydgB6?g0FAToZQtYx!=8x zCPaaDW>ouYl(!IhkjZLN^yQ~NFh0bze-!yfP)ZW{!{ku3;;uc{25OZlxc$_~__Z3_ zeK2|k*Ef|R_H)&l=3F+mJpLT&tyVv#Qtt9_V@>Yx%%7|`I+=b))~Zi-yUn^?OAY4j z(~ZGs@rMlzS>g3{$MQbs>3Iov1(8V!n-5>`ndsK@;kk}j@m*Ogg&`|z=ooksKjmb< z@0?_XjJhn3KxE%2dwlRCzEG*FBtsjwx==k(M-whWSP|=V zJa1{drl9~fjQ5xZR5R3m^ZTo9VjueB)v8wN4I0|9Pt2=6lIw)T3ta!lsPuaZ$dvo_ zDgW)fs&i%6?rqk9=q5y>`RWIFtz7G4`E~}8BrVBgOV3Tsb#ff?rKkIyY&3CEItmBX zglD#znwp=iK|H*%jm*|8Ut6O$$;TMYwFX##V#z*kE5y5T?9LQK=NA2EH8b=acZp!J zwAYYT7ZQS$jq9j2Hg*Y$A)PXIkK)&yrpT`^1qQ85vnYotTArTWfq;WBppZCCRt zZ^Wj9YN2NcE}kbh9#ak0jkMZ8yL8N#yXdh`b^cp(&fUvf$Y8nFKJNZgbLmlPuk$l% zU7#sNA7?1k8)^4CwRX{+gjayAE0Yhw-bfB18ZNY;-NRJtNtbBjOiQ(D&XUgI0Gnm+ zmd1;rwt4;z4}1jic|++RCI(|t0$WZq)t|{+rI9q#fwtv}lA4J14_y5fS7@RaU3$8n z{@zos$H1bsZ%O%J*-ngFM-53#eu&?osyPIy*uIFmYanr>@_9LPdW}GFuSV)$XLucSV7)oi$`p^t2ik09_p)4 zC5EzHb?eRU?@|#`^xl1 zKkrpHsxe7Dui(ZOpq0rk#qPchIUW!8Ce8ZaHr<+RI$Jvn2z7*v?wDhczJe12U=HP5 zn{)F!hWaiWbV)1F4>AXvc*2;yt(x!fl_kTfS?t8Ewf*WTSFPQX+dnJgqqYkWXD{DY zi6@+$^%uy7vpe56T`A)@aZf&Rr!j5!grxHtAylcXHz$~4ep9eB$dI|0GZ&q?7Zy^i zg}$WyHNaEwnX%P6$&NGl%A;biK#ULios07LJ%Ohl6?UPlqgQw-OL5J7l1ZAcpFmm5^3*5qmXt@91<@ zxy@gdS8i0dg`00qy@qEVthqpE;t)C_@s*5(<9RetG&MF#|swCpu&j1T2!|A1PifDfvprM^MCf^=WbU`d*nZhs&uFYw)naJcI# zs-y7w)t(HPaI9)+a)ReUZWcl9#5UJ=WUa$pUeCY!aWb<7UsjiI zwPZ>?85m-ImUdxk<$VkAxht@EVV_p(qP&&tO^TDJ%IZ z<%IPOsjTVDkZZdRD>c37Ev>-$YSnPsh6a%X(tu?GipYr-TxR@WuNU$ww_L$EWI^wT ztblV&;+`T{Jx~+Fpo4ZW$P}Yg-K*@)+CGB1iayn6$ zx`{Z;MxLkEZjQAms3tlYK~QTd>GOJIRK2A4tv8l18lmm3k42zDwt1$v&)hz28>W$2FKa z?o)OR<-T|j60Ckb;cJQQ;s&%Z*R$sNa5|#fP;2HlSRYGp9~rw}M%Xr8u2y+p{ zTt)4}B1uBib}MP0P-j+Mq?zY>xn#BAKSpT=c}Tt@(hioJP@z;0Pfezos0zv?fz}}N zBB21AyR)D&%=j_2DChjWt8n@gU3$fif#Z>x{4PYRAC?|&J5(F7nJ$WFvtrtPttWlb zPr0HKVK&C=k+D6=Ygv}{c>nmuY^k3F;g4_o-Be%zr=Yqi#drfrFQ3MR(QT(s@8vm{ zzD6w{W3>(5EHtl27Zpa z)a#*8-?JcQ9?wmH;v(Oyz_YG=qowngSDNR@QXS}ppdmYOVc3G$ff*kE{dHU<~d z_G^oyP9a314BTJY8!|zDSSKSc0?4v3`CP1?CQ?WS9%@pq3`wn-(YlNl7OaHoEePhR zD(Cx+E$;_BOx#JPgpbWjf!QF@P~~z{V$!oETg}BDR>7ij zqbg;4q~WA#YMlMXaVFF@D9FRnjow`?S?=fBNo%YwV6K*VdRi)#N$j3qms3+4Y)Y<} zW51zxYY1>sO_Xl+tkHz9EX6-lll746j}UbEXC;|3{v{rDpdxrXO%LSe6G4MkGq>-G z%iWZvM?*JQOY6tOuCp2l%IXqgd4iwfSzLn{`ZlJ`e3>+7#->3hY+<_3MJ4l`bPd;G z+fvW`bwY6OxraTRzir}~H0tt2 zdu>M?0WC|!(q9N4t!X-z--yf8?K8OcVefe{Sw=vb+_0VLFN-dQF7#L^p$NxC_@Z=- z94GLS-Rgk9g3LlSed5!^Kc8Kxn3kYJHO@(b8^o0M?0ya`d|I8GYt{^o(Xynmr#_wW zux=LmA=^>C5DW6kpCD&tqjf@s0qN2dZ)#7OLt6}1TgcOji+dhYLYDgF3gY)UkOWS( z_YTfTYuURw5;6-SV9LJSa~`(3BC^(1$+Kl2_IPb_JGXRY*dilp=Nm`BCNxdo5R-C@ zkKfkL(Q6^RGVk3>L?7QBtS}W2zZjy4RD`jY96|8!2 z=FxWYFB|gKTJ4{L(H&C|L24#Nmj%?!ePI)r!sWI2?&eykw!l`67|`n9Fj zfRv@jMH7?(2NL7dg0U;E^pNBo8(J6?uL9;L4HMzQ`>;RXTpT}r{f>XASw#`NhJulH zrL%}{z>Xd0FEL2Uds}7Lk(f-gNu_1Vh^?pNe55PCsw!gLUg`jv6$(q7}-5>}viqd^85TNM^_|G-C`mO8oyjgmSr?yWMeDFV zZCl8vEzdBepG))H_*B*Q)S;(@?_qrZ7mYxyp%HN_+K!BaGELVp4` zB|`jr;kD9U5aMkFlZ%y+{AgAT{e{Khrco;2cWgnpe}n&URUM~p%%w_vS)t}CQ;TSw zuwxegZtM_xKOWL^ipmFX>LNSfSue*O_Io9*N}=lv5)?^sHBqRo^D&9S{;5TE5tg=m zk{oD)JbosNew&Ckqw}^d`HmIR$)X7l1sNfp=;|0M|1mZRH9u}~4=S+4?Vx21U5^)C zQ?(Dam9X>Z!5X7Yf&&zCyMD59yU-J@!)B`lU*ZVrlPCjTgj&P;&4L%f(fDH)K|)Hz>o6(}pLpkNfQ?S5)2~b>M(Bcb9jgR_9I#9n$Utmo?&Dl+0PV2-^jbJ1q z&uf*{c=WODIze&lM$|g~2CFn}eeWZ80K@L9uSX*52k15-j9+c$0fhav>e(9Yy*+yQ zn0e*yHXExmG0hJy`_qQ9!V|Ch;#FmRLL^v4z<>hsvnqX$ z*VET;2h|ZiK|U0HT=dA+4A!wTX>KT*^}^09-%2PwV<5Sj1%sWB$IRs?y1DlRg{hD| z_8;r~c+X&YBdfr^qribc=@5KuYRq}#m5P`C^O^n}hGFZJZAa|<`aln-hn#l&I=;=P zvH5Gied2B}QthIo@nd7Ev~^YCU5>q_AIY&X)5ZsV@3Dl^QGULUc>IeUlP}X)qkSnF z9rXSJlxly0>psG@5}*?^4O$QlUnmPk$cFQ^L~WLFrpjKyJqn6lGedgDmTHQIE^1R9bEg6-}FswlPQAJ zvVH)oabRHvI8k6*yCnaNvLXR{{kBW~qGqCSDV8;^Jw1PODu4odmDTC=1Ilu$CXZ*b zUQcg`RkF~)RNcwEv7Sr6aii~GT32uRnoXCrSarW4`~&CTcbKZy!^l33Y>jkYv)5;5 zOsl2xU3Dt_g?dk-y$HjWi%6mXP9ZPfO0$2FcwdAqa85X_rz-)2Y1jnlQ}d%Xz~QkH z6E`?NzNhM$nd|NN)Dr&{gf?sKBA}+l_4suA!?-oE4)7}{+l*jkc#@!Fa&%u4eBx!mg=LpTFVvce`501gn|W~wqM_*hvL~(Bu#~#s zvRp>ELFP&sqRKdDb#IBThfy#VJ7eYV-I}AcA5I5yT7Jws@A?Z#CUXXN$YZ+<nh% zvg`_FWj;&2s$R+Dkc$Ta7PmUh-p+9s!6~dHJNC@Ogtwe&CFh!-W|1n=MjupsYGSP- zB+Iz2AsH0`3u|kP*tSD*&OYOJ^4z`C%z)pvLRWImb{|W$sacSHv)7gwyO1v}E$EH9 zO!G1}Y+SdPca^TWvciHmn_`%_=?%JX+>I;mVXSdr)xAA?P^|fe^r5=_0HzUxFB>RV z4awP@VM9LC6Xm>$(vvS*xwT9|x&#U_BxRzMyHjmUupOQpaj-QT!IHZo)0CP&6+&0S zEC1UJiHPC37TTuDYm7Z9-C@s<`0#CSw?1gIj3zpH{;8_MFg03@VXrVhFryuz7 zQ5)&Lu!AYa=OvEeI~%MXR{Th>OLuGb`ZlJQnd^$h{In+g<QR#=B7&~?@kHzo^L|oG-}&Dy~<{&9$c@bQ6`E1xeq$X z?W93#8qNG%pATp8#O)TMvqHfS?tPwuRV3~}viaF``noAP<+AszS=CzNKwyXXb|3+Y z43Ix%JKY=MtHab*o!9^nw~uCvwbM^kdN&_iyO*ZVDWhWdWz7B$@4GU+IH%m`HDE@D zCsy)o@L!x=PJqLFU(Wc#)S{~up=(8^6)D}|ucnluHI)7?X$!rZfj^FGLvn9vy1~7U z^HS^iO}oR_NkaVBj8GXT2|(MswGRJShtmyU5DX%qr6blI?it-lT*R^0gqoK=*vRHb z_6CnL_kd>H@S-h$(EGSzVBbO9y;Ow1$IKQ*emH5c89Sb+k~&>2W^@S=9^F+tJG*V1 zwa6qlyJh!Nm=fYage3Rl;W$-2|X)V78KXD)Opvq(&cRMs^nwGI_Uc)B^L^B-#VTzknyQ~XSkUt zpbxB+kj*}3DYG4y68`7vb@3hdkT3N2wRAF7L$Y7@>46~j{)n)sKWFhOW}6}yX}EV+ zt)!Bfi*=0o?gjZYdm-cC48r{+ns6=l3Qk>6@d=N824sPKIBzOYN9(3D$s1lm$aSlP zpIhBe-=wHAebRX+&u19^!d9`D(b|3pVGMs0uDKQSw-{Cts^3Mb#=d&3wGy2Bd%qFF z?GGAk)`(%9T(NNF3XEWxNhK#&`ZCK-dG`e*I@>0z^7{qn5S@_VcYtZgBAp8+!lexh zXM!!;76kNO=wg?5h8a6IL^6~399Q2Te{^32!lJPA7QMBx=~pNJzfv%5dq}tKX!pUA z+H6f^z-;2j+L_LUk->uvR)CYGOg}x9Q)cMXpWmYG^`PqwSE{s9^oe|f%(oNC5RU6t z(}(_OCm|2umW^I_oUu9p5xESG`Ra&MKym+rl!@^(sd$>sn8&%N{Tr}_=Ft}u5At6q zDd-HdX;=LDkE5e&V%tlbywcif*o}J#eFbBn8xCVMt1k=fL$DnG0?#!}md$cKUeCZ4<$JoE>0tOPF2iu|Bg|SVe;idy z(%Zfd$m(7bmGH9V>?qzo{hGby%`9QKZ25wuO)Ku^c*q-y0?epKm!$05_;0XYPr(aE z=Dt|Lb~i_AK;aQw4PlEtVG&0oV`qoGERXh-+_Xv%>7qdI zlfkB;3uhD^t#WZ2;%6RgtKa}luRbGYo2*49uHhXH5USX)0^D<5Pd@GUk$#lptx?xc zU2h$yntk*UK*%TvcaF0^#=SY->@gtOU??NG(p_#7?p60fH=aj8p>myXqqVHVHU1QI zG+&7FG7(|gv&QEnT3{r&QkK8-kR(;Q^JZwUs&O%@oUw0zUG>=tRnQieY ztaUZ?asQF=vnr5j&HGm`Ok?$k+LQ8TaeKAKQlDE{m^wl`3MA0~`RvO`eI2r8-Cll% zQ~5~8fX%w_E6}T2bv&6If8C*MS0%Y)Q(>mNjuH1YsU9QQi1KYoHV|)JROr>VLXG#G z#-RqiZWR7CNqZXJ6J;cV1D6QOEt+zlb;MXi%yyr3MS|4P))7f1_=va)p+|KeEq)Rt zg=*d;%~{0P?(3_JbVj2NdQ3ieye@}d`~%)momJ`t)$DTB$7t>p)^9RouQSVf9|vj2 zN`1fUQ=zOxklaWB!c-Hf@@6`0xaLza7g zE+QzmeKWR&KlSY$w)U`T9~S*4R=qN#j%be6nVA7Jmt%JyJSLjcRxzUlsC-`MFRw1#Si0J9dAhgN^8o{sqHLrQ zKeeCEqk26bl;5Pc=V-y#Ab4_eeZqn(Hu6~b5+obAIwHh8)_meu!>=v`9%CukeGZIq z#zqTU?2_SQOa9X1h_kCI2wSC=I;^1HzHK)$Wo0B;_VGK(MzfyD+l;Hr~j( zYAPOBB1{gl>0C9DV`TATvc)I}_xPzs)%q#5jj1JF{t+4`-U|vWmSs#|q7HGaR9?vb z52OJxp?8owG1=046fTX_?cnLo?jOZ=FTsSvMbP8Vk~h0$3E(;yq~7o^uwg_k{eP0) zJD%;e{~tg1=RUK`sqQ+sYt}w?i^_efMx>hBGu%}>BDEXq{XL^qR2|V!QF83fF=OZS zlqx|gR>jCEA|*&Ah{!mx`9`AV?+;75vDM1q7wix3=1Xje=^Q;yu3M#_GilhDouj_0;q4DXetC zCMV_h!2t*h3MMBl4eSbXKMaQR+%8J^E__HM6fVoL3y^`Nr_yB7#83yoglJsYMseU_ z!Qe*dskcy)vh-j6DDx-r=!~*?P3AvSA$qUYy=wK?taDEvnN!b9@5{B_#4o0;JFZty z7Q%IDT0Bk$UgUnC&Ie>YUhP3oNNEjpGdSgIPOFKFPf1*hK62^y$GM@K*Mc8`byy&g z*tqwJWytCyakO;zV0&G8qoUhfFr462wSC&(96wW;!BYl2-|Mbf(^oFHE>eG4vOn9q zCSd?9!Q+)#kz2vmQR-ZgX;OR1MKV~I(gTWNJoVU<)kDG3Dp;`&(e$qaDAeuuJcR4p z^-t@SN~P+3EjZ+fBDH5d$uK!JGm_>a=<8=aOJ9ecWm*gYP(&KfqO}vQOfaxi%?lz) z$1zqRzfAox6oDhWNjKp~BA3(?j?jAj>G)5=TGm!Q`8*0`wSg=IMA||Zy$0tLn(V*4 zCdft>S|6rE%Zyl%eStG0H(Cu`^g4JbR&(Ou9jHdDqSeD`4^-3TNe6O;lF^BxSX@87 zswna7bd)>FnvUK7FUasdo|q5GS^b*so%wXHz>^GWCTZ)1H5!6I~Mw_6-k^k z)Ml62J`J*G8kTYI8Ds#;s3xVqKl?KSFg~*44_;Vx@YX<3rR;ik+-Xoh!$!ZWKL)*g z%ZcNjZJgVo%mDB!8dcf2*A^~~v;hm}5>kKju{=a?TZv|i=oYw=8-#eGdVZTSAwD|R zTmowHlK4jv-$s1gc)g;%JR4d$Qom@UTW{J7>X39IM2*D&J&E8?+_0zZSAYW zZ2!kzG7UVndX72|ER!=jfgdkqUa&rQI`spPM&=8gaZ%9z#=D_IaFhq*zOS{WReQLwY{(-thFXf&~^#z`@((R zo%LqrhxIKHNKsJqI%TKa@uQyZ<;^Lel7aPWO|Zx4{}M4da{e$T0B{4Il=A;0^Z67M zf+BXR5kt4^hT>@w6Dcdo|NWx(<`>FO0E;?zv;=ieJHy(w$q@71=$lD^f41V1B5=7w ztqreLheN?Z7w1qsFQH1GkKb{Jmu4JQYK0xsI%~eDyL@+II4`IqGGN$ez!B#%B$4E} zr#$gwBbVO-;3HRCkk@HYpkNL5JACJfCcp5JjCI5brsmP042SqIR(*XW@Ubr(=n%VL zvcYbMT|xsL5aeE&lf5C>JYK;_)ZfXdV!ojn1iJ^jnv3_CxAM&Mz~`y0Zz&Z93&o>P zmK1u3VGfiozUBSDKT~mK0C&WfkRl*W&QgvlYRMSUATXe*%3E{<3i}=j37-M!b^!N; zBdxJTJX;Vq0d${rql!#^f}?u`98A0%2oXZ)prXf*ttN9^fA`v!{$ZZB%`w_EaTkKG z0T%aOVw+GCids4(+4VWStUef>CK$ydOMZ{5R(>+?@k=>XK3wG@h$5gfQM~Jwu(XpI zK;q6eF?XgjYuC2^5z&eFpSLvY3N` zn~M%_p#TRuaeGsy5q;P+==q%(-saUn5P{>r2zuRY!@`FDHt!eGR&(P`srLO*$Ja&c zLrK5Ce#0m^7m2Da-Aa(EO%bQ=FQv#*1Yg=znA#M^sYZyHrc4kkb2p_4Z1HJz1{FGuFg19F9t%8>34KLK4sWJIScAQ z_HI}LgiPmat;VOsqyu_eIq+jo03x6~h)}PT_z7xsEI6C-a>wVM92dnB0iD&^QvBCG zQRSerSJy1;L`mJ`EnF*#LNz7L2b->8^;Mb2??p0UN}-aNI`br_3X(e7f%ardFbQ|l zzx?5UK~i&BCju%JKIE|*wH|-v^G>7HDWHBje-EhrKn;=^SAZ$y4xMEZ(Ba38&#-Mz zj{M{V`h+QqEN|45oVLD*Q)QI0VNAU1M>dk*I6<)@SCYz@s}aY+a zylHIxOQuZf1e`ho>9MfHSfAPTxJ$$?1QutYn3c97bq;lsFylIH`1X5e zFqo~V%6SuSrlL>YpSvcLi2HFV>EOu5$nDIPpf)N0>mBbO0Yjv!5|7dRxk1K@nS|Gx zM?ej$zFhzDw}Y?3Xg`>d&7hb!Ij2WGQjXfGX7BZY!ie@ZIWquEXB}Gjp-9qL9-FUy zbzh_~?sLtB;YHp_di9Fw7C#z|wlmat$YMsv@?fI<#f_wrYW3>*4+Y!bSJOa2+V6F@ zqk=xj_j%o-3f?s3Mx@=n4j{mK;w~*PHQ zvFs1jq3J&q41=N)*5BFnaqsIU(~}Uhp8(cf;{A82tt+pQBFn{by?U_RGb&D~YW(-@ z8`o{gs+KP4x6=pB146rrC`&H5svkPgAIpgWZF#I}n(y3Q2C^Dg4^ladT?Qp$VtKZD zgNC44}5X7;6~eTlv+SzDA>YVkB1)5_)|?Y}=OqwzuQ zy*#zG;NC8>FFXO~2?0m8J!!N8qaWHbXN^__CT3aYeWuf12B<1N;Zude^U4 z{wz(h7w8m9NBNa}(u98Q(Ny(SYKZ(nZn6RcN+kxI3QA7`sutt5A=p-G$BKxMQN3~g zz}!F5h1L%mw`$^kzg0f}7Ag(o#CANAJ3W&o?7v0{P3-G^CljsgR_I@4l}LaZWtCor8YT>EwIzjUMQX1)JzW{36%13ge_6Zw(1#;sc0Msuq< zRM)`Nx`bA|h9hl4n+K1Y`gT)RK|U&pC)d7LAFAjSoK78GTX4)D)jPn8Gf=zFLr)^L z)9B~)3qS5I9&h1Mi}z#97AZGyRfM`FGBb}lub${Z-Zn_g?C?(hi>M zj@$Pm$!acTe!`ZSkAvgq1y8>@BMHV$9ve4GvLf2JGzhUCpo=i|Xd2D!bK5!DIgNT0#UF?Av z;N0EaKbfis7TjIGoo0-b%*;LCzgwX7vUZ7hT9R9q0nGvBP05_v!vaP-^-3=oFhYV^Gho zw728>X7N}U6*3+Z5lk%n0vO7gM?H%~>|Y@1gXxDJ$T(4@VIQ1%@O0j0YWt7$%;`UR z>3y^kLpe!%-7dq#I5M*rXR7 zFeX^a5>VJv;nosAKt+h#028V(pu!NUh+{R*PaL*s06ag?r(bk@2PkI?*ErYTg-5vQ zsO=qVU;A~iBb^XKtHZEFqtTXwqM!?Sh)(pdn!6P{rfl%0fv>VVnnw_I|IbMLeXe+mdeOr zU*|WU!@`nlRI*{S_WY?;qQL;j_Bm@0WqqA9U$0^+A?*m%NUa{ljg1sfC2U2E8_IWC z|3ZJR-L-A5eihV)y9-=i)fzvpxAW3y7a(pz3c)$DkCI!?%avX9IE5hBZo{*nvz}89RNr3ccq;FyX<-*8K z{O0&M>-d%lWCT5&XS<%ZyCzwLwmf0q8B~v6;ZdUfpLBj4@YrQR+ZJ|Yh>Ukp-noJP z&e7q`!#fKvI|i!RHV1J}SJ@J_c(2xHOs7jvN5vPz@oD7zW~g+p`&(V(|0Bbq3qnMo2T|b|I+bXM`c7gX0MWq2hmoLmMD6 zGT6U6bpEJ0`det`&ZEP4%S-B+YAOamFTNE7d`~4LGmR}f3N*)8(lI$6A5N@_m?C&qQ~^?v28;cJV5)k*{c3q3a5If?w6!Y+xUE5q7W^jnM*Pk$P{>^ zu^0;`#MY9al{p)q2QCPgqT5;ganXF8LllHZQ}7M&Y9Ifb5QR0Pd}y!xB2prd#PP%K zD8QfBOjCNI^ctE>n{nDg{rUBE86aJXY2@nDPj8BQg*j;q=l0jmEqMW z#)2PI33TN~)Py=MCA8Z!eOhP2^Or%#vyaEMo7K+uXE?g_Y(CS!k+3uB1?-C%&OrLr zoulZvos>SmJgckGPoOxs%|ECBUm;h#PqjB~mtuy7 z+L^&1ua>w6J7#s3x(rlT7Q^iFY(=(9V`h|O0q}8zzU+GZ>xgc1l+`r&K|*oarcX$1 z9ktpzy*47@*B##WwhTcLUyF^;1}g1g4gPa3Q<1KcpfvD3CC8PAJdfSct6v=TdZb4D z-gE&Kbuo~r6JT~hXw_AFkbjg$DwUMq?SAYHvb#5y1=TnE^z5Q4lAJQbjc!AU14v^i z6r|>mKB@^*-0AuoWw#$#D~o3co;U?X_1o)RA*1T6FTGQz=keSQ=m;` zstn1iH;;gGN!5<8DzEM=jU;9_%$|qw=m?za4%Fsb)Lyi!?JL_%DHjUX>SCQg-j4Uw zHmi|>(UT6i=q1r&5)f~PDj0$S*!(gBTjE68tS?1rFFRT2yy(kthW#byu7JqoVMq}| z?ZCeqmVRfEl2x{hTAfyYjI#hVR&lMx-7C~xwPI&fom zWL;czxo!Q?OeTVTD@QSq9<|ZtpLni?ZZ9cfVWGc(QCS_d)ZbVUI?jh}zl@o)ycfp! zMFijxYC!7I-UsiEdSxcOP1&czB*d}`9I7eKwWg%;o8;(JZ2W+ZoT(LaKX5?huq=%kh*-Z&6<~7WiUSn*o#=q1Y$DCS6 zBtC^APlHWyz1iWL!HR%02uTJSTXQiD2Pn=Y&E=%AxgFDNv#XVlXD5!n5{kiv?s-$J z6{}kJFf-!v7c7Ipu|cXdaGNsNS{3;P1n0_R04UONZ%F%k!aQRA z2R<%tX4UfCSC{OG>sE#hcexJ-9HEZmDnmSbnW3MPR-=e2^ZSE5(plO6yBtnyItnPk;Kf0q~}o+EU|Z#52GzN zhS!iwX7LUFoH~J8PpjO`N2@ewmE=QGV5GNjxC5m zZ1r@%7-0+V?9d+SNO=R}Ph}!IO$C@1+8=H^Nx;Z$&3Y~L-{w$S>G z+0NOXbU0LI4~nhzx$sl9*b~-{R5?i;ejqXZjrrW=rw&2<`;hRX&cgN{%KBPl{Ea)J zwTA*BG^{z|_oBffXE5$wbP_`&q@(WrBH}<S~4Zw)`WNlHjf2{eh~Yp=J&qGjw^bT+6a; z$Ta8D;ZK`8z6n{Q03MpgNuEho-sTkbdIyZ~VXPA9kjULPP3`ULg1TKc1}@L81LqiH zAj1?cSX&aHr?SItDk%AuYFc(1Z4rZ)NguKl=6W8pCvqtRB|ZilZZPt?Zx^(^xO%9X zBk7sypW3-h*U|d_75)HrigsWW&Pbs|bv$-UrG(-w*4E+vzHO9wYS0g$MJG91V10B}azW*0yUDgaokc==Rq zy{`=$*M{2wCF*AWH`h=It}18h(H)PR=svoOq6H$IUrv&>Od6x?pl6f!45Ce6Iuy~b zgNiUzw19q2-VGiUlz23rrT-xdATaK1C-h@NV5jt|IXAwb1ZDv zuxRqU2@5f9v*oetqzggRXN5KIov?|jPHbqwu{S(CY(Tfi>CATi8*Co+nJ2(_)+Zs3 zXYH~_{}l1ElYRoZIVjImdHI@8&>ds&C#QV)%u#}8B1U^k+np3aYH_UMr9&c?0)BTWn~(>0-E^nAx3+cDKE{%r@C1>*f5=^LXb^x(=?90?KJ$ zX$#=B7OGi?WVEdCpe9hj0Xy3+23Wfsbn@0WKFIP=dSZO|m8FB)GQckLIFF63oc6f7 z=tn*EtsHQ_0~|vKuJwEU#&sSBV7=2eduyXrqF(HD@Eqj#aaSs-8>g z!Dj>$z=qm(Eidc~VXf!(%aR7wAJogNn%!Ia@m{RtuVtgos`$B&1?1SU5c+{Z>z_b~ z{!oWrfNV#}tATotj<@&|Kgr;R64uM0DeaC-m+TBL`#XNa=JAS8shtLUDB#H5V3}>< z#RU7k2;wz*z%uj)x)CD(L|^qFD9wfeG>K#!J%YafWqOM`aZ@Nwa;_=cv8hT*f zOtjKd5B(c`xrq>;idDN{_PhN6+=_Xy@opmlAHI-Am6l6!nKc%7-?`st^nZNYT5tM4 z?U|6yLjB19;IKrj@?xW9v*KnFg>YXN3jRrMU-l_H2;}=8upQo11A^QUZJ!Wv@m>pY zT?+$kR});=@cz>cVKbc#Y`yi~F2hA%>Aty?Kh{)*rdi(UU|yI{Na5k2G{?+q&QU9f z+SsIsA<`k@X_9WI^<9~Bf~U+<`{#p= zDE$>O+_nyv{P$;nDJRs>F54%f50Y!kfCV2wyMo3CjP~KaW3$_Nz(Ev<4u0nrV|U|A zwpQ=O3+5LR_*nQAJknS|D6Klo-@o@>;2*%fAoX?=%pR#(q%{8xeWnNcEK;GDd1JMo zFYfL~&`7~~@Mx^g?Brdd;EEKDLM=5XT!V09hqNR<2E^WFSJFK`p zqWe#t{TZ`TDD@Qi>_|iEV-xU=zl(-R_Ivt|ExgY2f0(@e<&iSD?J=I}O2wwEhR$5S zA#@uXsPXORNXO%SFmvzQ-#0EhK-_Kj#GT~+33YA}Br;!eTZ0*l59S7aSo!~CUf)3=Quvc^HhGN6Ot>s`_|OWOrA`KqED z9fLhLUjC{vEV>n~2}BV6aVs~B-X0Qt`!$ZjDoE0Q`4;#*+GN}+NhFp80LsOVJB*(iR&Ay;6C(DN&oHEyhnYAwVup5Sgkv6 z{%g*+k=-4JK7hnLIlW5e1tXBl_QX!&3yqhjV)W7`JLK91Zw2(N_`FL-X<+Qkdh^k> zN??F3IeL?vqk#cBUqJ7&J{jHzrElTYhZ>BymZ}Bp#>?=f@5JN1hCOyamcI=)pY#W@ z3AM(Ddfm7ewtCiT!(+|op^a*QX2Mx5XM~3Ep`E9pJ`Jdxz|8Fnj({biY$%hoq%1ZF!L-p5eotg#?f|I1 z-8lW_&8Y}E?ax6S!QS4&n4YxL(ZN9wpxMIH9ACV1SYWg#;8Ee^Z@03{7XJQhg7Gfk zq?NT>mDT_R2IA|UMG?yi1-EAs^sCQ}#iy< zH+*y_+{d9ew!$0dY~}H82!M*J>`TM_Lj(mNE573prmgI#4-=`ZNI@g@czsr*&bD$l z&sCa)K;+-GdpW(>ZYmuiCotIaIiG{)|6a1SFF{2{2X9ttwTx%9PRg>_`Hy0uc?)?0zUD_GLk!YGjclcO8` zJ0!Lj6PF97FH>txNrcMdv0-sj<=^z$F7ngeyn`QTC|E=K^&<)mOmS2X{7S4D*nNOZ zKse*`r!Gitbq1jG&|dX)`r(~nC-86M(`4$4g&Fr%Ll+yvw^za|1B|d&V5|y~>a-F3 zM9h%1H!$iUxBw6na=Yo{7ez$htSOk7RF}-{NE5*|1LgTvU5PU#PWG#w3(avZ=@Pn| zuXkXsf-;b5(t6|gCZ-pz;;=qA>4&?^MFJrPjsDCPu=L>Vvu6E##TzLkOGwPe7sRN= zkOi=rDf%rf6aU93I#upy#HvTTglo)};cVYRan5f&+jH&A;8${I9kYWH_`VV}ec>(t zb&JZ7zn6Qf^xm^x@7HfxEk7iQ@nUIkg#Gc6bM-ii3KnGDWCZqiCmtNEpJ|ShUQ=7i z@~1EC2!>hWefjUSM#@@WiQadTD1cp4|IUshCkh8m4%&6=03U7$(QYyC>h|7)R<5QD zf-4?Bw&}NVcg=GLejI0f63n!a29i*M%XrJZ97z`#;DEniQE@@g;v_kG;3>MoWDfyc zFj#+<4i^?Xr+v`O1C~kgpxM2RBwoxLl?(%^wxs;)@(w@Y{9SN4pkA%Jr1lLA&+M5W zc=Be=`{P?ErVUi8dJrg};LbS9s`(2?G|KHRq21VvuXiu;thyykaQK^#K9YgyAY0Mb!?0FuE2f8#y*OK>!gEX!HaRkF zXUb>N-r>P+!C@`4A2%=p4ofVn0uu0Q78Y$p?@J6IpyW`Dxjdm`bF3?@H{$-#L5DDf z+3f1#qA{}lvIWeKYoG;;sowF0d8LLNL~>owug%bd8tA|;oQ9)&N+1HB?E zBVnu%>IHZMx6@0$j4=E{L%_hp11POK1KZbKiDlY;waC|U%L>W9S{Yk`-`4xUBTKo! z|I)ongAkS2=N}n`e&X~)Q13JEU_Odd@p$tuDoZT|=Wd*?^?f}z!lovo#RovC=fTiI z0QEE@Tj+Zu?bn)Ccv0kGK;?Z8c7L8H!^028J-1xbHXT29WGVFP9>3Mi{{`HI>uHW} z+F}H*EmO9R;<56Ek8M9;FJt%KrpxG9 zX$^kMWr&t8FhTb8h@%|7Ku)oLpit|d_P-gTDlpd9@-2dr42rAZR<4o}RjHhy{@lKA zuUEMZ+Yt%LE>7%iyx>lU$d)iiED7a;8+%hPxvbiq z=@FNYIaMBi7Op!9X0+bK_tPf5z1Juk<=aUg$`i=bR?C4*{)lwdjZ?FEoCQ^|Lpx$7 zo)!tbX8<;p66Ugs%nEL1R6`^%gQVx&l!!pIJ6+!uL-qfZuu z{+`M`O))#yqt!Zr)PNO57>auC)|~t@p7<<7F{(H;6kX#}Sup^nsWb>CWTT!kFctJS zJn6vO2SVS#2)C7z#sNCPoyQWW>ySjgWW}{#YBvOyBT+%n; z<{K>g3<#=kRze=#nq3@vWm?b;fF0ZZVz^)(^rrlYR%@C`QaZOx2;{4F$aB$U=+nw4 zpjx7@CyEdmujjE>Y0r;Rq)~@^1iI>t{u*|3AO9X+rxVV|RgSv;y1tI1On(J#(`W=Jyn zpmC?$%cbIZV@%)a(OI%XNnAR$!C?(+;YKt#Vxz`6w)g7_;}C@@WH6PI9BX5IP1KVH za4mr@gX=d{-Ms}aB~|gylS@In$-=@8*@6h^T4!GdbAllM`+Uf+Z*$XjdKBfCT~9!c zxiV?-RLQ2cZ;V#w-uI@-^FE57K)(U;CQ9l1-yp>)IB7ruSYR4N8taR~(*P*p8B(BA z(#5}LID{3z*uWwb{f(XDtWjO%CBJ$6fyPfa4w)bEd<9Sza1Z*7A$#P2=X5dsS|w9XrsV0w~`5J zsg3I0xss8U3?F5@s&@gAwe&%#0>}=9b52$|x7YH`^djO582Wk201`O4Mr1od0`p-h zZw7n%F!ol?XsU)3d-U*`17%N{>#4vFFs&Lc5XkX`G49Z~5o3kzu_#TH_t@0ao45{# zZ=CV{?(r8H&^K5AfC1tkxYa9H+zb! zcktfj(9b(ie*FbAPrt1!P(gv#uc4?LHf7!i#lV(5P1P!PXg>_+t3Pg=(r+}c1n#TS z*6UxmOgB_M9m480bGIQ@wdmX!3LPGr$g}0o7C-tkB0ty4eA!bv1!P%zuKT%9L_IX8 zPH&hVasiwx)zWy*Dm?fxL6#%cOZp^Wx zRZ$dYd$t?IDxOE$&^Rj7eaY;(^UnE$Sc4-wQtU~v8i8v9%dvS0qhOmf?*rx4N_XkV zrA2##5l!)zeGHCEffL=T9uZ!aC=_?GpyjBBi~EYn?gbIHCbg%IUj4NQEiJrT;CSrc zK#Lsdh?-6t9_)w)ma%n<;hjHuV+InlBudBwogd$+JzO`#Q(4HIeL`>DGA@4rS;V%0 z0AICos-12h&=F;RscKWuiV$%Ejy8a)QvJg}M;F{PF3*OMM_Q3P?gkyW1pI5=06=$< zjS^Zyt8Ajrwa-DPam`?@XCMgRn9V*jnd<%iX|Gt~wB+_7UDiTrNWsQ#_U23@z+iyFFAI{B!K$ zsK@W1-RrI=AI1?T^#aMiKZaYss})fwZHE-?gxU|#`02nF1TLflS3b`g5tvVp)_yms zs+_C6@ZW4Xnm|kuhC8_xlg+%s;E}NxlrBRjB0A@&wbCtnU^wWrw~(CeyA`v%K$W8@ z=S8!`C4rGms?Kyg54lHC4fI%aNyRHJ2Q7z61ncU-{&$=`lM3wRM)b}<@A++Thi*<8NS6Lh@3ONQ{hI^BG za@OF`Lj4rkQjXb_?A+7dLG(<10fEgy6-n$AH}!Mq7j(~H%WQrcN8(o!@^&*JTW_y8 z5Af~&{_GP-H4(q-r?^%yHZ8ZUHiD2P+AAG$rRTE2ZY_R&D& z@k)SJ5RVORQw&5kX|h;BwvM++eE=`t8^4Hin^Wgsa+@~eBB4F2x)aL zqVAoxnvyeWGRTP6-GYZ?9qMF-5UPY~(gHx^=iVuuMcv$-RRd2kCKoGVMX&U~MJXO? zU7r}6C3}_9gP|WVCuI9v)=Yj_Pyst@bY2l> zMNd{*`%mXpJLj}p(i?m=dil3mJRmP(2~*3`z?Iaa43Pw zgNa-7cH9e14)Vsy{J>=befJxk@0xy&xppIBW~U<~MC3M!otODjkW`LnYn_?liq;P+ zvbb~ThK0cvhbJLmgQln26f+Upn!h1t?gP{Fs^@w3iarU&^SMgRwM8v0YF8pxlq6{e@#?hZd;K9xe5$qU_W(qNc1m_HMN4S6?VXp*NYO^rH_!a zMxk!C+_`wzPm_y$Q0vI^-fySaL`R3Vj_Pnt&j6bc*YF-_X)&SKYQ78*NQfUA#+PQQ z8OmH)_FJvp(cAg_JO{LpIbpOdesb=Teeas98FV_>cfFpsjo6vxhu&$kN|K z_+oqv@<2zE59*(KaG(1W_eKV0TDcR?kWy{3i3sszT?jHl#z0l)`RC<EpbGpe+Z>Y3n!s7GM221GDj9cxzXn2yr>z%PA1xQ~g4uRqgDVQR^61`m*5 za?5Xo-N!}JT&7ih6=3a@BXDH$2{irQYF(o;lpG0MzNqdmTM;$E$-SZxh+UC}@$dZo zheY`yP%u=UE6SL&*4mVNN#6#(TGU^^4Sz_G3YBW8`K)m5< zh89vJo-}u=)0qn{92c#o9W|m}ZVJJA!a2NLx}&3Vbjy3GK@#L-z= ztgTmzF@6}a!Wqq9b?uW0U$NY)KW*Ker&5%`<|Gj_$IcqB(O6QOjLaHE8mC6okH@RT z1s1;QNT0u#=~mD1Su$}B(GvfXG>bn^43|;hz-rpvi|p3=_-gR_1w1((H}mv90`=8` zf8PUW=)zH-M^?nw6cq%2+TeeAfOK^)^Yc8Yz;C%c`CCi970C)jg9*d^HjiO#stbWx z-5I!nxJ3!`VBiX3;cddA?EwnF-;e6Lm5HrVkzpiQRe8L2cpA*Fq) zbNB@yf?PQ{XepgBr@_sCtQ2Xi4OpX+p{Pgbs?>VS&~%ICu+L@b&rzKNm`YlqG7XUJ zHxNd6k`|GAMLl+%dw9#>yfF)9qm7GCn*%LzM*ft@Zh6p;Q4JJ``j7dVBWODH60mC1 zshHA8_6qi*!~;9LL4te5v=FS0&G4Qu`Oe)G5)wfCDc$vRCs+QX^81l?2a&JoBd zAB`^4qe`dtFKOI6IUxk@kIzLT?ZOU<^~Ju5hrqZ%5l1jIdcK_AX8+B_*WRgYmAX%_ z&!3=!t5W4|EKeUkvtnQjQ06}<3@)RAO(d8DJ4_C#^_~GgFl9lCeDw}>>B(fd&o=G> z@V^+Z8o7)M*nBoR83s8A&a+bXS|6sE{^I!!bOM|LJyn1qcX{sVtZnrDd|zR(JqW@e zR7hFgAq)KGJh4B@RstOSUf374NkWwm_noYVU{>CerSSMzB~Qh-w~Fp9Ny)+X|HkAa zcdjwlfxk}(pQy(%_=hkPHtW28+#B-ofd$wxfOAEuZc_}15O^S1Yt2Up!Fd|UHmp{7&JV67trF_xf zpS^a%LzYy2-F<}uEsb^?@td%|Y4GvgfNS>XGEd3}+oFTUHdS9c*z7A%*r*C29itBe z;VI3+4bkYw>d%kPTwjW?zP>!geuDFrJ`{JO&+nEv13>%xO)j2?8$j;ljmfUjGiEe_ zC57>Uq*vo}myioF?YAHCN`;gkHv$4jeaDMJB_^2-BN&61@)rkgfq%f7io|$>TZZ71 zCtI?du3RdC>A`-qPWllFaahmY^sVj8&b9Z#zvY03(uI_He-k}5FgLP)3BemIU82ll z@f~W@;(lwdXz5TiGb+>4PdE_qwP4uI*IDmusiA?GpL4i5qx3(oM_=6c;Jf#`o)>uq z|EM~z%tdETo1z!qI)+Lm(~b2g^PK`1Y=kxYa#?96QZH=|s__weiTjAw7 zjZLxU1+`f7qbVOoNLDS)fT6ctm1{Ll+MnbkEZ@+-hdwdq@20E*S4-$+awXdx8%oLr zuAkP)JjAK$Kz!N`Tm{70d11K6(6z=BAMY8c5`7h8;Ud$a<-!xrlp~5Uo3AYW!mod} zU*_h30MTe=O0QKI1Y&X}_!m@&Q|{z@asD1U*A>^tO-!t1i%Gww7Oq zqZ;ot*j^h(6eUQNcR^&i(u!h)7f=F`Ot@wqcppoC%cSUsj9-wc2G#(f3nE)Gy88Fs ziE6bLuqHR@Nd!b$V+pZnsHfmj2MIbgBq5w`nZ5^pE>|+5RXgHH&oFWmVlOF`|0M@t z*l1h5j{SCenQSNlxfR`%(!O8x_4h@A(K3u(!r&nv@%?-^TbFzy9t_0+1Y{5G0taFk zBJ_s2_jxi18}8iije&8KN^g|U4>WV5?)xn+0RNmJT_fqP8o)Z_gW@moWceM2J!730 z1+bR+_*6hx>PtpU?WNlI0pFfwA8=G}OYNlx3=Vc*AsaT{**P#Kr}TEMDdSZ;-muc! zvNL<8Z)&@W_>Kx@bH704{bPnI5Dku|rOX!znhiiK)3X|_S2OgBFdUBt84A4be>Yk; zJ=(8^-W}9Kr#3Hi=2Gt~7b(190%;&=KoRITd;OSCU(e!N$lsrVLfC3eI3=m$e{g9)N3>fu-f@dtE(?MNG`nO;$zEca5LMXzluP^Pf3YS8n$ci>g7G{%mp)bAc*K=mK5p2 zfMcT9KzZ{y)ZaAPm5q&kT%zFQQB8y}UL%RE%KI|Mi@ktTW3tMH8#|F4+PsRH9hDtp(!)U?Sm{dQ|u9+`X_CTeoVRb_mNsRNK_wn)S!D z0@`N3=6eH!Z6Brpxo$2ZybZrxK~B@m4>j# z=0C{eeDlMQ!p#UI>SPh^RAXNYmRHFy2XRNb|DR9WT(K%|G2F=JEU3V!79To}OE4Xc zME5aEHK8>P5-_Fz_OCrv`oQWd%=yxLrrDffK(xSjb3~5OcLbJKWA0#l|3??YbqAD1 z`qfpqwiG5H29-dpy37XasKFdIQbMdZn4i6r3_`uP3&surWY`|(J$o!vHD*iIp0tR? z6lM-6j6&)cbs7v2Vx<7^e4Dl<)4jiPg&fFwopx;bb3 zAu0xZjud{HK0+y_pJ13uj~5q0D5%?&tVlSr}Z*}6w z&pxn~&F!Adm^Qa8NuZKttpRWlff!ri`m)(6z?K~4Vb8RIeyce5@6X8Vz$JtG+SZ9- zUz}M>RgRkqI8O^Try0P~8i#k~KeXn2*P_{o`>8HP#+<}*1h`m`f7Ji30v=(W7>dteCzfR5l^`a4z6FKx=dz^oR5_-4A{k@n3jW{+#y z_ydxnR)Q5NFEvyr0t z(VAl93ZUGWb!_c%3QfPpT#;;Za+Zd{Gct$TyhRuJelwoX*PM?X$7_P-8=TghG>^JC z#&_Piz?MfpuK`T14aT4AvpfTHfot^91j7wP%!Mm&C&Ok(1e|B(iPPJaRP-PfuK-!x_<3|*9RVp7O3H$HWqg!5rBJ|> z)U_^|Q@GCNLc+QJ(P8uF_}AbaCAdDVke5K?|(}Vqqp~!@w1nlL0-rwffIS}Yk#`c5kX^SCHtc|Iogaw z*)sO~;~#6elK4`&j3`q%)4f!sux*zcQa?NwDfzPdW85BoYNTKy!x|*vRUn;HY-TqV zSBr_PPaOetOlVBjt$qH#L=M&du0)9+Z~bRol=WZC3d>sPgV>0giji28*PT}b%39dt{RhB9HFZ;19No3dE47L9fAVtJ`Yn~gzs zSd!r7-1ChB5-1=5$_AX;#DqjIAqe2IOMYy6X6splDPkwVu|>U!KeIWHj?V|}LaemV z<8y1Y(^+RI4anSb8ZF0oQQ*t!?E5VuV1K6uH^GR-W}i>mi{%_G{5`I1Z@ z+FmV2dEW*q6!lD|2adEI8Uh%_^n`;OwEFI&n1eHE#s@IP13}QR)7`>`C1yYK0yn~8 z=lsJUteLVX)UU5LBFT-d5D{jtkj94=L8bfYm_nLaleQ`GLVFfTORKRZjiOoL6?Wa| zPw_iY^Mii+j?jvv1)G`nC4>8hClm>aHu2qlvlk*=yhn4q5{VE)LrxCb$&-HNn@eV> z2{#g??BpRpifuC0-O;Wy4pnJc)NkaECTNQH3J!ImwlNn*AIgL)`SdFM#Q_-^sll>l z=d0&Sx)gTxF1P6gwPfxZ+qUd5EPuyfSx9+hJM`2Qv;?!}QJIfmdYczs-c;lZYmVJ|%e=HbqT@S6rgo;Yld`;mVABu zeV_{46J6kc6A}?Kn7u=&ZCmBB!4e?hTCl376BrQL)%UI7ayr%Ec}ANgI9waj`jc)* zaL__UC71y^o2Rxk4&)vx(irT<-Dw<;1HKPbhMPnI=HyYhZ{6JaqqO%G%3wxp)S_Q* zH`WY3gor?cQyB`L6_S$be}%@c>L}jyx;LdOKIed|)g8~ezcy{d?+@l>Ur#hMG1#Um z+6mifOELr#>_9M+dUFMnICz0k(kRWM#0o*Cq}n&tO&rHHsywp#SpHAp`#E?VW^`fm@w*ssX#ljOBc2PoL6 zFpA%~*`nxhlO|wQ<%!XbE^wU~bbE=K3FuIQyH*!p?m@l({?%-y1-dZ8N#nO)zb+$QP00a0 zx<`uH@0*vWJp`x6jR5|h->~h$bnZKi!0JwdDXCfjv!O@Y-_xD9d@`^uLoUHJBJ3LC>i1I_?^x*K0OL$5-fFGpXYXD=#e#x zA3(bHWxGo&=_3%(cfoQ|O5#l*yuMeRaS`uZVb50aXMwpo*hVjzQO+oEZU!Tvp*7Vz@Z?4e`QLine&JX5%!T(a8OL-O~HLT(*LCF>Zp)hT! zfOhoK0q24Sx+Q%}3BH#eGeqy+*x2m?FWHU9uXE+CC7R|=OEZ68|HHP}@Qw5h|BJy|UTy zfS_fa2lPh}@n}QM!MMKm;wM4bvb9eC4%?}&A6vBuEO2L#-JJc#L*EzF%yo_c4;V|rVwd@**tLZtwdX! zCtURnZ{l`75>EgZ!;K@lL3TcE3Jbynk-zt;Y_iOL1)?wha zMqY^Zd=cSdTTeXWp4%IL+BA1`MZ!_66$Qe@4W%?%kH}Pw4q%8<;$(zbK{lS-F+K4w z@Gf?9p|o>k~&(?}461^%?%j{VnV{)$6+lyNUZI{1|47|MK0_-=*pmZ*H_S@$- zY!{Q}AYqNmUhLp0gC_~3v@g=C|2qn*RZY2dKx|5VKmJK?ZQ#$a!?q^2#|NX~I^TrF z=Ag;yos#B!p7ks{3_v>yS7h|(N zjn$>ZU^>|bS8QAi`&2^d`8NR?@HKf&F3Kfz)eq*q-v!guY}SWJ+5;9NID;G03R#hX z2g^+DOJ)eBMNLZLxES&N{Nn#+oU{b0U<37za7DH>xAKJF!D=(Mc+lF>IQvgL3HYM| zr*$u-r=<5APs=O1llO)GJ*e$Ls?7gi>~?rCAU@B6BL}3l9na<;%6=dHP#2QoLx~0@ zbV=Ak1V@=R5l9u%M-#|GXzK8A=9%x@K(Wi>4C2eaRix^$OsoKZK}!-&OrJFKsp>Lx z)&zhQ-j7}uTl4-oeKajBE?4mrOQnq{wk?k9bZVBgHV8K8!I@L0(5G#$&d^J4{a+e+ zSO`?%oL79%YGk-3iqPam$V7IT(GI*B~3+=lwu?4VmMqpzF; zIbB{^jEOqcxJ?z~N@_6}%7Bvpl5>kq^3q1YUgN(%IqO{*A!j6}U!+Z&+MOJ95S`d+ z#iKRBrRL1GM9?a|#I2YO`F-i=&b$O=HQ0#$Uc$LBwMuR2HvB`F&Q~y1W)7WdO)KcY z004ZAbbow@dxL*_yS48>JunSWTN5hnorLZRT5w)Tyts4Qn4s1%f6v`(GG=+P)_WAZ zrQ?8KNvodKP1s`)x7Lci(K7!y`yR0NQnavcpCHM%;a9P1isO=85 zM`}C3U9=hJAaQCMSF7HqLkw11)k~3nc^GgFR{0EuGr?H=3uvnUV<-aBW)JQ&{+kUY z9l{JiRr33+#S{vWq&=XkBwO0Ubwfe>k3%xidoe0t5WsC|yyL9w&vJF);z@_^L382z zGbX;+m5^w@7&c~vhtnHqUzTRaVB$?H)K?49!Y!D5)C>2pz?}Mf1|Ub=wr%Bymlqqf zf-6Hns?xk!&n3N_h~Hdm=VF`m)qt@H-60>8Cw=}V6{;|u=VTa&`_*8lOUKr@&e%N$ z+KY2g6n!CO7)tdzJG8U7q_mEQpmWAy{EhEg5f@IXEo3*?QBmf13ZH;5^smN@Z1x6E zwzLg@-vDQ48@Kko4%7rk5j*|z29#<4wt7ws-zbT5-0(_rL^NFTsTP$BAyzrC%gu^~ z6>{6Jr0;C}9=;eUzefiZ;#yUh^HUl%!&oMLYst(@@sw&|ns_^DImI~w?92Y^erwFqv|BbNPv8%V@@LP0j50DB8uPZ zrPO{Ut>2p#+9>qjjuESgY!WD{&K*F;88KI!v{RF>T>}_^^VSm*20sMcNx&5BN!f7n zoTB)0yrJj^OV)8_PCt|w6qmZ}Nb)2Mn*&7k&6zyYkLj~K8ab-1&iH@7;mkqbq!yT-Lv_0C*x=!~9hyjU3 z4~%%@lYFBeo`0hiC^ZHlAMp2Y*WiNDs-16aDn1Ma%~YnOG@H)ASr8QGI5XLy6%}Q$ z)dXx56;YGL+c)yZ*x;(B=KUYle)?Uw z?*Qy$ny4fw%IEx0Ez{lCK7->;`A~4?S}J)XLeJ^uOnZ0geD|IA0Cn_8=0-K{j+&=# zWYu+8)<{+|&7qaWjcL~4b;c(UnA`bDvo5fMah=u17u{#+Z+5G;jt|a%&heI`pxr_j zu&@5v}w8U?Xw00IMRV?;CFEaikijReOF;Y=Cd1ZCNM zNfJZ~8kEw3`saTZz4^zP$B&N+f7qLHw!g`(bGtpKGG7xB>u1}sCRvh&1Qdt63_}Z` z*r4}@A$t3$G|R29CAn80(s5eF*%DA_V?N=DLjk!u@3#5_&(G?-D>^s6>co}(34O6| zMf1cwtcF;O1=*S!YeM%5oMbkxXxNtP&!xPkPOTBG)`2a2x(G-+ThT+7iiZkL<&D2h z|MLsEZE~~b0fi}@P|Pn)I=rL-k_r{nv>7t7^k&K>Fiic8KbbV08%_v`(eTRRiRj7Y zJH47=&jaP(v2ym8hV=>1bAU&c;?%ODV#VqrD9P87GP2-4SLCMiHmMS5dYz#NRHZ`M z=#c-lftXc+cUhr=7~0(zx6HTvqF@KWM*8g!Z-oVFs(8jfn=x@|T~iLQ32yos_x+-+ z*JF)DJkg#%e!FWr>3J`Eeb?Vq09&$dTv}+hyIfm(y~Hse>d4_;&%4;pIRvo>RH(9^ zn}qGv4q^VfpAjlRE`aGM{=f41*ziOr9vEEQ+<5qhqjPDtMUFPh61Z!;K~ycRI*AWy zshx?)-wq2n0Yh-SOrlfJQdFHkM1s_8ZQOi=^;*SSPV;c0dnY7@o@q0gT^!xaHedxwNi3Ya?&i=`?bn=?1^XYoSh8& zYW6N>36y}S2vBQTylQ`__kqYUfXE}au+6z1_jaFC*c+fgX(AHF)I`znYzv1quiqNf z&l#Cdxe+I|B=7n$O2DrJf7_}q(X^k32LX;|ts{j2$Y|0D2#x^2$nDL(94Krkq}}D7 zQ^du_JLbl{MMQihzLVoF`|}H1eht)HlaI@w$8TKkquoyJJ8}u zvP&#R1a=ro49$w9Y~a5c5wv+C$>9oE9G)!}GL2(y<$P$*)fwCJ&o0@kB9lyBqa%Qe zCuk<*qrpoH;i5o~%rjBJk`<8+l>a$=pTZCcSe@; zWOFQ5n7$5QuVjd45No|0_ezF^gLHfmN^Gy5EIm_sy^vQ=4(vuR#%n`Uz(jf2HCk+z zu-bk}7?OdOe9UbFcY-8i&u3r=usx_k4DmNKK09e1Sd3Ub0Qi{+vb8$}+a1_50*VdI zqRgT{5STiT`K;-6uE+CeNCVWe3d@(x8M`}IM)x6|(O)(aEl(DV8G!X81(6imodQ^t zpJ`jYQ-u1XVG&qe)wGr~Me=P({)=B< z?qikB$joWGb07#hZm=vn+pgI_W=Htvz!k&M!OzW3i|`4^p@bj z?Ii<~n}#VA{z_We%5jIa32cqYD*U4u4!Ev4<(?c=jK)k|ZX0}l#^r|Flq(HBhx_fiyg_V*H(@0;`mXg}^F=8v}ZlEym zM%Dchuq>(`I+}F&?dc)!LSF!yIT?(LFK2(>y6AZ-`c27xRi~EMyco4%GwywmMwR;ME$7hfQY3Md*~n}(x0POi;(A^n$0LT`U`dj;IQ7#p3!k|zxhyb&W ztWp)Nh^GPC?nyU$>Ie5n?OicEJFGYSz3x7tricqBZ7J}F0zHjSuif~^@h)r73Xxcc zngiNxRkd+f6ezFi;1cofsPmWk3* zG<>?Na#@1&B?n#bQMx)_=1nzIz4MS}U}_2WSd3|cu8CRe<@c!fL*)+GTJL_n*(Jit z;SS0o@weho%6)}W#YEbR&s$v=VuUYAd18w5d?O15W##!8)0^4+Hv7q(=t-R(~3 zUejBVC(o8{9MvoL>+5~FP&qSYYcy02RWU_1LtMP)AT|*h=PT!;TZaHmjPYy5cZ9@{ zkjwzYEIA|+AuCKb@9kFS&&EfOZyvNSFr_q-_b1dK=H%Pdr31|Q zj?uPziO!l^%0kZdZF#e}3`p)W`g{{DKE0fxvi+UoL8+~GBoAt6!q{UtUx!${1V-l; z>lT^Jlg-KLE-|GYs+r3iHsnElGz{9E!x`3KOvOfVYP^qM|EXC(X>$y%3T1~8S0#q& z+aPX+Z3#BT7OK~jqax#2+j?{=jD1PuJ|bI-ZucrY;4onpmpNcr*W<3A8ekX+ez&Dt zG@r9mq3m~vsFma%nP9w6KB`4c`}^+%J)07$+~$&{e3h6ArGJ7dm}~a;<+7O!I_4== zX~4KiF^(@KfM(;!si$0ao8`s7bAD|#&T|BNzZQCORhOm`d zVt@@4EA*_3o4`rdH+4Koq;yz~Zs;nzrbeb0=8~TEEWuL#M zi76jcg5$=jI_y3R&iBuXLCeIfkGe~+>r>D0eF~o@LJ~$LH1@k3L%PvM;E;?#YucHlkK~%-_ebhA%Tk=>jVyr0D~OG zwQjDz+F;K-dB&^mSIfspLf%aEoSjy2Khl0!oAJwHAc_axvQS zZLTaN>DtvhO0o=A%*9op+vk0&S?3+U>gOT<7nIzLssaJr6iw(W7>L%Ts0yn{QUcHr zAY~}Ka3tt-Yl0Vh^mM+o$Z8q@1`0Om{S%s+p~>$fB8q8dL4BC2tWb3z&_Il^ewu!d z0eSzEgO7z$?<-IVT%6^{Vxf<(m7)s5z5Y0KK|WzUSEkY?+kkLo`~gg{tdeTp9C^{>|8#Ca5k=H{{%WxK4q1BkNZZOgUahElP8+p7a0 z8z~~qFBHCTu3wrjeE`&uHtc=~D;wOx$7m!d`CXfhV)wuDsJ!SQcuVAe=8d-lD7$vs<;G^6Fr=PzuAJy zqBsm^7yw0ZuYJk~=M?+4&K`P4hA~O*3!cKye?tc2F9zQoi>vFtkEvxwFR3wv<1&9o z_DJh7(m8%%0aFa}*A~(4NEBC5cjBB?p;H=piyy@Wr8k?CZ6KdK7)=KO!IVCvw%ZYiK=5UrndkF zxF2hG&nlPcWse|0kMYF;Hej**UxnAqUGjOL2?E?gzk69z4mBBsdT0WB!&o=iUetnEjFevOxQ#~I(&GoH}tYp({ivpI>d+vXC0n0CW{vl6}_&^3?U@ixl zf_Ft1!pqw}Ydgd&2z($w4c6S&fj_AEUZ7)4M-H7X1o?>1DB?QH<=Y&D`U&|Q1f0598@U# z4cZ9xVnpI~XrlykaywB!?5FoYJ^nI@_i>N?o{tb)EfIPpjChfg!M4qs8nV=X_125D z%O$7Wf3FKXe<9bi(5apN2v@V62#ve!V=qSalKRMe1>|+q4EgnNtbr^+ww1^d`UYT7 z@ObbPFdYFw+~pm#yZ+`>aofyuSBF-uw1|GZ69@;?;0J@x9k;9=C}(74{c6po`?j4JFpe=5&&CCg<_(g!GXpgh`Z0kXT6ZBKI+(>O7N7! zgyYg@>*~OKa!T&&%1h^^2*ISqVvLr@(%XVoLByNW33rb}UWr>pb|+ibrq41)BtR?E zXORcXm(PJ+lx~cE8Kx7zq`5?>Cf!EiLrAiSv|*~<3w~?Z_0CwopE@O1;3IpE8!k6O zvNpv=CnZ+nhOHN8%L)bHwbX9}li61jPOWIeWb5qf9{kDhOefS6tHlSY0Dm7V67?I? zV`Oa#mH6{hpV$+^Zu$=I&+NO_QRiFnDH7;V1|xy zX!Wk^VnbVQ|LGyF*AVo2xwx+Fgfw6-M7CS83SQ8QM9`=jCUDwmr*{CrW1u9fJfO7Dccf9XG z9%(jc5Iw2)cO-()T#&IR%iqzTMonX(phU|p>nL@pUE{x zuM3$$X-C`FH4l0omnim8j>gy1FPg>4N)OJUw};LHCTAn%0{tKIF;P0Rn%O3QK2v*n z^UkPN^wX5)O;Yw>k=@b|$|Qm@k}p7-+`*<tD?v$I6bG8DVES^axecXUOT7-yL>idfC?aOv6s8tI27 zub19Cx=#j}J((@C}f1^dy- zq){c{^C}gf^Khx>@P;xiIQao%j;^2%TWePx7MBeC`2|t8?eEa|0c~;8ee)mzO#^RE z9@4&n(>+We)AFW2>V*V-AR&t{2Z}0XyieAM+Be-qV@c=}7%#`%Cb_K|#Qku)b8_h_ zrL(jB(dg2J*zT0A(h}`s1ZL1Eydz`^cgFQYv5!s#bw<&QuKzPPLQBPv!~wY6 z1BKU9Yx7pI<+cBj+cwndl(A1YQ(sFO1H0W#BR1So4!wO{lYwcs(Arx-qr-6>3{~1C zm%6xSibCp*-n^6vDzU?H5xbf?V`e*EqGWx?H!GUwY0>f&?|ZQenshU#O3HfG!8pv3wv3bu;e+u%@29vOkZLtc`&S5AzD@!RuYR_ZX!3&&6X2D2SSZ^8JasAe7XBhe3;3j{$AFC?tP4 zdDRE^6^)Z88&0Da8COIy`am4N*?moQaX)jjm2 zd057&UgJU)4QMBx58m=hn~XL@|1>i{HxQcyePh5Fk!_D(z%227Z{>35y_r&g4I!n? zd&B{LfNFsdlB|Lx)}u9*`0l@r|7{)-ymCrO!FO7{WNONcC{pwf5O&=rimq3px&$lI zAD41_WzlB-Ad;e_!>mW>Y2Du1sMX^ZK-JEIKjx?2CNM1&C0;Z>c$jw%&QtkX?FEjgN z76DG8oINgt>On5?X47lx76)V{H3Rs4bT`-28|Rdf#e)eao0`h-Y7DD<2tbEcEBUKJ zdGhL7excFArdwvNw)qzP=jn?gjeBLH6V2P$-&-csf8E$p0VFb!nFOGv9lsZeTHrW! zjNVioU^aov@=@2CCMB6xJ^EG0wgPccRB8^2ngfiB@-bdy>UHn~x~mkdPw!m3blr^g zTPSHXW95IFq!2&_v}Op&CG{_K8}FVSpuA3o4%E6Uq5;V=xAi)DK8CykSEdI~RBKw2 zYD|VFS}8(HH8kkC`mQ3wzr}9NK_rOCZSvJ_XbVnhn$x1F#>#s~>r%P*Ybw!s2& zC+=PC_||#sac6kKF2ylTN$7t7KQJB7XNs^-XH;3NDVfTR;A}M+=%_-FyLGx{;JNxe zeS3Qrfwd_yCI}!A_!JQOG>4kF!!}ct9GH{;o$5?f_Z{%UTCFsDv@p7gRY76=oh9dLC^#lJLWT+FXpNCdFMQUiafZ%*DI zaIb!^Dk6)U_Qt<879@7Xt3H=r@=JYke*SQH1forW@;Qn-8}q&T%y^EA=}NhR=fIHhvpfqh>QV;?5{a=X z5zVBY;X(+^s@)iH-DS;I!453-JkKoVUB@hXe^9~%J>rUh=kQ^B!;gz<@sTd# z!{H6~E9PdN(`yvS1iv>jOU|6#4(VgLSyXu$#GAi+;r~`ozw;;3@Plp24fd%FXHXq> zv!+Ze@z4a&PIB!xX0<_1bOR2aq!p)Gf9H&&4#y#&aYNxf4CIB?Z?;0O89?R+pb44_ zO^Fxm&ic!P%_$e-2yus`v@VKfmBFK?e*1Ww7tgoV>L~Y#YB}CSWCMfyu3Xx#P$jy1<}RJI~V^g0@P6)P=eXtKWtWjY}l077pjKUkG73fnk7q;b~8RnX1`>y4J8L_Ff+Tu?!gC z=sQK5Bh+I>_J4jso8#G?MR*4Lpu{dPl5`yG-cQ~mys_*Kd?w!kL_QL#A?S>^`xhnz&7%QT*9oC9hiV1-fmFOr5|GFu zHqsvUPB`r2t!6`}!tGoh61VA#Zwz30;yCfyUi!Q`UlfA63zN@E( z>bt_@c$JNkipD@pvDHT1E{F}P>4sZE~$|} z@9E$Bt!`{3eMpx@Vz7pQgquY|asu6>N4^YvdA1UE-u~v?2TxFx9~&0Sh-aHOdfzX} zUN_xC<|kSPsA!)bG>V52F(;oGfZuJD?xl}Ab^VG7#ivd;*xb#%_2(B3PhXb?jB}iX zULJ+W80d#UfEUQ|E3%+jJMOOerTiqb3R+i`h3Xm}y*F8;QD!igT9bJJB)f+7Ha{qA zyQfgR4SEXwCr!#Oc`k?TbKX*uu>?^QYN5h}RicJoS4jkbTsSn}7~0WJ@a~j>-lW>I z0r#Er$(J<;hfs^(Gp|-Q-zH*#C&x=5;`}Y0XQGXvn)p_o{cnD)CCh0JLXYO}W%*-; zG7WfX+jg{y7?C*%^wBu;44{d?{H6h%x%$6wV&1sA(B}TOXae zjjQ4-iy{kHF-D3P-^2Fk!mr+;qGj;q@6Dkd{TDQ!r7l@$-XJqY@UEp5zil>Ur9g{d zU=}7bqy%M0MaFvk$Ikl9hp3~wQ+7%y=aZ%WNgP;?_w`smJPY=RTH(13T9!wqv(whG zlQyBCqJv3i-bdVq*iIi@sD4f%x-h^U+OOAMo^NfL%J|Q=+Wysm1pkk}$p`}08R%Dp&Uk~h@#2N{ ze+c4}j{D)!Hqq`Qj+ywi`=d_i#s3Xi{*SfaW&*uVUZ9$~+V1->)?O%pGvB#hwnuZ4 zcnY9SuHX8^8?GHoA*#gIVz=*z5Gq=|q4h!LaiXQ?sbwq5QVG z=`WF*-K1gf|GzD=GfT~QdftOZwtPZyGmQO+VQ+{NU*cPtN9I@O&O--BF+&t+BeBhi zJ})jsAGT-7{Qr%<2i=}(T7F3Bt+~gF0f-v%gN~jrCIpc_r~l}Op=MPoxEmy>&v$y& zk^5sj{VBEP-YY)dfqiKN7U#IDr-_;}X#Ts)sTUwu-`vRCIhFGXA=5yua)5Kz52#6)1i|edOI_h zxnpX{!7f3j4f_{tHnzt9{NmOQU~_vfcrSYU+oqkItN@Ndi%91WSv_eQuvQe)bVv<+ zP4LcitABlRtM21u?V4JGA?8<@iNe~ciQt(CnA%Rjo8=w!0i=$S=yA&h=hhXu4PA!B z+Q$GiIUb}|wFt|Ial3bnYY&oQeiEiOOU0+q=gxYUyDWm~lVKx0q0Na0;yxs!*=LWH zou~vBs>6*D&hcm69u{94ir$g9YHq{veq(h=N`fhq;e4c zgFLevTk$ubnJRZe{ySnqx2vP@+_}eza8PCds~9%Mi-in`stoYh1_;L2i%k!MAX5*y zSuViZfL~=u^ocY1ObUL%lXe~&o=OTIm^P>)iIH)N(z3O3YHE|TW)mZ8rLfvvG=u4h zyAuAhMuHFEcu921mFIq%K_)-X<5*p}{#&lLMVa~_@M8msZU-0VO->xw5#ydD9V;=u zLGA?~K#x^GoQ}P8%#q!34cKJfYX?KVsn`eF7B|XncaDzZLQt&iAr2Ytv%{$+E z)$JP+!Q^M$vAye_T|4r)&%!G}Fzw2M7MsHSX;a+UYnc5maKB2?3)%dEXA=>}olM$a zf{@{?RxkAV^NXr&VQ<|Dc@fg&MMyl}_(O0a{QZG5?-f28SSxotkQ}}h_Y8?`eE++- zTJQ=S9*;%>=83hq(>YOuBA}{ZMeC;G7Mi7Gg7xmow;GZX!3jc?H>VRw{N>yIFwWpQ zoc?5zV7fD}+cRmf$T%2YhIhEZwAR3&xag~Lf`8zotM0nIwcLXgy4z5TFPZ0<^i?xm zRunfMeJNuSJbVV#87Fs(qM6V*&ex8A?!{86!!`9bV$X<-;A&JN{iigcfMhN9a~lmym9^A zq^fmUVt3f$Y$u%$6J!CRzy0jiWuFd*h!zjBM6{ol>4`1=`e zu-4i-t91AVd}RsxywR&--TtVb5!Ujumo@`D1FV_}7R%eZ+leumT2IqM-vXnD;^?f{ zx^-Fxki;HcRU`L`Y}QU)Jkx}UxEZ`YJYmuSJ9V!rL5fOgUK|ABd4ts9@bzPZCe=Az zdgQkt4%II`*~`+4VR{EijZ4E0>|R5{9r=_==)fu!B8Fhn)=q=dAvg&=eB$N{-qgGE z7S`|V95Jl``z6$>dkr@qc@mXa=L!MXJ+r{B1d-uH`(w*yk}7#fyG2vcSXZ zXi`j&p>G<%fgENkg{MAQZlpsT#haRWr!Y|nqXIKt{(O>!#__B z3Em$;twmPj~d-5C5=IaupGQpEh**Mr!>UNTq$gfcCP>^hsSZrAdsZyRycD z{@zk^D4g?|J*)h|Aph`GW5mG6z>mgH*XY8}KF}8VaJOclw)d_CnsB_7Ofh}_Ns^+b zSir3HeZ$)x6)UwDst<9A+1@7Tw(J6-T+n{n2U@&I=+_^9h5GB%^{iYV+tySN^VK*H zh4(Eb>5Kwqu7Lnd1549~qOs1?oey03wJml>T64*~IlCf)e~pWq@}k)Z?mj^5B5z0x?hYMm zVZJ7$sxdT~H@gWRbXSO0Piqd_^JVqKTbPfQHRql+w?86w8hmn)x5Bq))jFvSlmYoK zM_ww}LVcDudV9J=O3Rm{gQesiih4(vp(7M92tK!zJV+-~6H1D7j-8oz*I1stMS7Lk zS+FBLf9lf5;F(9bO43l7tc^?$qd`u)|$BWKHa=ulK`T zQSZGyybdl~ryv8a7D?}rEfs?3sN+3!HR5U5=`((tloe@6rSlP=C3zEC0)Fx@b!Qi? zDB!=70CTSn0MT|7^Bkn7M~ zPHt*ao{^pOK|Un&*PDvYJWZ7!Q>Z5m*3%-sH#Te?;DR^7kFmqA(64}zv@Zg^S{Kg5 zH#<=XIgPNs&y&4bk8NBK-veX2P#m=L(!#ax%R&qyx9CAUepFhY5 zk~T+m%FMw}J!zG;s?KU|0Q%Jh@=u?T$DtZ%0q}lfKy)f|`ox`V1;(Gfd-ehz;DAdCaBZlla+ zTPzQc^9p&z-O}cEgqkB`!-gk4IW3U<=BQ==rV;7t!#*B#nh2D62cfG;6pwd?=^vf%4txeaJlJM zC(1Qv3vb-9y<8X=1g?2tQ)Wn`h}62NRf4%r^qV)RY@5q;=8?AETdU=XFelB>eeBVXh7jaD}{{ zabohN+YGUe{*6hOy%v1W#tmxFnSliJb1CML0xDAqI-38p@ZyjgB20Z zI~lhrGV|JLX^+ujte1Y?_yKz^ai&T{`S*J;E?2n1pO;Yo>Cq_2-ZFCEQGS?o*3a%p zCqxh1Rwd9V4sKHhAyVg>{D^IN8z8{W+(rI4dQk|a207Y{8pkwQGK<$N5Q*>b5E(o_6XYxA7d=KKJ@1-v>A@~t1mCBJaAKei>|@uuCY z-6ZKVKcCSfmKO_0<#2s#aA?vFS5WG-M-c=%uij6OFPX%gO~XES#k#QNAF zgK-splatQHc=KpiOth+{AIu2&95kjdH#$fo89Xs@>fU|dDTwt=$>Dk!v{&kf@Geg4 z$Z0uEdIGG)Fhb8i^w>uEJ!^JpmME6e#xSJG1o|e$g3Y~I`B1l0cj~y~D#~eIh;kAG zJi$9>aqGkAV2O{%1GQ1DZil2ej7e4&s@tCuLO5m=COvn#UWAA_-@`#LHcC#$*}hye zr&hFlybTTBZP`XZYgGun-TFA9??_s~g(vbFW@A>*pm(BGH)z>&y30dF1dl;Xr<=Uxbd2n1ntceo?CqZ7T< zD!R1if~7d|7bO7pkUt&Wd$9dwLd-2lJId}k%i`3)i%Dm_X;8ZI^ZmR{N>^PV6LlFRl-3RwNeJF{H*2=fg7jd zGy|P37e{TED99yMBEmm!-`?K0uhU6cGtf8C97=V%gxEMdHN-dj5t=_7Qg#P$Y|6dN zK##`mO*f~TavmI#^$|N%u!}Ed_5#-b7+U%M@ml7Rn1f!B@fMoiz?B9x@+0LQK;LOs zZn3I1#h6quIMAZWwwPT~Ps6o?RChbr;ciY(Ux}8AUxa5yO&t`;eydJxFZhWr274aT zJ(Y^Ghtm`nS~ zZ>sbi1g+~xqRSKSt=$x;-T4BIc?1b^rJh(Q(R}~SX=kmHqnT&3m1eFg4ao208khwG!Q9zA)yMPjgF*^1bn)%I zlxwU1TyVPZ;&wk)Is)uS8vf~^TbLdR-%zrahXH`JyU!l=Sit;;1e~;UH55^(EP{0m zDEK(l?J`ot>YMtf*;@K%49p}B;JX1``^*pLhB1t_8c@9LA*TdYgk>#_4hJv|0vE}z z>R<>euG2;TMkL~a`W0pJ*6E;wes1gZmcDU#8R!aq?tlQ%JL(wI3JfoX+?qWOB3vDpuLWfh!lhLpP93F zVzFzZ9nqs z^k2Z4;ei8f=6$ixl4C_XO%1CnjkQ#bJ$J1Z5z}vqPyGF43-eVGHsWVDoaw}subH!*r zD0MvFBIxFfG_W55Z7lAk_!PvnEd&BXpmFqJa_@Tksoq(ew}i(fpfdImPxjGF@_kEH zbiTz%YrfRjFU%L~*jqMqZ<*rh9vB!91ESJCRsQp*a+kezRW$;>!#Uxns8CVU-zRH| zq~rgU=-7Gbt{$|HxtaFlpYXL4!&zSPCzOr9c}lI@!ND};y7)u|(xfJh9Mqe2O)2qc zS1Xm2?44E&boR+K318jKH1uWvP()6TOUtxIrmMUGY`!=BI*;fgOlo$G56N3^@QFEP zYaa=e%9n4q+hn0K;EB^zJ&1I&R^=AT5}*zEe6vO`H|2fu-PP&QyeRfhz4Lc~8WQjD zt2$^LMm{}v*le5%diMd*BZ_51wt1lF4gr?W%a?!YwRxvy=B7&w?ns^40fPhTh^^>G zQmo&|N7w4z?lJHF=!A{R0yIbS;4QdW1;mF5^U-T|`Eu|Tn2rLIm}C6p5YAknkb#pj z4#J6V(|*wqmb8F>U6dVstZ_5&fW{(PVq05^VEY+X&qP&oU^?Z|imm%{0dYlv05IXE z9ZkmTD^s1Cz_bp?H>7vVg8C;%Yf^Xc43JiQdgrs_@Jih}8~Mn1{M5%dnw_XdX4rHN{mHn~Ml7gXoYmwFB6bO!}gS#*BH^;&7W&$|zO^(eN|Sw>D< zl|0eh*6#Oy4-J|{5#JlmFJN3zOLl9Ec)*KpvRS!~SC>RHzT5OfE$|X19k5mR41LN) zphne{1suFi$*RSjYrsRSf6S@HrG4gaH74(qu=|g%a~x#GjaX7owmV81P^LRMwm1IG z(D2sc1tqFJ>q-a0z-v4W5aLCNc%1zxE~}w0AMBqoz#Jl`>4}%tKbDes5NsR1*su{@ z>UFT5?c@+tIHzUV$=q*6y$8v9aCQms+mJrk_>fxIE~KHgBnw*re~ry2-Ew+cE_D!e zy8TgxnsYkQ+vdj({&pV)f~1#wTyPk z>@cFLzskQ?>b^8!Kom(*;8N|COyZU*qj+K}PQ8a16tEuDYV79 zMPP@>8!rNP2nKj_Z`=m@|4(;vizlp262x8Swv04)B(aBkWEIl>lTHbP4t~kA5O?ZL z3B|jqs4nXqknQBgEWr-HqeZE#cv7dDqB;CAega#F@DsW}#phdSjk7I{o`rMFLmUbP z2v`5`vmK+2O$Aeq3M90?A(^mXMlxpfCFRRht^(Fy1hk%T0Tg;|j7fE}BHi3ze6FYC z?X4z|h6&Tv%YxizxD=fp=tt(M#bzRon=ZV%VS`0A+`HMz9{x9Yr?a`$ZhEsUNY&OikL8B>#>0ysoK zFCr(|Z_SO(Gg(M6S@GJvRsCakBhx1K15wXvkN&ErPdBfkILH%e%1vdkF|bj-4~_Ty(WQP zt9*NVv(c?&6Qfm`ukG)%>=RggBi(t(?@hxGTk!a_K_WCW;A`<9CaET^d=lLOCVm^L z67ir9#k8!7<6E`ZIKRY`t)e;2*Pi+!_8_YRk=umogW_34mxyWy=D7x9#d^1%PCjYt zE1`F(Rue$-X6q3j8k}8x3zpeF^m|icXb?8;{{!j6+eWVRn%&=9Z2@d(le~Q}atYt? z7q9nFUTFu84-@QF`M$*KLLuO7zL}p}<5OM&S0a7+$y9_^WGB^lQ` zu^_m`Uc-$)KU0KCnz^Q&AaGrnca>7gT{Z;-9XQ3wcGG}9g9nsl-8Cb07$O=7|B8Ef zmcYeVk$QMU?pFB^aBE=xqTVhE3*?1+2H?zB{;w=G9cNS-l|&zq zawNQ_zb@;)y$+|)YVJw;gGyl_2lF#MjW5$q^C|zG0A|+aLW~>_nR#;b>MssnDjKQ- zs}=HzHno1_+zco^!iOii#LxK~*0xhko@|QRy}k{5we<@vWQtr2AFYG!>{SJYY`WXT z<*A$^(1MxU4?FfJQ5jnEgzt+P^xukaXDy`9LF?wT>kM0l`mnb&9t`9{TVzqw3Kihp ziJ*NC^#W;ZWM`yJ<=U@ZEq0ecPy>mdQ4wU3DNOZ-3zEw)f^(3VpN&;LF`V5QA((rI zn4xTbi4{88Yi42WQ8GU)F5s?DzCgNk13^tw0w^#a_{%Y$4=ic+0p$P8v*oCWo&po% zz2C7FH_^K22qjUa`{EiVkIiC3!gVUh6CmyiB6%Y9GW*^s;BZ3CFSv0b%i{)k!jljl zE&o~S3sA0?B|8alpML4IAytc*4ypA` zbJSCY7lbgZQ$=Bj2>6L^J~&XT8y)rS`pbJK{RY{tjdXT%p42)gYVN%3Ca-?Zf0NhX zuzxY~88pd7I_^2jI|8hRhHGcY*O%hd^jxF3L3sx$vEQGAu_?(erJfi!z zPtgbi4*@3uVL6bH?6rXPjsPEI8UDUr8%IfhFNDCLbTZY$>@AVz><~k| zTk}X(q?Zwx?to-Z;COxB_3l#@DQ8BRX6DdP3 z;c$9)?y%M;3k>?3UQWiGUa7Lw-PqQe&O|H&DPQo_Ga$ED+XuE9u59y9e(uo=el>62AAMU| zzZTUkos}9DaA?T!oHdN;WNt|T*oW2+iA?>x3X`m-eSRK$!^0f@#5mfIdD!E1(@_Qu z26P90?SI5@>nzdnGFP@nqYHGlIl=-tQ>Cdh+wVGX(yb*s01Oc+uJz@jXEYn1OeJAxDN%&6+6GH*h>AYItvRpPYv$kiVZ`iLN>*@0Dk zl!tV3hu9m3-OKF0L17A$Pj>a}Z zEn<_Cm5;RNCx10{MNq}PK=xglmpD@g3uCjflX-UDTPq**YUiPM8YU)%&RweH;_0@A zLv7VE&JF|#aa27MbYn)%qa^iuxx-g(MHx( zArQLnxmc}Fx++2!{0wvQUj6DWM!o@q>o&Rm?~kM3$cqmh*3xT7bk`&g{r4G%2>m>U zvok8Vz_W~o%mZutP(wy@#xHU!nZ1gDA!Kg=Mk?m%Dn3YinAmA30RKA}tL(db`&B01 z-7dT9{;6jYPBDCr?)})krdb2P?$et0RABsAD(|q(DBRVt@vyJwiA?GZ535=8nLfKd z`b{wK@eTMvka{_A$xeN%{z(Nja|%ubuWZdCVUWG}cS+*v zh70rb^v)!pBf$H8&-#b5nUh>>o1d}YUVDa#HO|N*XZ-Kc|fShT}%W^q^aOG zt!r!a_Z745Q+GzckQ}$&^l-el=Ha;Cdm;h@J7o8@-u~AC1Nq}{mDXM@);Z_%?Yn^g zC9~qsMV#0P@Hjy(q~jB~xr+svG-)`#$Sk-#Js@d99l)B0-c_Hxz~GJ;-NqrWfL_K| zwH&cj6TW$Kw&qz(#7#h=IEC)uQDk6=poJK%dOPd=wD_BspkF_bOeXGSb;COm{%dw2 zKrslcQ3!D@808@{1%0my>M4ei^Xc-*rrx%4{Ab_7JHuE7HmG&+ zh07NGG`2eY4?2=R*2>-It{DwSS%;pCl+iY22|3FAJjJfw;UuqDV?gY4ok5WK9C8n8 z2g}xFrE(Fh8YBDyb+#zU&a=MnvA!jNUx2tPdGo4e;SIDGb1u5}=~e#8YZ4;bRfVJ>Uu#Kkh;e5Ejc1li zph>!O+NT)2OJ_D~u#iM~0pTP;1&dstdgncGG^wQ)B>b5&j$OHXa8fv!y6MWqY2f^|5-SYd9kJ1MWtWGc%8n+x((uHe+m&YLoSgBiwY*6a4wvkBv&HRKfgypF3(Ix! zh_ACXahC0s!-PIkZ?QIE>mPLSifV65|R~5pA%vLdsQ@6SdQpC^m_CY|)R&7nxQRft?9(EXlt{$#v?+!&) z#scr$rOIB$T6z)KxQUHViSW}lFq#Mj*(4Pra9jmocD^QaIR=-QL4_!LtvX{b1m+P- zO{Zp^9a~IQrKvBQK-m=MKOTU%vV7+0vw09%;QjZ2#;4ci0IvNSAFSmPT)1Y9moP4LJ`)3)xWq zOP}Nnmk-W!+p}6eKP! z#)Mx@sRnypxYCiWNJ!bY>{9x#J-|M~3Rg+vwnJraG)mGFf3hczx}E-YP(YyHA#h(c zPZ;+N;-l~QN^z!{-0tn3?&4QaIL3V0^G8|REeZ$Ut~Z=DA8i*s(shTU#j&0f>iT`> z7JYds=DYqhH<}UF0Qimj)q*QrhHR8c*vbYfUs@JtLSiX`*0n}XDx!eRX1@C_0?s%73f)g`H15Cae037T z@A(1jL`x=4%O2jJsEVj`x?k6N9kc2O_DpVoaLA~$G0GO6vqqK3OLz)=?M;bGMeE#X z&KT(4%tIY@Pm+Hf#!RTr0wiDi zy^dYeUeHUAT7l(R=X0)IWmcfO0~-ei3EIX8Y;s0(xO}`-Nm@EcTIZk2RlosmH^rb- zv96+C_!N{h^@*E0ld+4us;r%gh4B4jsrP*@l(xVbM}9@Wn7a#TS?wp3et*4GR?$9~ z2A9YnnVs~6Tq3yXtol3??E3~pePnHY;D6XYe{nBn*y-U=K=^9@$sbjNa81K45W4sY z^xjSTMBUnkkPv=YT6sp~MT-!+7H|h^mf4644bRz0n=ua1S~* zCj?Dj`9m07wrRQQswsjAAhsK#)6NhoH|0F||Lgh(@NTVgoc`y_oZ+5*Z>&%EflE?vL(Ba6=Y8 zV{wr*C24)Gkz2d$PJUMzL$gGddI-MPN{i`5L^ZFgtnH0csRy8yr12!XX zY`}_lUd!5Gf8@aiNWtz!^Vd9sUi(Jzd2RWGB>tP)1&TS^LZqX+j{TVPMwivg z-s^wOl@@H$Qkk4c0P5+|0vZgeG^@VrEusEw0`i&8a?jwxy9F>udJgn*rOg2ob#X(@ zE^}`e7OEC4!Co4gU}8!a4-lJED2HJ4w9*UUrInSQL3WLLvFQX*C{@Oe`$Fb8O2rjK z9hb6S;St_oh&`vX#Tl4(Z$TWGRJ!?WIciBH&=+D-DE!-11}X%8!T?xqmrqhj zHYQMz2Je&av+v6mV}ixE$K+(v_c(VlvO2`&E5|K^a(%1x>kR)0yjvzmSfb)R>t7xo z;6mtv7hgM~ut7rHtFt$$i#3{my*mv~7boX!=VG*8f+%y@kWGh5wIs(5v3t*wbsWl* zI;Nn|NJS@8c*NxaJCWt&ELyAJ4jw&-UTG)KZjLjol1++IQkZdF^wR_xmIK}BHKe5D;CQnpj%I!Cbz6M$3IY`S$`{6!8U zCB4+01zMa4mw&)pvq2x-!E~F z>pbWY1gCquV&!9%nM;`=9pnzf`tcUThUFZ#a~-JdfJMcFyqVdXL5@Z!GKvqez01bm z^AT`eX%vTRr-9ab+UD(o-wtMIr9OVx_*gH|O#j=hzrRB_rMGJu{!!FWr2MkHKz9lb z)`B$(^4v0PLWbJemb3haVu=3%7L~ZpR$h^LFM{!qA{RU~%ypGXP0 znJES*dQtx`djAlSS~QMLQIHUI4Azq#FPKkvXk>YSU>OVj4MKm;3cd4QY4CiEJ{xR~ zieMlBG;gfRn5njgphU4`+OF!43|hC4t`>d*lohjx|c^gpZl;vg>0 zFS`zRodE)HB|+nXzmNJHwn=h{xr- zn^O>pWkue1Q#DrytdA#wO7AD1%;X9QoaHuDL`OitIn3#DK<#R!t}8j~93V?>BGtgv zv%h+$?IdxDxWR3HTTC#uQFA1TH3+j};3B3GX>-$_VY55E15i#tn)`GiO#j)y;~g*1 zi97=Tfglt#&`&DdYiSJ12`7ILK!iQNufMVZHK!0HkuA5@2=)0vnGoFVK{P77tfTm$Q}yyG zY0HJ@H$;sDrg}k!U(vy3atqL%;JDc+>Y-rl$`0du3p3gC*J64+cv>&*Nf&1xvQo1^ z{Ww&&cmuF7Wen%|-N;m@4#VOLYXBbIJ|1M&_jB8}Ei8wCUYOT$@|Vgh4@wT{$1?;i z-M%VMBV^VVw6o@TPUJ=b={w*3cm?H<0}eG}Vwj)^L<`nbJE?L_PTxO2`-_Z^=(nrj|6)o&%#1QgFuUit}%vqsi?xo|lX^F!W?%g`gh0VGI7gD>z z>Ii{ZTQKcR08_j*{IlL0Y^^DQe8$0=(1s>{!TP+)%Z{Hdc{$o9muT+dLkw;(TRVDYGVhwT=v>N ztUa!8`ldZrnb8Aw#pF#>Dy0=M8iv)INZbXMFMocPDC;E8FW(>R8YGn&YU-Dxri%1I z4UxX+*Pe}uX(O-JmN_5EI8$O=!4lngZlxpJ201+_b=Y#WKLbnCh&z?2mxq%c1~pDJ zk{47hq!)m70M-+_RklUvP-S#Bv&x;(@!EW2X3zPZsAzVi5V^SyRNldzYQr7IGCxjl zqY){=Wn;@A8p#prU&x!o4vV{O!2qyNXZ5HlDoEbp(hmud+Ao_(XCw0$*(|fJmC13;0+L5Vk zol*xoo+qDp)Lm*hNL`8?Lny9YY!DXESU4Pn<-8ZNW;JfJgU@^eTj-}#V4)|}2=iab(Yu#o$=)&jtLTZSK zQ)VPgF7tqo@T)Bvyvl`K-gK##4CLXN6fL>d$9ob;<$|yK1GcV(fHzOjK#Lx-ToLWX z;Nj##MNMsoS7kFlOq^fgDF`d0BQDo9+}8LuBmptp-;tqt>*D(4Aa_~nkI+?P_4X>J zC%I=7ymc`#G&6BLwJc3&d`F&-AzdOGKnCee0A<3RA$8ke5CNmVvbs4y zRE+T9tR=(*u`aYd!aGl;YR56dh;lsRP}k@)RM~h2miiQyte(cat@I|)I(o!zuc!;R z@pXRD+Ybh6ooIh)%Oohz@QLH1c%)=spn^OVtv|lmy%IRky&fAZ0IU?MJz8l*5K)VD_roq<2l3Zo zpY4T+sP4<1D3RkU9X@{3tf8@9K|&8x6*OvllCN^&N~})4>hv#TW6hask4M%GSm}fA zAPwvojUMSj;a{f=kDtvD!G4BJX<(bXw(xH59<~4=JqA?^nX1&VM%(1p(Qju(!s3k7 zjYYr3BgOVKwcd|{&XNEX*(@yhCU^?Rrw6${eu&LY3o6*}3{#JzrI32_4L_CmtgbX{ z6i~rB9@_LoP@MeTAc;%n5QGF)#@b$<38SWC$&I3CY-JqdJ_FHX)#t0DrF-`Z0^qqzV9B_H_#=%Ge4(FXkm+k+I)1cdX<6-bJ+^GIJ)>$P z0QohL+$Vwa0>$OQUKfoU-6sjx!g%LEuY%7++8FfiIPygE4U$eSGx*2l1S*q^0v?8- zUoD0vWuOxZ_Own^I-?q*U7^|Vo8z~I>I+Ec4s`$zY9QWPnn{V($fzDM!Lld=yr2%_ zFQW}^;FlLvZE3Q$ zlR9M1?p&nJL17Mg=v`3KS06m1346K%t_bW%gaM6fG(KciyZg;!64 zMe<;K?J}wdF6JUH9hKQE%}>@^Y}uh+vCshSw}ALRIo(GpX9EV2OtV)L@zklwkY2|# z8SP}RK|rfC)z0r#^kw6O_%za`-P~l(zZz`5W!c12O~KiAVpa{k>^Nr|aq44*r2)7}I$c-RPpha~1kD1M zrt}X@D#r*Vk4O}k3e;h4XGsc)oy=EoZ$UIWTfS;{VKmr^wgD$ZtsxUL# zm?J&BpLN+yRW7A7>zBB*e6eRZ`6Jl+q2(W)gTI388Q@IkgDe}`VZgRt_@CTQYWaO0 z<*7A`UiW#L3w+hy(JS`xJQ3l5d3!%|!iW%_y9Xx#n~~zjnN%a^)91h&gGrLQ6?x)+ zf?bQzZw6s|4YPOl(FwNZwERJQ06P0o9W2d9p;KezhG-Zt4FRi8X1>o#*1XCkF9ZA) zA3J_gC=*hB6aW*Gu<1;J4f3pktZ^-S>VRkvgpgKlZga`3$|9&AAyC{Ls@PgD05)ec z{X4nRb7?>x;$&8+TfA(n>8djlC=y`7hWMtKL~CmLFyk9!^&+Ogvp67&%bc`_`8C7_ zS{=rb1Cqc_SrNGaPU(Y?HoLsKajEfY0I4SOoL%&ggu{mh8=Gv3-|6IQ(7Z#9%JfI# zF`#b#XG0Gm`Sf#oe+e*_k4Q{BPfz9m7^>b*gCa}~p_w8S7z9_acJ#hjQ z3Z-aU^^(J*%ze;5lzJf2!-5t&I{)kaAli5EC;kc~k(G02@{Iw)$qeeZ$$nX!##JV-d=-5O7^K#89$SeGo>My zB^if5&PsR0Jc?O<_R)P{uw6y;aUWZ^(ymR1pb3pn+`J;|on^hu z+^9mir7P$QiUux4sf7o@jV5J?A)EM^t-Is(b;>_4Y<6BtltP}QzY_s9gaqe@4p6DN zmw!wd-UHlSK%>177cp!WDy7p!05)o!wsG%$DstAsxgUxcRkgZtueU;&O(79bVri0R zp34a$!`zIh!EGxJ217~XU4<>}d%HM&SXw7Q6?*w!KkTq%agb+T^@iqO4&H7frj?xx z4>f@nZA5yuy#7<=_V^vVp(ut=wb$5~2V*19>kp4nCkdwDEHEpQ#23&vr*IMdNR@)$ z&}Ba_vkd5glQm=y8kWe z$N-rKpXhM!oooh-iE-V{V=k=Aug?B5_p3rGaUHn{{K)_(fr^MeDb`RoGD=T6n{_37l&h~~ej4IFL&Iy~iK*An7l$7Q(s*?QjIeYG5V6!9C@Cgnc z)Iz`DDrL>GodA?>2KHVn42(WXrU@uq&K|UUr_IDdvwOa-d-0vv<22ZlV@8#`E?722 z8<1C2jHMg6yt}lP`0B?Ny7u8=K7JsTAQbTKva-XLcpJe2*I!OcDq^4Lz6nXrxwI1t zUK#it<=*Es#;QJVhs5ogK03iz>tK5i4^R}{BX%B10{lhFwMFQm70qanmvUkncTq-zt317?FEHhs5<^U z_kD(q>NvGDZ!d50jmy24BtKFdau45v{fF%Wh;28x+pg3*Z)^ph#_R4g3IQQ5 zJ~|=uVzK*2=}xbqc$1sSnG(&LGg5p2m1;A=q$W2$=UI=167^KfGGp-RuM2<{O*Mav ziP_0PaurvHmkmg_(o2KR0L=43Ddvk8A@(&~z_fUys}oJ)5M4%ES!W$HytZJ*Y(Nf$ zV$3YdeA|PD${(Nx=ssmH=z;dGSF=DttMRXKB6d0LyQe)MS+B2ZwiQ0AEnYmnP^z#~ z&Z#Z8e6x>O_^>)- zZy}~LcxN5aM)6t`Vu~ojHT>i;#1T9xk3Dap+l{^>RuO+7x2OKWKZSAXC5tUaflF^I z5D7m8zmsp?5u03Wl=QK0_b@Pn3ytXn!olc zs66acO?aFQ4mM0~XKqB8>-?P3+JmUPy{VjRH9|)hH zzkbyYxPjCCOue55_8=%)Qq;+tZ4vW^y(M)>z_n2Tl_C}tN_37#%Qwq@%56uxV0W_h zL5+Gv{?lIc_fVHjaH1YG#QPT*A_MGr$h$4k@Nn8%JqO<3VcBn;+7PQLzE`A+0XbpT z{;G`s0IDO^_OK#j+&OZ&1cC;wO8eG9u24lStz{6^?I*eqEo%|W2N9}=A~!=mU~Ke< zySwIJ8eZ6?2A`gglTk1~4yT!A-m3!J`@Yb`MNo@;l|6dbH2cMs7em{ljG?+QLKH`! z9Rs#F?+F2~>tH1OYChYnSPl#_wNI#y%9Yd<3hM zD}MUoAAGBvuOX5Mag`VBFxR`>E&n)j04)55MQyiC%eM+MLQO+IG(VsY9a68e1#W#&)RfN0_3q=v$@iR zej}P+cjfJDgzVoC1bIei#h=oO&n=tvB9%*3{);-`h=78pLWuviD)^g5aM4?eA@CW8 z5Ja9Mcw3SuPDQtBtvx71b|Q721#N4+{O*H~`}(KPEGH=CZlY3c#l>K=gn5~K{iiPM zja_?7$`&?gh?{optI6oA;4?GpLV9qzHbWs@VE)y=9Bqkq7_YD#idXSPztgoRYLt$@ z0en-G;~YzG#Hk}4jDiPG-JW4DmY3v&{|4F|@I-O_7Z$7XquaH!nfGYV=Tu(3xZF9A zqvx7=a483ve#`j?2eC__7P0o0-kc8dV_qzB)OmmrmlE?}|LmplQC-u99Qs~`BhE2%8&&u#ld-u4LycjG-&f-8J;{rO^OF0oX zrRN;tJk1P@6HgoH3;z5p)*5@Ob$!TYN?Zi`D}EuNkXd`A{Ne}d?%Kref28QAB`#u* zw30X3bQu8Qlj;l9anmi$C7mXLOT=|JPJ@Ks--3z43}^jPAzMMmj%-(!1|l99BtwPh z7o;w3Xf}}g0`eiG=6M*q;(>JhS5R8;pe1(m^WZd)+&9Zjy}SqX>N%_*HZ8^qv_QyP zYef^i*aF=Lc>CHs*zs3&Chf5iCTD%0$->0^LQP8SH*TUc*mh87%(U*JEwnjkK6tjIPgtMEh63YExa$ zQNWA9Y{fOD=NSN}CwQOsbl6v3N%yqyy%7P6H|nH($)H7X{cT|EAnuMxGHVz-gr3F* ztDmJtoS$CL62+;g||-PH#vDe1(y^oo|;Wh4HgTFD_d zJIS^26K!%AV;nu4y2B>>945a;GXQYX?)CWcXno=PpzB7AnlLjf;?yluSiVjJUY2~) zE?f0(<9|JLmopvt!RTw$hNKXS8U(#GlI^SyS6gjzd9&m9Y8TG;8f%512az|h2))ok zU0PM%h^Cj623Wy+Zqt}Xq>Z?|v>MEWeWVc;=+B5r*wlUZ*9;kR5{$Q`vjzis)>L<3 zNfEL=P;h9+3rTxM{p2-62m^tjSq^dx>wfj?qq#Z9zc2joeb-V=j(VARGEV@}(MYTA zWVANw<_=T@hlEUb@ns;|`4`mt7F1wgfy*l6bn#kWvzK$_V@r$8%f%C|fb?>SOeFA@ z_e476q}2K(%Pw#!trA@FtN*cx7b@k`a`%dpOMTN&>WSv|6zqWqDgWRWxx4^0LF&n4 zbh#M3zRvnYugurE3z%o&(b%ipdXP^<5i*DsY*z7=$=tnrYld3EnS$2G_CZ@kOLy=1 zWWxL9@Q3-X89DFYvID!;=N=Vp-@3Vy^6s$$%ubtugrKiu;_EdlyzZ@RRJKW`sQ0e| zBayup-PQwLLr$HPkzff_&VCTg1+ZE%F(Fx=v$LT1Ind-m1;TkfV0&VaMuqyn-`aBO zpv?L#r2JyYQq}rya=%}VD|47PQh!sCP*FK8k1Y6gSSaa~@jb+z zb)pseU^2@~U+^AB{;P7J-0UQRJ4g7QuG9aggVKl&c$o zhnLJ0gq^%$&L5ZeM@8QPk>{$1^E*4z;40u=P(i7F&>S+qn@_U>sl;Kj&S!CBD@z6P zs5p!ivFGj-2w{5_W)FNdOTyR(4XYvfs(ciEQ4Xbs<_y4H1@FbZ6$Vdrdg zDV#`DmP3}EUj2)I27xQ}U#Lb0x)BwP1 zZrYxbR>=wtaeqq&!>kVUy<%w;#GM5q;I7;f_?CCf`1cx6dHZvY!pzY^u-I;G`)*j9!=az_$jFaBd0<7yEl2U(@{%X=(pCSfrGFgccm0h;N76tr5 z3kJ9pO@o_kIoppM+Su}0;ff$gcG9kK5WZFC)!B;$yc9DEs~#00!Ds=Z~R_zZjaF*`)voU5X$2n-SCANP1+8y)X~j7~gF!uQ8KO_t3kT>i!&h>wvV&*wGOVXxJF9y z@`Aq`ml{Dl9kKtVci_ig%8HYna4h?ds= za}*AlVo(d{lW8g^B~bw}v(n366_0DYuH%*I=A++^gYN;X=9<)lfc1iq0D+2<__>!m zH&f6&jbY@8xP`j5)}euu?>h40Z;l1ug#fBNE=hc`mQb7_83h5sa*bBBWjG9!M5Q5a z1BFG(py!st9-a>wnEdnW<} zX5A7}>G4D_?9b261U%P{|0h>B$UicogH@I|)?hR@;4cA^AX9Cd>U~gq6K4ch|eC+qIRYjMjhv z;a`BKr+$3V>cFOG*uxq~1TX?YftFeJi1MI&j-x(^HL(N82+1ckt8V+U@t2k>!@o;Z z{Gr1|x7Uks$P4L@)X6_uWU+$rW=YOhU|JIz{-tjAc_S#M>Rti0T^qsNxWg??76Bbh z+;P|rkHV@z^OM4^I9ORFe)-_x=`(bOjKhv<)wX2dAKW7kfeQKV+J7E;_UA9egQf+> z$EAr8Gv$f@0fJi`Mu?(;!EPsUhV;O1yl5aD>iH;Im$XYV$tA8m+z_#iXx(yl%e%96 z4dJXyb{i?6$owQYm*~?!E^3O{VQpm>Jj}1$dS=nsZRy2yx3AB(o<2othN!pXm`#jt zq`3we)H%^l?t#yjdMERL<_zj`4?)0{(m86p$X>}m1~l@CdyePl$NyR`>DM4aTbjiO zvEznBg|}?U1(V{o2-j#l`b&AqPJqpJ_O`WBfcUB9N{D>8#PU`M)2ukP2)|H(>yrBa z*I@p~Mn6llGPB<0+yFyDX;j*$NW8t(WQjsvMYeEZfg|be^mn3|XY#G3j#!glJ2|{i zo{Do3q-_3PtmzD!ffo3%?l*;;A~S2d0_hS!t%{A}yHarhMuD8-U9e_b zJtiCD++PviANAr}nfM&DCKBa3G~VivRm6pe$at4(eB&rOfF~PE#rrWoBBar&Vsz4YayVO$=_CGFK1XREb^KS z!$nakhe@+Wl^!VYkLc|$_|;y=8fxLLdiVLKq;AdL?=MpdkBSDMEI!lwWQe}muC{2j zPGLn{g&#lXnU5o%AAJ?|$4H=tedM!?9$aXn6&KVSD zMrN{@I+9N-5%+u01 z5%cMvpFR5Jz_H1TKEXW)fSS)IafxWP(=WKQJ6JGa7DMICyafzUcxBq5D<7Ra>!#h5QAKTtVSRCJ@Pf3@O6=}T!*6Blm;D?cY@cpjK+WP+S3NI^50eAeA1S6??^xfkHyDwE{^i5KVGX&XY+l#dVSMT# zJEIy_0cyT7M(swlwY}WCMYstCjtxw1ieA!H4X30#nVxs{wTZ~bUo|U#+w1nW$YN+; zyaHpq!2Q_j8cJu_Tm+ph&8zt!FSb@>dL~l}d`O|VpRYJsF7Lj4bYSaumzQT|AIJY; zYe`W*`%<3n4=de2#pD2bC$gZ3s89wt%acv9o!``iUPsuR<@{8sVg)^`BXNnQujS$b z9&SD>>!Lxs3BS_Enfu(zPL5^$*hD&Aw6D6+0>luFzYl3PQyMZh_NkK|&Z$GdDVUj? zvn9Th<&F_dpyZ=?s>87bEcq3yvFp; z^L?@4{%~M%C=9)=dg0SA$R91sk>;v=Id@|m{Wf~lXdR{L`>otPz~7)aEUkW@yZ(C5 z?>}IuPHPV!PMx843Bi|dZPl@L<{lfpHicX)XT6d>res}-PLa(10S37h;Tb(+TzC=6 zfb8)8eu`5U)LhK=O3zdqegBEJfH=@@KRfU13RpUT zDc=75JQ7tSS+1JGIm8h8{dG^KM>yaBH9UD?t=3$5qr$eHQSiwgimt+YaLKDM zMgxqgtK?dy?54ku{>Ggq-ODQce(NX$)TxP6#?^Mo3b?(=0t%2n7>H3CDVuLXI|g4T zYcy=yiVqb)JY&3|j3cZs&9M_sVOVF7Kk9%tc`Ce)Jm<@i{rTBUr18)|mbP0sC?wt< zF#O!TZhAD;*G0QPx%VRyoo^$ugL2crKvPe)C$sRZ?di+8#OTe@MupBA0t+1rr$_q6C>37VirOjz@uRj!3gFiw3R>sy+cZqw&cAq5;6dq4_%<%l_NFR0O+ zl{`W6TE0d2^Y-sI9&G-NOz4Ml(=%gED7!EmH9!bDcL7O58j*9J<- zIq%~0_*Q>+CAeSa8%3o2FCh0>uoRNdq*DeSGFoESZ_{BGup^Pq_U%GjiZn9gB+@?0 z_%eUxRgp-o94PzlBfotdc(L6&;6&*G#7}&EutuJ9*3m2w{3@9R3yHk>0nF#4<|OZBa@H$9mPStrxJHw?*TVc zAv4ClO|Sa7Wx#!r+&S%V=|a!SUbdu$A}fOqyQx{?JDosV$gP5U-@z2|sN{y;x};H~ z!W!CwVw<^dS~YkbyXv?SjsE-~qAcmp&r+er%{OB3g^>&?P&y;%;79szp=_!UDU<{P zh#Ch6uYK0A*VQI<%f$X2qrEeLIiE^Ve!XFH<&7ngf3`ur+xXEttq%3F+t@zeKs@Xw zupztq5`QZsyqeo%?^RO*l`$^{*RV{#8`T|7f=Pw4Y0q7!ny+tkx6d<`tyyX9<2JsBgm z3xz_P_l!svReAFcd{Otkn1uZcmSC+EOC9ddJ}$qIoK!&k}tEsNF zHCM_dl&xjg;Kp#F+bBQ+KqXWUWfp|Kt7=7$RYRS|$NR0(tW&Q=N=LG`d(Hgi9N7$f z^)L)?4_lH6rO0@pmkG~CTpEf4I+r*XD;VZv)s7&S^3P2z*C~D$eeMg7Cq6V|M0r*Y z<|bHC(RW=q&WL&KnZs7PR9C1@S6#E-1K2oHa{aJ4HDeNQrg#fl@+xfIZY`whL8dnc zqZU8zJ&R#UZeAp0)JOEXWr)<1Hzs7Gu`?Ib=aQlh8xn~6u7yo0H)vzbsSfE52!I*12bn66MaGKQ=}WDe zLt;5~==RpF-6yjH1eq@o>2!qZHzG~v?9TUq3NtQB%0ROsH8_81fS1v3u#Rhcv-FRY z37I|mrmul9U8XeJ-!Ar>4y(FWE}#PP+&RRkHe5t(wXI>vH||o<`!XvNi%8Rfw!Kxq zZ%X@YWd-sQxgM7_(2$Gggsniy@~;hs>&tZ!Jv#W3o6cfr8Q9q}z(qC!5JmS+_uVeK z0^r7i8p@ID2t}_3B(vS102*?zO!!h<+crO@fj7z%V9B(_QCem+GCfHgQ8+8J`z#2yI?TJ}v%DzEGDXr^1QOWAy0zrKrmuf8@C?DT3Wr2&wp} z_6usHX}t3l(=(oR+_v9vS3w`Mu+uC4=Q$fKdLf0AV^6wP)6I34t=R+o*<$q2;BKjX z)S6ixj`4&FQrMCR#adjxXr4AbY`6*3vThJCO{1nWSMdNGVt9?O)`|^huebsMNICRl z$%=8SFMG>76*RMbi*b)gUOaA7|2@6y9wFrUat|Dxe4cLg2*qERZXglcag;BhjkcVW zM>S33?tkCq#P30(i>N^vN#4DC7nuZhNs(!r^hYcM4bjyn!2kx)V}9ZOQU7@y_0-T* z=Y-Ib@}7v-Idu^4XxNOKJ%dR0?Ex0Io&}okVg#lqvj&5?oWDn2?1)zd|ErbGqKSz) z@YkU!1t#?#c*jtTc}x^nrM5V z(i{A(0iJICvW21Y(oBcFJe6mM|Bs~aj%)f{+wVE=+xFOMJyih}k)tw{S((bpTLB>; zM)nAh$_^n=W{iyQ=d>yU0woX-5K{I`5J=dNRzVCHkQE?6QjsAeq$H9UGWvVc-#`7s zie!AB`?>DxzV7QvUb4by1fQWDiAihfmJnHvO^%_FqEtTYwLs(qD#gAp(T_7kv@8;t zy23!aqNxHceV@ecBAsPZNiy%1%%Gg$u~_ZNV=SV)`5t)WH@ zv{|S(DddS_VBFB*BKX@`PTET6McuX5hBDtkAeV}9sf#|7U$F_dW=MPOKxL`&$^J&Y z!>AU!6lA#3twQ6ySyJly#IQl%LWlUq-LiR}528~H;)Vik%03@^OgXU~)dkH2e86%< zXF~9lG=Qci_q@E}#>si*sm^W5|7&4)Span&W43@RxVxuhtw28kaoitrN(C& zQdCwv{?z^jTxnF;El8v0wX(NFyi=~`vpRDMXoXK9u-TBh=5d!U zMG{;%Z+pL>cm76U?53s0u7x64GwySrq&pk zURqj*hu+x05!q;(|>7KS$BMsx0M=yt?RZWEANi z!qu7vR8S@Ol`W(AVGQ~yp@n-Z*UTBng9Tm*+BG*J3Y-#B$LYkKvqKQYYg~WUq0p=* zy<0|&GorYy{|jQtwlojN#uaZ2kC&Ay``n7S&()(7;Ec}RN7iQ^KJm4?MFINBJ+Xqf z6D(p+FYvp&CAVueOr8e}iVkziTTO)7!_E|pkhN#5c zEQ3n~l=)wuDMN*9uXNv!(Z`+8dJZ;uP$k|pV!t3`s$u-t6nGn?-vfh)8-qIJ&ha;= zwqAJn_S|;QfnRDXbjza1fVAMv$Ac*XXJ#$?v>1?B{n_2ETFxK0!h56&)Shfi{Z8>! zLGnFTJJDY^Ebgm+(-tQzDxkGt+_*7V1-_lf^3IGb2W$AuAWT-o?8TJcN7rnaiXeEx zls-e`Tweg&X=8Y+>2HVqL2=JJQl=E$r*>4f7<6YK4w*&i?eDX%*U4FvpEOH-KCay0 zxOrSD-iT7RK8_t^Ci(mCgI{P}b$Z~RfrA0R_B!ump-JxA0R(o*;PLUBvI2YPe@3OC z-4D3ag4ObjFH(UM&fSsl*@lx%m1NB-S@ja!$JmE(VYEUR9ZMzF77|3avN*??e3cG-t_ew zlAvbQG9CFOQJOD_^bG3yFcdX}Qz<=Lo9NnMtmoA1vj4qDK)GgkRyBv%i~e#i+(Ce& z%RaWA_LfZ+PH=;c5#Ib(qu_(oVvCL@3n({Ai}NZ?WZ8o5)Z~GNl6H(nW54*8nH0as z{HDr?0i_&7yu;2L?WZ$I1C7Z9tBkh9{3R1fJCxD%ecg7BD&Lq19u49QGmdq6NIF%w zePZjoaYf;=2|zGzAya)Z!($+aG7=Wceh|q!ANRVF*kDmx@+G4u`xv-lp=qm@_@<@F zmW)j?D3F^KX!P49h8kN(I6yZQ6Fmkg;irYxmsByvR7c}4fde>rMt`00{k<<;W+Jb4 zS5v??d=5tekxBbm9&~fQsp9Pl0-S1`tcVC7%-sD!ap?=q&;1tcPrbHbXaeKu+qTyo ztYyCofMU+y2su^+T`_lkL#&q^&OF&zDc3Fj_LF;twLuV1q1b-`0sRV)Nx5 zS}2~Rh)Zj=y!+$^O5Oy25jcM{?~Wt4bL2nZ?RO49+uOaFvD%rR6cC_{W=*b^O~drS zgtn9wjpRb(SJHZI=<+W}7oKetkH$w&9_wOM3t`E0=uN)#z(?H`esCtDN2xifuf)36 z1qMP9Bk1@wFlls|&AV1R7bc&7P0}q16g0g~xk0jW?R66`jR>H+0MNO>Od5H}d$rDK z9{{cgq2YC9uTCqV@4Q`G{KADnLsFL4wqx0VSwX-|`Y=6AP?ZB6_QFKbo#_qoNB)q< z`){pT*FfS5X9u#L1egNKpi<0l&c4s(*9pTwp~7ja#`-$c6FQeC3F`N5i%S#%0M64K zx*29jRg@HTyk3~Fo@^9H41Sn6;Z_xB#));Z{IQ!Dc4S|)BnAXo&Y!Q1h=*-ZPhUXOJ`??iBvl{V$qlb`I?6Fu$2 z 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

_Hy&I=tiyR>i#_+R+|vVdV5s{0W# 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ΜvRTa^-L*r!|6;E8zD*1Vb zJozl-v_;MH;Wua2y#dVvixp3T3HDu{^G2x&QjZ|R8Ynkl!V<

&4ywIKY;q^LvGb z)weO$>iOn?3F$Y=dxEt3{IZ0zFRy*Gf^A07^3HFDjJc5e5nZwghsK*|$B280h7Wp& zd~sVm@l=!b4-5;sRb6|!&S;3(!Lk(M>rWqO`jMKGCJMcDKk+{#jv|>gLRw)n4<+gU zEWoi`lSS_iV4*(#L33|51f9hLD_%XQ@Ldv>(xCgQ&Ml~y=sJ>lOw`dngr&h;IB;1m zW|aR_soR05zy;wf#eU_6b=mhNr~Kz9Cs72fUu(t6HbP!QdG%W!PdYlQBko|-v#l=& z+RSuQ@^O~Ap86M?F)40Qah~K0b!cG{aObsfYp+D91J~g>Td#REc&2gswCvq`ALy;h{BV@?9*Rn2n`_>i)hTLPxLl_kw`c$(xZsikfqgNL16o7 zm9hLd7#oQ9>8=*>wjjpO5x27SDJQT|Q(VX+7vox=#KryCY_V8ENQC}@IYv~}wdF-Z2j+e8!f6AT9!ioHU{6YM3@U}TsNv<|^tkg3 zDuu(kiV;6izWX+WrGX!|B~mkp7HTHOjXRslf$!a|Sb>aq(#R6wF${ z+?n6E-;KaY75?(dI@2A!DbCP6l8?nPX7vr`%-%2`Ek}eIRZv3?!9YqkrWHbr!TSVk zZBJ{!Q<{bd<@lZxonc$jGrG`F5)&gu@!=@cE-UnT{vZfJcR_w>0r&&ZQ658g^ zFe7ha=&3oFm1smkdn*DxY)v4Iaayh~fp2~gYgdjSxO*ryNb97C`SH%dAvR~8sEmz0 z7JW>9_|$W?r)pznP$v=P?1U3K8s2xNQTIy#sUXI}{Zs#uu~F3Pw^9Ssr)GQnSzkwa zOorsMIc1;*?9&+r?7pIxB1bapY}90X>5 zc8VM#AJGI0#1EmR&4Qs7%_Slh_$lV(P+1IMw2LIl+obtPspPkvM1s|j_nH`1BNeWD zG6>Y&ETNYD%_&!E4wQc0p`K+5=n>0 zw#j^Yd!|Qo5nQ^c-w^~CaOb3N)B+aD zL_}|fQ;2j{12Y^Kf#oRLK9{6Rxm%E)IjS2@QfTBPUf^_i%5sE31dCgALDvpj@~~)A zP83ytU0yae7QS9W3dHRTCQ?Jk$GYHLB(5xG5lD0G{uEQx14DXccqp;Eafptn3PEGm z8@$fk$}B@=cvSu6N-Akuut(Bm4)DFAUOj#6;+?Ssk?9E0GVz? zK35b{(S{tvU310m;Ue1mA@^|iO`|BVwIu15HHNTbP9~7(@XJ9!4|aRlwb1-U{TA`_tb0)rxWUV?1g@w?U*yqdD0lIX<7|nsV(2+Ubr@| zM8*9$qpDg-u*~en#L*IQ#0aaCY>MG&sefYp#JsmGuo9cFtBEoBJj==xN2g8QJ4lJX_wMhXn4OY9YGsh#Pg^;x%LhI*bjaLvP3|3NfDQ z?cZB&?nY4~?q?KN^}K%s1W}8dsWrw&%`jr-B(Y1JTTHyqpk7=cFVX99c_?6=9U6RGdqlVSU#D3+VGcbkQvySAvZGd0oRsQp!NWjq3U*g9->8B;A zXqeYZs>@-RI#irG?|@FyLf+yyMi>f_qqlNYzxqCC8G|xA9a|rP9enB@ zLCFk%fJskHHe4Dg@JQr!Y4k*BzTyyeLbls|%7@Futl`~6eau8(4Cpcsy0ym4jP^9H z3o5kK78FyWVY1}rI(Zl8EnJihGM@wlGNbcJsEgQY_YH4BYd}PwL1j`uy`EN~6*XN@ z#7uu6-C}pUIS+$R*hDAq6=$(l6hdWvrg$nw;^%a#QF+~Z{#W(UNK+~&*vJLOT&u!8 zsYXo0{5$!l7ILdHwkJ?orR9qrav;)2cWX+c#S-kw1dqx=GmXH^N*^fI-aO5ALb^w2 zcNh_vlA>%)22d6peHWZZc_h^4#)9PfUhe0(p5dAvS28O_h@JM+g=R&SjB7g+_Nr2^ z%Q3E@=bAq$UckeApF{PO9H#wpQu&iR z49W`$(-NVLM>j~0!LH2VEWxtNWMH>Yep)emsS=xqM3|Kl_da9@>t$&WmVSJMLezty zK4k-nM$y=8#Pgi634;dsucbB~e5_7Xm*>%%eVPco9C*@~C^YHZ4XCR`K6o#-u^jq} zFy=U){3Utu>$4nD{j(bv0%B3+*iRiCb}vdaZS=JIy;R9bWn0(|UOzr!^`T-F^~l64 zS<%PH4_$y3w=mjaGn_Eln=psDk!l>VF#eE*DTiuCiX8;hF(G=tcN=RE&v$g%bS*RP zlCg6GqEC{Qch9d()k|JTpldEwMv11grs&?&k{gcTIQ3*akNaSA{?u3hi>|F}9@Cj_ zu~NI>t3=DpofF98nk8Ab*F54!yP9&q>tX%N1^}*u>`1NhJwinpI=D~(e5id9Qk#Au|QGJZJd42N$wAK#2kTz zDB1@9AQ$9Xsq;3bqBQw&nHFwUB7Fyxo^$#YcP+ueW%lLG@VGqzpZ<_%E+{YyA~pxd zm1ZWh$79xk3VhV5VIiNS4HnNW@D6Fk`UduzHkd;+yB4!w-q3_0rx(;Hh1`$@bz(x( zJyhMLU!Z3{%*7_dQ#hirBEeB^NbdwD+^5LUiCrhhn2CZIubA5HsHnTBp2jx0Bz#I) z2uV(T(BN(H4&7(DZXrCP9a|$rooTV~E%u=DPDogn!h93L2QaJeO108A>k8jjY*D6} zD@`OH+cYtm_^;Fu%jrqECcQ&_TT_Ez#i;0NAy?`;920ob^gh^nbr#YU}vp+yzs4~NU~J)4$&iQN#xb}3#oO{R#qZdHKE5jG1Alz78+B+D5Ln4 z6BbMGHTlu!y-*<&qFl&TJ52o%Y%R~Z5FnKG?1<#uc9^flW-q!KVthXlf3( zj)FyZDUBYv$~Ab{vQ6-cr(ZR2DZUZvn9IPdV}F`MtccOP7^bs%q153I($pp5oYpyHXK@1X59Id{a`a7NiLe?=t_;T zA}gucFl&d2slGc6ThDk{cnLQ{n(HY#qh-YLa+HqxyToA2kc35SPv%EvMlOD^D1@i# z)W_5W#3AZ2Fp4JrAkrZQP^*kC7UytFQ&s{$MZ@4y7)Fo}^1iNA`{xbtUgOOrkvaJGf{cyApB_+@;zTvcOQ= zN;g%@EZ}_|`sc$RHrR$(;gndxv?k7h&2O67bz&>AIF}6;R>b20o5dg&pzTio~Q?D1Um5!0@xUaA>{kK8k@r_VI;ou(EiLQS{_8q zdG2S#TMv% zjnvJfP{j;xeL#pbDR><`pnq`9#R02bf4xIn*J=T z{P;WRJtD>M&b7;_7Pgg5VdMAJ!B5oZjGUFTu7-@Tdnd z@R6%t2f1-t@(6+BP?o-bam`h~PobXShgv7%0C(Dy1&Ar_j;ZRyMQ$u4l&}ULd*4H8nRhP&S4|P=JAutQa3r2`zoj3K~;(#$am{%ttpcUB*Gzp%lq#voxjv zdah=*lb%_EfNE*eS1FYvyV9DN8UzLP8>mJhO46|kpAAp7YXQ&h1-t1I91t`|cil`mKl#%wF{Wq9Oukd6;C!!5#`RmXg(XIIZaB`y1IlRAONz&hnu(fbT^&dopWG@+O8c+igR} zv}S`OvnCRx{r7o_$_t5I?QapncfG4+s@nE5pgMYdG+@o73f%9&!}$hI8Nf;gFayj1 zpU4PjZhy9YXA1%CG9_#!VPzyc`C&@SwS@5}I=;$O*y`YOsH{5|N;cvc0=)fsGR$d@ zs6m) z;@G3y%*Z4kZH+EJFO`G`(M~aumV&wJV(C6rAw0<~Fe7inziLdbS4q(HqvJ3nJmp2q z|B-8K;8@+8RH&jQ8dji7j{qo&ekio!8DoEUBzORxX>z=gxNu9b*W)?<>1-D>>~$-P ztuCSp2TBU=S?TSqN%FJ87 ztg!F%CkBWm|v;~qPT_N{H z5K|mtE2Sw4w6-?-LCoa-^3Ybu9X>&mtoNTIk_z#6y8wcNm*+> zLH& zePXKr3YU6_Nb*ZHgr>C<6xXKn1WY)aqVqP24c~k~%#>UYA9I*3VrR6$p3*)U?V$OR z&y#yIJv*~)dpgM`HeP`|2D@urWMSdgEU4QQKBMT0ea_~Q(*3z70G}I{>=eEsl$}N= zm5wjJ3o_6-jByqP9PMXmqF4hQp3M;i7vI0z$x>)Hrz?UXWH0mu8G&LC*WCh`MRXLG zZ6-fF6Zs1ZV6dO~^A?)2nFZN$h@0;r_Hzc6WNH>a+2p148(( zP9=-ST)d%+C+4KCfl0F(^>!@ z=|N&PieFNm=sqNmEHxwr8!ScIH}bN)P|&&X*gv!u%K*@=U^QT$*VUA0G$$}>#FM`Hfe9Q4~F5J-` zLWrfx*f1wxF*rn^AIb%>k&kBBqp{IZ+39ghR6oAXbJtSCaCS8T#%W@< z-ulz;pn+g=@R$7XlfPTHG)txe#(SXFeChLH4+FjZizQc@26~fMB0FE5TYtEP_8EVFq1*kzi7fRuYMv1{G_t?rE?4VuE*f~r^xw>?)cAD|%2xUL~ zEm`{mEs7*bC2%perItuBHO!{CE}O$J&MI?6cv_;vkx~^nzcY~;B%RSLil|RDR~$76 zT`xj8DhATAywZ^8BbiiRurW<#FsKNs{K&-nFT+8+j3KNA+)ja}=}ruoCkCVGrfCWC zu`4klFv z`#p8vRL?UTp0NvOUNcm6Ks3Tm9bM$#jkc`^@EmcH4hgZ$@%4)JQWOg@#aFl$wJS5W zg{tHCOJM5VGnVmto3X$op-?v zj1d#?F5w!x!meO`1@*`{-G<8_SFC@9#veMda|6M=zJ-5b=I7*rMQu^y|49 z2@qwXw9H>a!<~v`Ky#l0Q9rk%%Nenf2WcqG;W~3 zRf+HJJSyxpGBR!WfG~&M8MbG@TEL?+1`9>i6`^cwJV-uLGzI0?!$&{s(gU=f#CE6G zoMcwv!hGQtc+2kBuTOB#svH`pUPW;KIp>%8$d&WTJLHXIt4j3`ga=bKtbxncKDf>y zk4y3CE4P~&(14is>9?2Zg?Sf8Km2F5xcwMxt=GyBd zVR}qg*^|@Rb`1zFDgyGHN-uT;@8AM#9nA+7_4Y<~GfQ|du)X>&hY|rzc^j*_+eXTM zo`wP|+yKU~FlMJxA=02wq`qP5y_?=!Oc7ru+;gPWEAt}$vNb&e$kz^Lx?RJmOA#&5 z0c;4O4^z9D30!p#HtQcD00)>{7ZVq>Roa9@qS8j>5G&}i<$z}LExz4BxNdQBF#bpm zq(dG%Q0TPPzJ8lY0Id6Qok-Np8L#~;eRTMiIx7hCR<++OTqM6k-k*`xs05`C!MeRZR~Q}K7F#PoYc@}lzT&Q1tA z`A*v0)0(tc_Z^1d{CnL}l^4lPph=aKxJZ1HVby`)E&4+iRCp1a@fk4AH%q z+=_HQ>DHmBptEOL#$~VfpWqf;k~BCUe4?pq<4iNyzJ>_KCkKDJtYNaH9O&O3Af{US z(CCSTKh#Hq3xd!7BXhuuUx?NRQ@H(*544V=o0c9Qo+0PGbNq>ACCjBSR2}1&svewF ztSkcA>nQ&HkX{s=yzf5n6r;U`LU^F1q@#mV=l{ZlQ{<=m!e4R%w6<&kHyTZx0(I1c zdNzu*pk9BgzPSgg$aMTYMpwiBP<%edx&3R4f&}kn#SOwJi-Io?OveV;fh{pc$cIS>ftbul3mbOq?D#Z&TbQ=_t2Z)(hp~GoiZ$^TKmOPU+>o zBc*AYIkwm{#cNtw=QCR~o#eG;Qa@)}Yj5@b&PfH{(iU|?z>|yYs{zw!+9bEa3mE@>3NPYv)4P) zbWrJ4Lek}{=g!ParIOxvylsE56qyYqxWrPkb}H}8Q`DpU@^-ekgsEBk#U{Y*-3u0= zdxuSjvV({*WmL{6t@zQUM>WLO{PWvecseJKK^D?Is+N2k&8qG804dn*OSj}Y*ph1h z{`Rm}wcpLHj=LRfcx*#iuk7(@0$eObX*n-(QfD`kC5*XSI962OmyX=(@8_CGUAMts z9$#I04Go zLs4UAjiY`JLk5}6zUnYN8F3x4CdZMkNPSPic3D~|#GL>;J9d1s;lBf!t?R&T}eNg{6iK?LNoDDANS!cc|((osiiN(%vXqVrMI18e#rShDoti9$c;o@L&CE~>z#RMp`pu%Afs#N7Yfsto)(^p zhei1j^`6a)@oXaR=P6|1rJpbfAZ@b2EoJWd$^=t#kF@!XIidWB4&GP^3nYV}1FMGm z7h#J8UfQaN3mG+#($m`@uQJ7GU$t`$eX6Z(RV=~ zfM)34?gldp_eJi%qp1EkI?-Y_0#X$5EESPyL}dKNsS06_^7WVt`QU1+XHK4T6GcmG&vY|05ejm|(o5 z(qy+Q|HAazl%j`e>bW9Y<}VHCGV0xaI{Zh5;@B22?Jave2+Mj5jF-My$y7~WrNVFe zGbaHCB`8RW|HKGCG%$obg6fWE9gfGdj`$~_qaRK)RE*8g2zz>}UdT>3^w12{$wVvm&2sXkT;sTYjmS$5X)jS?(dvgo4rwAY>4 z=F^0UEx4b2zZHs;uCez2(K1$ko1d^wuIlfb`X5<0^XM z|K5uovc;3Yo-UN#GNlCXbR|4-Ccub?Z&UzdQ%hjd8dKAgc=C8|k!HNK6drE@P_krb z+nGeS-b~4CsvTA9BL&m>Rs+17stKOQ4_wDAYF@9&6yhgrGuErF^1KEXRK6sMDIcU@${iShJku)!0l(bODdZ=qjR+|w4*q5E|O_r|t+2lD(e$cYmQ>u5d(J#M6`QuuvKgkQ@MM|&bh|YMGZOp8&hudAA9bCLyytEVC^0hl@Mce z^hq|=YgE}B|8&?MM-`K==<%WfOSn8&hri?Sf*}kfExR6@IOEuwYBzVN>y}_#z^vx% zRFvIvs+FyHEA(X;K!?j8`Oid%)|Y2GriOZe@qXnU1egT(FtTPdyR7S9bBV$9^47Vl zevB^MuKMV4dr*G)knuILkin;BtOmuc;Y{s*IpV7<^);0n1Imurmjfu`c?wYyFITow zz*si(l5?F8H?le1ANPe2Qz06iucO8mQv0HdaaXQOU0_(?E#85uEJbkF7m&`W9)+8B zE9)-8S?q6h^Z2um`rW&yajjgD25AqedmS~7{@UV%w9Ur}54#Y*bgPjTp*^dVm3JzR zA(KT3WyIB z*r;`wnm?SfOt*Vem@DNO2kngP^CEy&R{jE_pKkTeJ2;o8#d72%Cxo@4%6EOB5(L)a zSGI-huvN81S-!Q~2&5zMb<8Tt-manIrQ%fSSLb)4e# zYOHUhgto?Kto%Ds#J7D?SKgx$9aEG2@q-WzeN9!=a;m++kkyMe4MLU9$=+aqy2g@{ z#C1M?DYLsBelpsAv-(QOj+11)GiuiG_0W%N*<)yU8(N!G3MY-7iaeBCyyLxx3^M%} z+xGZLW11(V`xluNeIluaTT6y%|Eb-bug#q9avD0+`OfVOWy=e@!+aja%g@!`x>_m@ z5RSC1-?LV0(uQPK88Gj+WNARkRR{?D*YqbP5QT?A(6v7jeM9(*=q1#LtBO7dG#9|6x8ji6iJc*iRv zop2Z27I6FQ_WdAoo2^q_8!f-(BTQA%K)PS%nLrm6O9OWYZ-+Dq1qW&0B1ttCQeAa1 zyZLmoHKKbrC}7WoZ~YhzHju>dvRxL4*A)RNdJ6s{BYjg^lVc%Xx}=RDUU!N()#hla zRXNdy^i4pFP4#~za`i7Dnx3Tzb;?3ZDbn^Hq`Atm0KlkM{A^ta77*P+1kc;eRNPSn z2}yMk4g6a8rt;*{lY0zpCq4wbDG@3qYWgH*q)3!bn@XiM9S5piTIRt;+Alh0vch(q z9SQR6G-_F!tXKMAZDpnVjAhXTy}%DywM_q!advUwK=!S1548~a>Ri7BPw)(&(Y`Ta zIgaDUV3b@&V87B@2$YHrtc;$tZZK|_PIS@zmJhKm3y}bzxA5m zLI@s6Xx{O?hJR95`eE8?^tp_CN-0R?D!AdVtd8*aT#?F3+m4D<7QW~6+d*%e;)FSt zu$|$O8QbPA)tt)+3E95*_mAEu|BgGr-%nKPFX2`Cnu&j^cwnI{B&D)Uxd80h?)=G= zqtb6ZYV;v`7Lm{`to|k~F5C1FJOtywejV5FX$wiCqQ2e2 zs-2R-_AVFLhT(+mO`2qM&!8scbY?+V4zguX%8 z2k%6Lyu3hoJS~9f6?+Kunzf#s>bv<^>ea^MWO_Zx{5q*GT!PvNzocQp0dNQP4@8R& ziVbo{0)ov>!oj($^kH|uk%2nm@yx3*Qt&8uqA0nYIBYUrx91V|$ogEiQf_(bqqxcX zrgWlkmgR^Q=Fsb#gsDv^N=)nLlSBh2tomCcqBTNoTkUheY;^whf`zf}KXZ@!u&}qL zs80`2hc1q{!-m<>P@ZiT*%{u?Z*{Bk>d z)Wnvyw!0PjC(3&Ll-}j<7k;;KPXv1{{x8lj?~kq@PHJDK#ov{-gnpdHt3eZmCWgU6L`e@V_t0crOgTC}6MpE>WBHsQZ zE8*_rs#Sd|eV+N^Zurfx=Vl`)0HWzBQ@OO;=?^@|=6LJUs`klM3A2dqmo~I@i&N_! zhmD1r=`Yvlq-yycv-VWV1 zpz3{$3JvCcujW~513>#%KG;g1^+3|U+8*X=DxeqY-xeULo=rL3#R$RrWF)KccJTI6 zGf9oCsu|pL1$*@*O0;D}nae}WjNT>S$J$>a$eTyI*C-L<0JthV)g|A^78_iMi}9q| z9OLF^iu0uOqUyOHR0|YYN9crw@qp&5-qz50mv52ssii&3{BQ&s>t9O#=+#fO9)M4< z7mpiR>Rq#x?bimS+lOIJUluET!Q=D>~ zU6jk*9>aU7cL@k8m@3Ve{nYmGQI|lyvmWAQL{=<_ghw6N9H6<#x4=7im9d3-kIvE6 z*lRhY%+0WY(Vp$JSR7*u@|cC+b>gQE;f+V_c=~j+&~0tBmoJ=AzfVlIovV%?{uYCz(;W3(+xS``fB@(OQ>v_D?=OaYPbrg;7Jl9g z?#8#%0($MXhlTc&X>eK5bd;8vz#-E^ykK8EN-kV$yodHxB>|+g$g!3hvlpyS0Gd)+ zzS?nH%{0kW`fi_05qf#v2_?-TneKWmNXx`!I_Q0T0eQ@jx!1V7iXbaE=G^`{K!48Q z^%qe@|8}>mE@lp+#P_J$sGUY^K6>^)vM9I>$*UgBaUXYGAcp#p{r$z8r#hY`$p4+$6hj1hueUwJTNyEgynY|*R2bdw(l6}@9!C=-f3Dhw* zO<~~6d%vsu(t~d*9FlNeNDNm=>bN{^z~4l z6dHSnoW-zhjn50o)N2TUiin=VFhbEH+RXTIN=x;;5}j?$_==W`l&C%DF&OZTp-Kwf>BrHes0nJ;fQ=`}5*`D=QvL#*P#<-lhLI(1Gxoli^~ zu0qMxfb0oQBC1F*Ri>+dGYq1|%qD@a=g_9Itz0E};P2 z(B&{`g*Pv425oU*de&F2<{r*~){JkuHuPDl3_{%Wx2y*Aw;*5lAiGbpn#pgxY3X7Uie`Hm`rt2|8X4GPTDXB@6x=D;jA%=o0Zh_{ zZ|n5yET=tX_G?DeW#>?}i%2}g$asfFQ~tK>C9s!UH+fY%t-S+E*-MKGC%ARdKa>gm z)H&$p3zqWZz=Wp_ARrGuaH7*StCM7IuAJ{Mim@WEk4^)|Bp)B2Z`lV-6^%cAYp32@^UQjH8Gf{%n--MT#?YlHwlfj0f?K<(}B%tfC&`M^VUd6;wq({IZHjBb6;77Bd+~8kPJ`qbJ$x z{9ogm?0AO8H%9{aQT&Hg&aTFQEE*AQgIJq!(5bGc@=Y_=$Q|MV67x~8l2zk#k@BHm zr_&4q#nsS`U>Qt{i$S31l9=!z&sUHn`TR)<)-Lp99N#ucP~6pzWCu5q4e|~UE8Dfo zPpWm2+E(55EffR*I>?vgY#c5MWd3uDyGFf*jieZg1+IX5&LIjsK`37tm@(_d7UVe) z7w)N1W0v>Jr|%~43hq=u&mC`f=jCP1q3-z9KrKQZYcw+p%i|(5Oz1Ud-M1TGm}4LlEsal72WS=NR?_MaoLy#nwmrP z5D8`b7UR&@#)qA*0+CQJ{g=HdqE=$3_b)$B2)p+x4xpO_Imou0s>5_Cx{yjfd$=29 zBD)O_WvUj96k`cI#qI~%136U{snLJs_1MJTGMZ$^XrU)W5(3FLt+zYUZvxW>&iLR*kTllu zifBx!kfzZ`jwxHYus8nF*IYNUi?pe4ZkB)cRhEp?{(iKpc^Ao0KeMcx<9tJ>*)&=4 zv)Dz9=Yq?HYUlt7fAM&~o&cc~5}DL|31Z0bT6?>Nid?&bk?FF5mXs{Ih`;smfdeH{ z56Y4nk&tYB2jcl#k5uZ@TLA=}lE$nqS?iI@>h{|->I~w>vmHnJt7D4Gu*XXh@5nLw z{6Pb;qp2JU{yF)SU1>Zn?-{rjXywH+0h9JE;G75Z*N_y1$zvl~L{hdD{?39{;3s%` z&;5BIxN_CITxYJgfZpI0w!}DucjSXMcWUQeI8)os|9!Wju`Jfk@__@)%cUpvbs{wN zIynyCKJp5sc1IoLTixb={89)P;=K9bO2K^~P$D!p8cocUFatXGzTV`z$=nJ#URp`9 z5pK%=_U#}XKECv~u@b}g$HS>fy_5yLh0w1_%hR6D^NmN^%dDRI_l1Ap-RbYmDjs?43w{p?I_-wL)4#4I%&3%VPkah|kMy7;(f_nT@K)Hs3o-gg60ZiRr zF-B_famEateH3=}JD$~PaLyC=Em`F>z*)hTs|C@)zr6!?&vgp_u|^>~!V8DB@pvJaW$B?LNwi4wBe-`{;DL@6!{%IO6Scme)Izf7kb{ z4jW0Wp)1Ez<2F8`Z^w(@t-U4+6ubz16`?dz$mjjn0P=vR`>9q5PUe^-J0s;eMS4a3 zdt=Fl0`I9Q9YDB`xqMw!QkD0+Y5p*G^Yl5=N1wS$Kf-d)SQyzYF==o^)=sruyz`!L ze}7V<1B)jRleqaZKFl(O@~W)ge-#Loo?1f;!=Pj1*1-!WQNhsGRsjfgiIlbHrSk6* z{b8e_z^)swwv2~$de|a=Y%nqm0Di`Z`t+o!&$1-gVcV<(%N#$5cPT?4)G9>n1aJz5jYP#>I}|WK<=H6Ht5h;`Y(U zUZ$j<0M(BxQpztqeg#D$kv*Uwi25^buc6T-5y;e z$}sx*TM5Ulnu5Q2(zmf&A9~$BV_N7N7w~sbH@=&a+ zBPGT=^R6K~0nl=#W9A+fdMrfY*1`LCFpyiL=tJpwBO1U+`{MZ$qsqV184`~(cIq)X zwcS{-+#00rrx}FsSqTfaSCBuq-~0KkJ#RPDTid@ix#R5W_8jZd-ijxbRu(^N9M-&d&-5vC zqFxrCh66?-Y2Ex4ofF%8#thd|Z|pawayggz``W>#gGs_OM{WUpp3O{e5V9X6b*+~_ zc&z6-ea6~w@wBe)mK%eH1Q*QDcW5oMLujt~u?)q0*;y+u$UW+xZ3>C?@U7p4q+@q- zk^~;$4)J9qbtG@MN>4$&CQNg@>Z@sz4QXO5r5S!nK!7g#Dn=Qn2&ACU;y5&c2s9S& zYc$h#FL%Bc&E&6QT=GFwTI;-?g_JY_Q$1ysNbn|Th6*`PGD(+ffzuqTwpft#uyFas zt&L=<(2Yd^b}Q?JYZXJ1&?dhR;84ZgiGEil%jZ--C1&*i4v{8TY8TdsyL2~gdwzBO zH8o9$wRG2ZjmbvPT$)zsRJU2%G27@dP_S3eQ{`GNlBSdQHDWwCcxUTkP`-^^Di%*0 zrC2}E*>XTq_{;TLlN<_;B|r5-JXPDaFrdy;t!0xODs;Nt2Im9jO+uE-bmP_($LNCK zM^qi;45> z5^dYKSjw`)5r63~aJb9$L^aLgsO(pE;UwEM#da@AWHNsRr!fEGS3ftXse4&9-y5_5 z@UeF86Dk;&0^gYW)J5iq_1!^_G)u|cs4|}=d0d6IvfOn$q=VWh72?x_hBfAqg63m* zJw%r|zi3L+vuaW^nK1F&qx!${vWNT{y2v$9*KNZFFhE^~pW~9GvDiv26E7hzOibh| zQJVGWVY_u$qs<;le%gJ^7nk@|nyP{bE~5UtLm^Nnw0qoVd;P6ui&Uc8q$rN4+@p~l zLcZ(S14!D3$U9a2!pr#oveseprB=FvBN&UQ!yV>H0C;vBsbgAC)FADt`fw#n2N-b2 zeWE8Uw_j#vY-gn>J!a4jz{qATXnL9pur^PpA{!PpPr+(?U7!HrjI8j1)l9?GCE$M4 zh_6&b=vi&ZU0>$7dklZ*$kwsVTmrQ>q# zTC@5KYjfxntg!qbXK6V+ilOtP#xlaZVvAQE0?6H6*+iCB)ALw0H;%L+_Zd9KY`o;a z@9%w3oDkG^V#E-NT<~q_p&?6XBwd$}!r(?INkn2<%=A-!tt_w6Xk-3Jug%U&>;cjw z-zO-&)*B_t2ZIZufq|}@-Lk=I zlehdI%#iZb?s1&k@F^2hJl#A&mr4U^#=;6th0G-^e2M!C+@n{Ln4DRTG>KG5h>I<8w2Fla%@^N zX_9YB{^HQ9nd8EdtQe@q2ncv;`)Ay~W%Nk~Fe+3b6tfx1{$=57Jp zB1|d{>o8%V1hYc~hn0H%Ma)P5B)djkcI@S4e07>~x2VCW;2eOwmUsg#m84AAbe%ZlRXU%~SR^1CR15TK7>kB&%Do|Tn z@zRH{kwQac7$F5u%>^K8mZN>jskn!2?rk)i>SsFpm(^y~3w9x0v}lx6vYgR{?SuHL z%W#1XFdg8kAp9lclPmeSxkb_KUD*$Uq1yz_Gg;1Nq2NE_-ZABD1L#JSAawh=&!E`{ z4qA_SC?g`{zU~W!@>4asxSt8~fbp_qVaHgURvTv$dgq1g5Q^(9b&td!$h#S(cK(wb zJ;tr{q%*^NDveRF=^_#`;mjS=KYW=UObSbU?W#*f$QBp7s*Vx@Yf9Pr4fHd7UiMIs z%g<{>H?AN#!w84hUtQaJE?j6_cKsVqfu)3p{`|**k9p zaFg6XVdaQiPx?`*Ul=A|PO)*kO`B51>QUZFkZu;jF3r0gA|$0jS+XRE z+&-OuR@UmW24Hg|zna?#$dDzSY7G@2?d_`U;wqCtI9Fa?^5upPSt$dJL%B_v_!v4l*iqXDkjP9WKVH?wX6=EJL z1ME=-U@ofkdydHbHrqq-)Ny=$CueAnJff?fn9OHWcSG4q!r|Cz&7oIEsLzKXJFN>@ z()&L&Ev$xD?HSX$S5w?DYbwNF7opy=g!@DZEA_jH3xA=0BgNpY?3?ks8O;b~A+SM& zZrESgqB0UO#jBntmIpa`QFCSGSy`MsrmIkp}EbLy1Z4ZTZmnW33 z+n`L@g?FO$=x!*gZkHR0FbaKx1FSAR`V08m0;f;)m-XHeGD~8M=FDt)J{!@@Ru5Lg#aa%WlPR7 z-NI~J%3f}5q1TI-wZo7@t~@bSGR&Wq0>rs<-B48R%PgUz%gIiDxu!~|+W4UO=PUP-vqTFA?a)qo)Dv{ zzDklCuqsr_S(@sFUkq=z)9_Q$ZSX>d=3dTDM!ou97+eHy7*yO;$wgu%a19fgnpg^| z!2UtE09pH#bXkij#!%}hW^+H{KB`nSO5WTFG5gPlrm$v|2kjwz_4z55gJOFw>HZ>H zeXvCcRiWF%HtK~pbQP{#sL`IbjF^!ZH+nH1eOZY!rjrjAeBGA*AFQOmZs)PkyVi)3 zIx`5H7W4N`C;StnY} z3^^7QR8~~O9O}BjT!jpNJBy@mmO*f{pd>2~c{N}D}7!NEPvvb99Lp&ySljND?{DlI$3Q@KGEA&S3x(o5Rou*Sc($&@ z*hGBpw)vWd-#CYSsOisO+b%0`q-c-ekx^hP9(2C@QD$huU}|pmnk&gZ903jC$y8kc zK%Ih}Yd{sRI=N0^yF&_evEwtBfpS0sKJeKraQzl5;)wKfwGruBG>E7ld8~5g zq+}ca6g+$U@FSqifS@7*^nX%FrjF+1aThpCHKY1Tg9QymclV42>iVapxJezxW;xP1ORB4Vh(8K@O|$* zEJS)huV(^%&tIyku10a9q$GUgyZ4@iZ(DY=gql?N+VCI9YKU>TB#t+5ODgf0Y+ZPS z_>&_-_c2M1h}n)TP8@-%x^1H={D~bCsgRMmO&_khocg`woxbirgJO#`kAF%vh&S*_22H%0yKIHNKzzPs?Fdm2)ApX|tpT#$$8YeroV^x?L%bi_xsiv>A4#n$l$=!?Xa&XG*H#2OYtt(l! zesNo(zGA6^mgOfis947Arr?ynqX@7gGba5`76(vc&DN{GA3`(#=Wj`=0 zZ5)+_3@__XQR+totl$SA%#| z<)G{w`f**_{-GY-lXCZ%!06|q*(;&M$!&g_1-ahn;3Cvn=3DytUB**#xp%i#dhhXT z8cb{ph4^}lmvTCW@_YBDy{Riuxcw@Ysd)k(#=F$O*$0-VUyZrv+`REViag(GBDb_= z6~V1+0C5$de>67{&)pGvdN-@}qUr0GRF!r?%gb7Z4+l&fUWq$$f6vmdF*Fv;g~bzF z-zEdY8GljUI7N?gQ~yy*&q*NN{;X}_L@U3QJ^Mhwy9a@?W~5il_S*v%io zJfi#O&y!g;Govsy<0A;7NN;cc$_6{w=YY^C+AG_S7L{Od^(o78$cKtI@Yt4ve?v|A z%pAaf?Gt5|>jHi|=fRhQUPILvEC0@YaIJ|qJo%|7;>|kDX(5LL(mbo+hlWMmfJ}%j zN!WoHf#HOuUmKC+Cbb_)A`D-H40r2XC^HvuCqv70ykf?R!Fx`hU!(A%ih|z56(J3z zw&8HTEhCfdj-IGG^!2532$Rug=4E)?AXUGJ<8X2Y;2nm8F+YN9O>-3oh4yF(%-pXc?$T1nOtZuV-=vXI6*(C7qW z+3WFAo|a@^nt0m4AeJt+_!7T#=I;UG+m^4BWpM#((JVj$#|)4}-k5VqxO;utR>LTC zxWe*M*_RR|sjBTopO^SoBFV3kj`_N;{sbq1rrW)j^AGN_5GXw@wGo+X0gW~z9aK}$ z*G6pP)3|bBFRedV711v;HOcLJG{mk(QWg2jKii~a%xQT$_1xV;(n8fjxbX%Cc5mtXKwZ&mNDBKP4lEpGWnGg@nHOZ@Ubj?TiZ$^UJ` zfPln+(I802s1YKK(j6Neqe~HxPI2TIL%LI%kB3mj?^I`hzKg7DBt(_y?+2Z z*s*8NKF@t$*Lg}-dL^*Uc@j5|@B~`=QsRn$l~NFnUi|Bo^RjqMYJ{AAA%Bl+4@oms zq)t?j)}icHO$3$Uf|@WL+sJ26C*tma(07< z57%Z2p27`QBQ_2-Tsp$1b18qw!P{Yvs7QeEQtGl^Y`lzSi1`z{IXew%XXklMOV{*X z;TPMk@pcwXmPYvThQr%g$tsmwUu+(dV;s(LedH# zewgt<+D$t5z8gred#n%jFZ}>Q$cGfhdu};Tc|o)kzL-K`C);{L^9?MG?vkIbB(6E@ z;S#x~z^Tvk)0nC_0>6^eN6d|2BMeiY?vFGr9ZvtdbGQ{o0$&K-I=0a21}*s~);~Wk z-HXXmNj`f$eQ6swTF?ZJ!sJ;kBGbXuJW}f=r@9!o#(^oIP(EhI{2?gYk>aWbZ6uS7wH}7#IA8ISC#@ISi5NKd@hr7Hix)(HKbu)S&fd@kr40GQzpW*I4Eht9% z1)d^`??lIN_>*9}0ykBS=yQs8Ybw|pqB@*!2-f-&16|jEiH|hDqAV6-~Bs*nbn0I$6{dN zYfWUGp`An4lhueD&MtV`H-Pa?B(oF4iJRgzOXRZ%yV8bXn}@Ww-x>9um0k7+#AA8i zDL>r!1$bNx1gaGzBQx&;&6#<>lWMfjX(-+#ud?DWUfeFdWI!n`PdLtpvtP(oh??cW zl_Hx9hNg;+$0t$t;6OUhnAV$f{*M2KI$V+!C5==NtWIy|llOa8TEIVq`VJK8+-kbl zyZf5&;{ZT_#!J)h=k6^U6h{3s7SrxB3an>HN*6p4rIU0g+A<2aL_|#-CD3Sn)5opQ zy&s1Q3l4o~#)C9Rc?!CJUURrUf-xBVnQQxpWu-{URq-A+BzH&*w3w=D?zdS2C;+OP{crZ@SxB>aC4C)$^go-p>zy^8rWY2CaGT+z0%8u&d*(Lg%irwoONEN%MZsl$nS2 zoMbDksy2r4YhsURi&h!bwgr%B`;toCr7z{k03+DL#%9_!& zD0$6w_cFoUB!8deN%pAK>b~9t=gVzBz5z+t)jZaD0z8mglD%bO^8nuk=UgD655e`Y z48@>~SXqh$;CtWplZe)=#fcR-FU##{K3gJFbAAsC#8EPX1Of|LWT|vad$oz_4~!r9 z$Y5<6xRtGXf|6EhInSQJc6cbpxVxAx-7uP5+gx3!mZP|fja%6`EqM1-Ng;a|UFw@T z^1_gYe~xs;@{mVUg~hszH^L}i`L|Kb`yt=kYmcteRB}`;{3n3Lb{o- zw=B=76297eXIKR;=h5Ti6+U4@xOy34mlEO_{5x>H^&^T^9nCr5XsH_69p_Ey3hOjI zxpIQTaIEAiA8`O>b~ztTAv(O>SNr5%pxo#o$xCR$-c#w-gpGJPX-L%6t_{S5=nOfxdmCJ6gz{^J{#CZ-^J$|l6ZqE`7f}IXQh&=D{e70 z(Bul(Wx43$Zl%viAOgN4sO_hCgw)bOJF@+u9qc5d!fK-aKt0 zW0x}d3x1@bTEyPxY8YWog?~R5PWgF4;sp%%K&)8LOBNW7v2}xUTDe@T)`utNq8i%8~CFS7xrdr!VT$Ha5QzFMWWx5i$$sr^B zN>;30e805x^R!=oFs6avj(xfUuyUBd0H5Fv4!T|k< z#QSmdBeC}3$7+Qr4g@~2^1Huv0xUoDcCoYC5Q?s>`N(ZlsOQT65_M1sRg1P&VSpM8;P6 zg%-CUi7c{8f)6TB3NcXCoJs2AUr-L2XhfHZss zqrw}(7L^%{CU2@d?g|*7Gvy8s6OqRT#|lwjN0=^Mz2z-qZro#?;j`*<rGrW4ufTr{$) zd8#8gW?2!uKZmE;xbg1J;Mb?p`^+#4NOef2^r>cJhz-ly?(2p>I@nTrova=^UyT)d zg)H67*5u4rtA{93X;{mTCH#aC%!ZY^{c6k^;o=L$FKMkqK{--Z;FgyUVqovxZ*s=# zEW`pkJ;)o*8qg0jXDCN-Gm^Fo!_N59W?S^d+CmkkfThib@J0aU~4?oQDBm^V_9!8cUbr5jjrSu#${x8Hzl?lY)O?fS z9IP4gyJ19p!pD+#%j=ys*}O?`G%*78@wY2K>Q1qSvAmQKbc~a~|6dVC(IWCCK--`x z4nX7|hZ1@)`OV)*qRcqO`yrP+CsD>$hHd zA74JGHFo_I9dI(qnSN`IRBfkuwdVbIm9_)cXTe7`H!X!_#Y=|CDY659joxV=OI$LV z`*4%-*FS9AgzQ;WT4^8UTcBUjobO1rC1F|IEb7L924>EpNH_Dqg#|9R6+fq zLAdEbcl%WM@(OSw!Ndn_GIbkK{&1pP?i8y5&M(q9kiPpW$HXu6Jx`iTMg?2Ck-!FT z2v?X`u|jHocS#8Li7v_FgxXFTvm~ZdXk(0B}Exw6?SMW**}-AK(VF$k!=B{#j>;_Mi#37%((eF+hYAt z+Ct2+K;_RouB^;7*Ol_lVdZzaW{Ylx$CZ1##uT8(R zR&uCG&vyBWPa z^pal4^1-EqkJei}kQd9P`M>Ir-1lOiRRlaYjeHU_c??;a+-Xe;cB+cDdT(hJrQDcs zH@9%Uzh7_NqldW${d>(^Q8$H8=+%51Q0dTHhNLpbBDBj8i#Q#Eo^6D03tfubau^ii z@E=JlAB9A${`N=8=RMoWNlNaBG;1`SzQDqoBrQ3|nz0=GN=At7S%yPcq&raeW@VhY z*^J>6^r7m{;k!%mn6hlxd0wL=u3RG2*#m5877yM8x_sCmK5pU=)CtBotcXbo{wnZlTEYtID^!^I!akjzkm?f{-e3@iFgVcU>7z zEPT-6p{}V0C$o_;-E2Tt645ac5BIkdGJt?W!24Ts?Z8@mvC!c@$QsyPZ&p0I&S9ad zpUZ5LwvZ9*W8A-Nz0g_CM`Fo!bbWy{Go>Ay_J+uw;ZiM0{iLLe)>1u9Z4$QvM)P~T zR0hlH{%c!&7?opeROt9hDD8q~|Ee^@>w2*w?HQtkwqr0yB{|i4Yw!5v zFSJO!dVlgfnk|Mnn5_`xB|WsF1{WLv9S0x^F&{y6soE4!!PjNTTR9^2R{r$QfeDPB z>%vZNb>JurTaNLsPG{FM#in;n%be9cm*NwMCaaxf3SCCqt?9(;| zR#9hjg%HtLE|KJQ|5uMLGnyY4N>}~yh^G*6f2tZ$9|~^ zWh7%(Kkx&pLQKem1dATZ+m91B*(QLeDQkHHvdK2N^h<+*2|mSQe#Aeln=g6YL{F+e z*%8Qoc0Hw`p0vSLdLY}M!cdC7$oH9?t8P;mL#UL|2IpofR7VzmbnibB*M!$bnh|8Z z+;Xd_7-XwE5I?f=*QF#k)m6M6mZcU~K6rf}q?L&v3q*F3NttwAgpP?h#08+!SNCMJ ztx@YQFnsL@;b8oD7S~_R!tGQV%35Z$0%F+sFDz7K$NTg$p0*hNv?{_r0OehJ4`3(jN|@xrr_U#PuiOI=z4UUP~J;E^@3P~Yf# ztdtzUz??QQ0*QBVol7$wL}K_@!P3a;6XdALk>Et(&2hL$xIoC**g;4G)ZigJIF zW>`(5bkkt;gc(`G;Dr$wsnj~wlTmh74>247$@_+)@upc7SW5#4AGb!f+cvAjV_>mD zRSCg+$6BYlWGj~#i!C18N94_&0*QynRfA`P)UOqKdD+u2G@p!bgyH9$pZkg_3uP!y}ps=59PY+I`%c zP5BHsTS!v)iDe(hLz$`vyPdh*tKwONheEVa`TbPC89DzqVMNS%>iTA~7_%w)q�u z@aD6z8o&ooWb7U}G0{)l-ar<5U2Xp5V;OeBfoJt};(XP>%EnfISN^L85m!vFYvAi3+E| z=hcU-`eVhEhB!Z6stEB_MvU|}L}pvwz!-pPFoxo#;R5pa3+0KLJZ(iyhGB$*Y}nHI z$TBOn6M0|&sQQKn-JXh&lOIzU`QwWAUfw#mkRnCn5?gH%aHRr|a1w0^tS|=3?|45T zig`=9=Av6X6)UnvE#4V+Nvh<; zaLp_Fe*lC{9k*qnQ02P+NLX>~1H!0dgG^~(NSz10<#hR6{TIyGD&^u*RF$?HDjidn zUL5#)mrjov&TEr(SVPX#{`L^rCOP#j_JrVC#A)!`3#2a)FXpJ6X{BuEo^a%DVEot? z#L|v5s&vjBxU((v4${lT-FCe33dNxyPW?V2`a(z4E$#jB-}`BsU}bj|w;{6~;(0Yq z&`@hZ)Vl8zS&E|Re&jBi*TBY5nQyfH#y}Jj3IpL44f&z!AHP(AMiJronngzSS-+ZI zbSatj!BAm0yuzxU$M|KicbQBv>{h6nXAbh89%&xiZH2R!g6DuwC7mW;t zUmSsLEB9H%vYbr%@Qb9Wu<;AZR}V%Y)JN8dolajD#a-`SfS^=uul~xy;;m+s)QqxR zG3=_jc{0LO>xmyfEYXVAv*x}&!4q=wdE^kcVN3-~s@~fx{DSTZJiEt&$@#LDA08oJ znB!R0Q|TpB67AaBg_8s3g70xvUyxSTdrfq^FkndS61MXB-by9Ff(*i|E(A|?0;I_T&rZmHz^aC}q{A|Z_d8D{jSsl~Jt0=LI5q*GrJ43*f`DF4- z_@`5?3uON^&Dua~<5nJbF=!iQ?;}S!8&KS}FlkdUNd0&0V2u{JVzB$y&fVHu2@6$1 zKUnA9!K+yAvdpbk#KHnl*x00R28JaupOv8=k-d>pXvEcTve+?E`R%@w3AX63-R9#m zv7<$Awr~BoLy4}o=%=siG3~{xYNt$uBfKGPAXevGFmSjvy>`-h!Q9u<(i4kRH}#jRo~;i z=76^6hen-WM2P=K!oY$Er=nKe-M;3?N}?EIRLkmr$|1~G|9<7lUbiEh+gP#S>cQ*wdVOqetB$}Qe%v?~bHpk&8i?4^_) zunjawQ|SfwM4toRk=TS|fD$Yt88sPE0^s66ECw1Boy;F9(0{F| zBBW7}8|2!rao}EtLP}U?y)Vtqme*qtD{VlP=?k6*ih=92?W}SMNm~zB*g8NV?TJI1Y+L8Y6bNdsXl4vpGT4Q-ybYp7aq zz^9;{;ZLI;lF0ljz|zXEJ@6D7M77j$(yK^QtLgq8F&#Zvg{H;Ud2bI}4~BN$DK*(yF*-PQcKtopPYDQ8v$DQ7HBP}n>A;o|*8#$H>& z7E!m2V!ekeFnaR=^&O-js_`CIFW=+7I%oCfG{@4Eb|UK`7uyN$JW}ei`mh~Z`>fHi zmP)&_KUbcJX}^1$rZKYe2&9)nm(93WmaFf2xd#Q#a>+l);1hxd@X zGiKgMdHQfxtVtOEapR>EY~V(mvG9l+>Q-hW!strNe`JU8w4D*Y&oRT*{9R-V9ym)d( zL8EfL*Fw8X6yO{8(xgOU>X-T-r-ZwjQ8JFgRd7T0&hu52ARCKw zpUYYThgjm4a!GJ6y((FA`){YFpXb%n$Z4;_*0;te4rmpnYsxjGznol&U1}(8mS)0+ zYA%_jriryvX#bn`i<&zWgKwZxPv;FvZ}IEO+46+{9>ubPpn(TV^nwZrh}vRZMFXvX z?^%^FRb7A!d~uv@VbJL(*}HBm8Juj(zh_8s<`{!LW^^*_NN!fcVcP1m6PXNvD}|YH!|TiH}k=${J~V6yaIBq-O$=0 z$|Sxcr2ZqwB6uxY^MxUECnEH#o_vEhw4`v_cw8s@xt#2~&%yUZzSS*I6u06ZR546+ zEX<*=jFOAI^7(yEn@B2U~t%bFx zw@HH?KMVw>ikicPxJ~0We($Jzst|Y6Z zrKp+gONz}?ZX&ex9{)#8%0i{DbiF$RtMBx9f`WKqTrz|@ai+VDp!D{~XoJq}cyHH2 z$hF&G(nM*lG|R~0SDm>2kza|2B0G4V;iX);-jm_!*1rg|7i9>;5Hh6iZxfSLmA#|K zhX+jP9c&aP#^Q;u)+aRy{{ip>n%rdK`GYJr1Y=Mp11mma7Ha9GOX0`bYUjhV@4gCGw;4ZtLv@&^y$!qQWUW(|8j9WD>bDAzR_cFUIQ{NYZDQOMPpqEwVXts=Z=dSwD4ugc2ugn)~I~^0Za`Gsk~)quqHg zUxj+?4_@YGKJW+tpLlUoo*&%3aP>Nl+d@%^bfFMDalW&X#z}Nz$9D2r_ThIbvI}Ec zO6Etjv>6ON*!+|l=Fw%35Bs`ql8?;j4Vyj|Oq&_+|7+KZ{QU1ZOrcdTTAT5mm+!k= zvg%KRYmARbOVKZOcU+@i6~ZU(<$TnVy2-?iH}_tV#_kye9MPx0n;6+SzIp}G?fUd@ z*6ZKBVdR@n<$ckT9?$g$JB+s1KoeMMsV-B6He=PxA4}iU?V>Q(PLg+(xqd>1vOPo0 zH}SQ8HXXg<)kv<})NDdn#q5dJ4e=46e~X7)+qH%`LmCr}GQAc+Qs=K&C^Yi92enSikoj2X0E+W(x*VFsU0{>FZ58q|5$yv9Z`X7F-06){4Mf z|K|7ydEhlPPCFBV7SH~j-f-)hfKpa)0Q?{*LZuLyhNDo&p|S70i9lf*DqY5*z@^f- za8qlR56;FlyxAwb2~2QeWwaDfuTu8GO@zQ{U~68DVmD+-ewZ^M(cauj>f&>b;aeep zV7vCJhYsCtR3Dj!;v~50tsCEg|M&q(&x==7qzs=ZfY$4#``2*p0!fnm0)XZ}VsS3xGYT*L0idzDEddWV6VLNB0#vwP@Uk;>kt}r@ zqXG@Jg@_#0rC3`4MaRnV26*e0&os@^H=1;t019Zn6USyMr4cMl+`}6b0{U=-f9+}| zsHOe7VUYCYq|O(6`sfB0=h|W+PABBkVHt;b{Zg*}Ts&Ko#<#ADEMHB3#RzsQmPs^A zh-tJDG#SthR!CGnFz`@)<#dyVOYCcafI&Ce3e{%qmudhTPE*-Mc}OJ9~ylqn%kNg*@%nP~7{;PsxMJUgr=U{pToQBYJeX@%G7h zu|W&lTbn6?M2ahPP>* zGimVAbYV6_7X@=WuLx>qjfp8nnMKw}@76GxqNkdy8(o>d>M)#&Vn*9f{#5r^kCa~k zF}v<+$3@NeDUMFakT1?N%UWJPNDz-B<0qU@`xHL0BySR5*;6@ zqy36JwTS=u@1&kF*$38bEh83Zxs;q{Smd}*1%5bdgR>G0y9F22;5M|Cqjkf7s_i(_ zXuA!jh#VLuF(S)rArzz$Zgz{-1OHdX*P-{B!nRNAj%*M?kQ|)?V7lD$`4E>k@9D05 zLu89{L*%wsB!LzdWk6pFF}`iaDL%|rvLA3Rd_dO5shHm@9jrcL)ft%6S~em$l5Hbm zA9D}ekLEMha2=BxC;(DJ4#c6=UzwL*##=p7R%Ba!V*8P|MEg&whA&d$Nq}qsO*ZGOond&>@33Lb&8*SUq zQ%Jf$U333@*cm(Mdu{{Vrx!gGG)x93Mqi_&t0&bEA>snWgC*KLwwY?fK9#2&&+4!z z#xM1+A20L>X2p*&f%DXin&f5k2yQ8rg<|4;e!Ye%Y zVt9&=o{%<$mbUAty9G#|sQFD6p4cW$w442q!d!3u)H z8n^vk5Fr@Oc)w2INeIU>CP?Rf>+>7eF)lSlV%N|JXodQjbig z7x&yHa8$11rpEJ(Aj(gaLZ05=9ylDfm96x+PX&Z|L z!Y8+wYvvLlv?OnG__a37kaWNY7(sZu_aP5%RLN6l@s1+Zi6q$OSliDci`wXd?=%MH zYGpB#h8B0`Et9-^epA-;ShJT9Y7jVTq=}GyeIAP~V1Gm!oxhyAogf)-rkE+u^I{t% zL1#-K)K2ByQ>C~a_fqh|BTy>AqDDJ^sQ&Nqzc&+!r(>Mj?J(Az%f!{5q|w-)dLLMeg~_~Z14gfMi&ZvPLL)6kX&hFlw-pFPtz z?)!4OqRf<#eVddm2*_KiG>f5Cz+5{6t5IGeZ&MaJncwhOd3>r3OV3mE?S?9~PRF$8 z1jSqqXxL#j{f7d5mdNB_aHfI+`~`cXoY*kv&wx(RThW2iCpK%cT6^-h>tSanqvjpr zO8(ibi|;sS@=ka=lZyd_1Xb%;e7@#8K^beXsTUG_TcdpQ&w4P@AgN-U+xys=qyN|l zwBbF`HzGBsW)U;>gf~Tr_DcdHpE1O|i#>2#o}})ZoC}CMoMn_dzpulpa5@UIM?HZ|jN4&QV+t-@f=mn0891;yj!c(_*K z4<6(TwsyR0)r~!bGHdUZY>*0nX*68Pb+*+c7hR@s7Fn6cMZzOI*YBFv-pR=JGqERu z?6m695-`$C77mw>nP|S{P8Eqbi;@(7k|NFnecr`rFfvcEm|*t&lK<0AP+OB{^Xq!* zR7N+*7`}%dui5i-FG!IeoVBQz1rK@&ts4E08^8U?ylll_-OGwU zagWS9U};rkq#1gE8bzVovYknX zsvylckEn+j=^ill$fp4iB=ZIy^+jFM=4n~zr>766SfH%{XfXVK7ntKn#SfN=Olu$} zCW%z?dpewn_AtOWbrskuhGCiz8%K+nyQh)SJ?I;i9e$}4DgkJ^rrQHgTP{a9RLPfW zxVkoX=}32MeE^Q|!KowYPHNCllqO`1T+Oq2R=R`1xG&JNrb_cqk6?W!f8=uHpj*c+ z3E1uUbAO0eLThrb!VihUdTKkQO}8>_iq5GQp54_h3JOVSjFmX zM}^Zfu=vzBlh*3PJ+FROJJdfY=lEutNq^cr^5Kc~ zT*tO!%Dq}?@aZFVUu1g5BzKgtyt6dOiqGhXq|Eqd@0Wm`s+tTw9$TG_?3ZJY*)fF5 zyCS;d;H9<0I5fdqXW4Zc>hu+4trS`PxxYR=H?L&$BLOt~fwiA9fo0^=DwB$Le?LW|7b*jo6p_IIJVvLBZcnroQ?yz! zuW8+kpuhvs<@N$LqfO_=;X#TcJty!B6U5n9{5P$7(p$r}h-PA1#sulI13t@8dK-N= zxz}b5_)D28isLgB-=u5na1v)23oE9B7?MHN6HDR0ZT+4`$X8+fd&(e}g>uD#qs1b>4rD5`^IZe zOFU#m)?p|s4I;el0l=ln1G7`ZA1C9RnneWlZ6q~DSIoD1qu<}`1rg&-U8wP_)4YcT zA(JpaEP#-aY%<&C?r9eg=+>Xm^`jmAQxIEr>56?^VGlLO0X*B zR1G=*&p!0|g(`#Fef`DRa~c7wL0W4g^ls?#oA2BL=4_9+pKLrEP%An*eZ??|U*0tR z_v10fpN~+$w#HWLHI<}U)rYz><$--=&EmyxjF@{b{Z04Lk#o16O$YhJlF&wTd6|0E zVe-(|38{akvFT#}lGBOvsxkpF@VVD?J4_wOmZ0Ug^M?IfJJT<#lk1+ea9zmPLEYxr zdkh`o6oUpz$tm91PZ@D7<6$x{d8W)<*K+U9DEwe%;y-{&c%&86Z!7#sHEJ&#x#<}C1QW4O?eiw=mQ|4F)WZ!4>D)GoYIlVUnCT4R zE-XhW`#|~WJ=TLO8F-qbN!te4J5APb;nf8?w-UkmF;+gv$vhgb3c3b>U$B4aq)JnF0#sm_@Wy?w)PHp(j zb>AT}5#LcF6g27S&5fT8spd!m2-hBUEN6KUR#4zA^QibTJ7f;`s`YMs-H^GYXFSN)xU8=Nd-P z(hmke5r6bq%%{CjB~C?Tgi@V9II-C+Eb2#4A;@@DfSArPpz=c*u}JaP4po$0Txjg% z_c_?xUa7c$b!nQlR)s}nPZ=c#l@4zgCU{}m)1`SVxU*W8c72&LX;NvwRXu2L84g48 z4K+^Q%X86k=E9d(cXT!7S~;W@7aM{bWsHHwC}iwElI2gQhZaaP#pG*CWcJT}|E*_{ zOK-P22&Yc0tMY?yOL)Y5p8oCrEg32k!uog6j&(ZwU!$qFRX`DK5lq)Y`vo%E^akMD$qR>i@giS9+H2P zuvV!Y_qnA+Xe>a;a$>Nn$IQZqsmyNZI36zTCO&hOTWLvERlwE@teT^_^Qwp3VKId+ zeWRLuQ@_mL_W2rz@GYVB{MC{1!4sDNFQ8vM{2s#24JV=v{ zk&{AJetiglEF~rR`leVLDRcuaef|92W6{v@Z8pej*#*ZmCYFu# zx(OIQ2)(Z2;F}}jW7v)*=P&8Yju&ie5z-hE3OB9d+avS0joA#^Zoi>0cMI@YprlkW zem@4@^mPmcOH!2%Il7aE#dqU$y@8!@e<;Zbp*Q~(NY*IS{#Ndzr-r!sr}J{7I##sr zu32{hJ@{HEUokgc^;4a(K4HFlBAxMuSMj|V>VeJa;rSB?a^Cfv) zT}FXss?_snkzm#C9l}rgIyvkwOf@clWPCkf_x0+jCgGV_uRod9@CcWGnnjDJuEEc9 z_o9XawgezFz&;BV^|BdHsSz3?UVfsSSRPZ3xL1LN#(VP&hZ+o&Gv>NZ^do;lI^2&f zP5?8HdkJaQ~?CQyosAu+q?X9a(0H~f!#nx`H zK$!S>Zitx1x=cO!x4(c*-cL(le+>n)VXB!b0R{wss=!DnMey~>=p4D%$V*CSWoJkJ zwtA$^&c73_6+4@wwCe}Rcs+H@CjX<4wRi2(2XGPsASsgD&JeN70yJAlM%{eZ)~x+9I*p<~8MmOaI_+)S3D&G|0>cwMSc+-ZNpZw2M3V$FOao9v+pQs>NfM-;~2t-Fgtg`qo!I+y7Nsy~uwg z52>l)te36lu3s%~`Y-6q_MyI1+j_j8t7C}-Ag)Q=Xk>w{fYUWhfv3qeMZ!8tQykM{ zQsXXqs?l!KeuvBU2(Oh#-ntvsA*@|MDl@~FbB4sTTi8?Lp+cw9Cg3%{NAe04kH&?` zt0#AE8vt&R2kO&opqYRr6r|vG=K)>QOF{CgiRIwAB$YApHbW!j{HAhPA?%idp`i(a zQ_SfhQmalcTxfSsQED$KYV)c(Ni~OQDcMa@e7jiR#GKLLB`okD%`*1nWd0 ztD;ytNU7GHpU6nkWR&`-`~OJl0@jb4R1HG>XTGU6+Edls7_u~%NyEemz0RwU7{%>M7L{(jC{3_?3rNUwocV* z0(;A;we@aQr)um{FsBtfe20KrazT(M^L&@XGA}h6wQ(v5QIW2Px7t!92OeH2YAC4? z{vh(=f+5=Zb@9KNdw&9i4cSma_}1XY^)#Lpq7>l1G{eWK;Im9C+IA0MsMM?~hJfmzgMC! zrNyz58gHQxjc}B^^w5!cFW_DLc_|I^CD;HjSWmL?Sh!2zJX%klAfKI0O`TIWpq+BZ zPSf$_5|AS4@E9LpXTUQ?BMr8QoPkMl-xKnos>QJZa=Pc$dA<>BtI|=S`0*sE_$?k| z+%_t$@{?ni)$TP%!WR}@X;zD5x#f~K2O|+qwPJ;ucEQt|0Z(9jMyL(+^#|ajQN8YC zvh>`9{DQiO{`lzq7Pby1DU@(2mSD&BJt{cTh@91}WiDpO5)0T#IMW^y3g(1(S&YDN z`2wGBT3@WrZoEqgprMCM&mE0E%~9WaPghR`=x@<2ACh6*gH8NB^vfAX;x& z0d?@fK^j~Xt?>k@E_gJS#nsv%zr79EboAnx)_|Avi+~O?{Y)4hnI6k$?r2Ub{e}eH zHSV4KNlzVFg&wPvQ(yNu4SNE2nnX+wS8~I~g2yA094_z#h^A`KUb)MAmU(uM_rPz^ zS3o}taTD0A6eIGon}BVw${Bn_^vEQltD}*w;hQ!EvgOMx1E)ZqR!KOhOmc=DTMg#y zPY>pK;mz+DXQsS)bmw^dV)Nm}XHt-!1Wa z6}60Hu=OaI{Qlg^*y@2M*W$_*-2I;r&AR8!61H~V8)3A8AuEvuw?LB zPuT+)vS$TUr_jiJdds1&i38+S3K@B6+q#qYw+$7#NJKPJKZbj8DkC!^uV=ho%@03Ovv>U6OykRI z@GZ`DwME^o=~Mlz?V+ue%LB^yn8AFaZ+IGryx8pX`j>Rfm zP+ab3`~d?Yzk@)O-7M!cYsYW8woleqSo;}m*#sk$9$IZeUGV)JbGC{t)cr%+9&xo4sF;SRN zRayu29^sBdyWcbpy6~C0JC@w2ecW+{1=UEH`-f zxy{V?PDL|e$I@K6)4)}N+&>Br7<-x;TXD3pyDtk@O%q4@?`V(k+6W)(yi-DC&g{=! znO$&Q#vuPAVS4xRT*X(fF-5^xpUqR~AXM3Va$UEZUGBG#2f|jB5Ej(uX40ZHMfj zII(u5VyUp`^Q8O1is4x?8#t zbZCa5J0%9`?#`i8y3rXzKtLK4_4(%ge#73!zW2S>b)Bag6C-KY%2}kbor4DMcJFLp zOQv~IoY!WEicFcO?hKRfZVrz*Zbc|xM;j?aqRjPkVWhC+_Yj!^>O=aMCV#de=;UQq zTThcIJOl--}gxQZ$$IU2FLlqm^mCi}l`3(azYK z#rucRJ|-wpjIYT)cc*gbz6csXaH?owrEw|062&Qb{!OFEt6l_?|J$zDfNe1WWG^b; z-@3iG$~vz<{+3l(V3uY@AonsT_je!C%6+f#vtLsTb{}-%3e~F1();zC)C)r zEv>N{t>%0l><&o~JR={PKE6~8Czo$n>Uhi5LkN=ew~(EG1FS=DG^vK@A%D~vZy59N z$or6O^w0W^5^Z<lu`BN!c@a5*3)h3UN+*o0pvO8aQeV{BVIJ~(_x zpN?%8z|E;7qi{j}Qc!7Hg=c55(($2YJxv5ZsFk6ESA;Kd`|IE!{~{i1;|ry)6N#a*-M(&|Vonn^UfuoIjpbwV=jK=t+3Q5{i7;{+>tP4j`Xt$n!+)VE-F2PV>`)g3joDu zv)Z8fLaQ37EzAaoM+l#8X*j*WP`~{UGO=1c;&!N=_(EvavoQ2BYU)vl+)sApI z7xeTu>stIuQu}$l-w0$#9=X|Vfiud4~Y^Su91OiD-k>Oh>@s#f@`YXLvFceXNo7>#ZZ)ywg!CPuRh;a8JOAPUP`8Vfnwm?fM&4KZHDbNLej3!b z+*&taWw(N23OYGIy$}76&uS-?@3OocmTgnklY;5ip5$Y{E=*gljqxZA3O+uB)Myu> z3xCZSw9;q)2n(L>|63C|e;UGFfc0@+A-#~c&HNT_05(;)lLekXmOChduU(`KFk^eN zCw~H!)YOxE?z$L@aM^l?;m=U+$Xk-GE7X246j1lFoYN4vJQH5fXn9yuXi5}pF*Xv z8miPor{p}aq$qm?Zy29HQdRQeCFoQbToHo%G~L3D#sgnpPdrTL`WKcx%NuLuo-K zC&_Tj)5{|4kB}oX%F93ceB4j*?d4LPWKmbtm$NJ~Ad76xO!l&AuWo+Mq&3>svyeu3 z*J;KpG^;?q9VEmNnfZm`?*Am>hex_#_vq~Ry$GSyWForPMWpuy+lg-@!D@sp?zhIb~Vne zD#xoNec)?OtuVA@C_>EMs#}lJ*szA2aCLWlvlux82oRau2G_kYHp>~1k-JfL76C2U zk3gCQLbl^_U)d(*B_NP>MD)qk4QKfEAN_CWcvqXTjbMej2zjjwBp=JS53l?jJHgEh-In3y9qpJW+W2#a5Se+KA45` zvX$Z^C&fF0)WbN>hNT)POd1`;31UP3`ej%@*xyjzrjfo6O&7BYYY;Rd(6qh09x=v} zSLV#1>P^WfU9t=ULw_f~7C2YN-A9F-N+OFk@kO zpMA&{_b$faTX(EXx3U8}Z>w}Kfgf0^)Y8io5b3u{n1bX!!=D?w&_=mEuWQBfSgn=r z#{d(33TP58jvMb>fn+3R={fnNQFw>jlF=-i@u*SLN1xmGXV&q^?C%FAO>Zpm0&&6w z#8?{lRUTEjisTjJ%ok}vz48~oVQ_wiA1LL_sRfEW{)vLt zYJAtHEll{|qzvRX^jmdk1QlaHLv0N3YBO9gAs@%$iic=IKc12(U>Pgl86>rg@@t-b z>QinZ2eRx{kWvZ0`o_%@+Zms`0eZHr<+Y8mA*3}g!Pt+=_(L4#3mMc(@Q=vff zW@L-npdvH4M?LS40WXSZRtK)cZ#!>%2~}tQb5i7KTfl7t(L)wTN8U{pw0|smRbPik>@{#u~-b%EW(Mj$6wuUcr(sZH>zmmF@oF7IXRq|XDU6QIa)OA_K|@9SUktpQsib}3aFXh?ik zo@cBGB!8>g?n3-)FjA9D8BFW_{Kk$Q)uxPfy*~32Y z8UK~^7H%4hINH)n&4j)tmh>>V8TJ`y90~Az{03$%VCLO;j#IP8i=d4@`Qt>$VCdS*VtQJbtv(~-v8Sg7^`ORm^`RE`c=S`${71wpo%^6+ps9aflxUlDKCN)xme9?jp(AZ%*6pq5}w7+9y=V*4xi=eUUgG z16$irwFIFSA_yV`{kB?R0--~kQpACjl*~y31*dcgB<-8^f4VkhLZ<&e&N7h7V=RA!5Z)Rdb$uGr z|HS0*v)^)v#(`uoR-)_AAEEGc6rN~y6P_W zDV$n!t~4**Nu;?e6$IPrY(^96K5>bRLD|B%er?#T~zWO>^pb2%^b4wMdcfA#jJ z@OD~!c|wlM{!q%e3jTQe`HOqty-(GK{@fGv2jDLUwXEDZhNJO~L7bxxdGB@i4-Ru8 z=QL9}p%{*?Zkh0rE$he zP{g6>q~WuqrHL9--u;2W1M66+J^z7{0EWD8k!Z$GPp+%A5E72h`a50gYUDIhCcd4( z&2zsMtrFPrJ4}_}P5ssG`8WEUJo$8v9SicDqVXd^4@jc17Y0sTVg8ORE<89N7_4bMKm718tL3NSffrFo zMlp}+4Jc9$sIS8sgNqTsnF+!G#2X5^KL>i1(c(e)NfXtb#MD=znuokEe{9y|omIN4 zD#&G`HI@;42e~k^UL!FeEA)MM*@auIG#wY9g#awV&xrIa?RDzJdT#a+xS6VNCT0et zvBu+BDa4KO1i-IH$7)S#s%+=fbEq1~yQMa(2xQ{rdc2-A6a6d!ry@PURy?BLed4*y zwLtRF2x2E^N_WCU#6RShCn(l?S$j3M5g|Y+(dm^@q)-$-h_u6HW$W+HGICF~JtN1` z4fcyHo7aDYOi{-z-X0Ao0p|_#6-?*b;LJQjr`8Sl;c0>guWAmt>jQsFCtn)+CPA$` znHUO=Ge6vv%5hK#Fn&;~?jpK3LhOI00g3cfx%vK;Qe=O{^NIE4!&iT`lB17)!*DuY zTkwhN`Hk)GPqi)0ipY@_g|TUoN?u9njgG81wLoX#dF%MX>|EYVn%cx4N*ac@S)ho# zKh_-(G0d#LvVQSi^why^%zyf!4u|c}pBlMe#nAt@(3y`(Ube&)3!*#olQ8I~#05D{ z8@v3jZDhT18sZ&KDPgYB3D#-~d_exViOsIRS84N^-oE{po$u&!e=rq$8a}1$bxCx+ zH}vi+ZRz7jtCT+*{N#?~n?0*50ThR7IP^8aruZ*a5{?e`6UkLBnJMDNlGTY*v>a*U z%39bSG0OZ0DM1O9UCqo#18JUcePK|iy2aBZzp06pvd7j%@a*+M*i>=BmVepz27mMM zfT}Ge?xhUXipF_Vy?Mq!tJe5BSJA~)E-zh;o}>vRcOsyQ&^Y(ry=n#?AtTe{i{JT* zG=s8%{5D0pc%MV<@Zdibme13pA6q~!CZD!t z+`sy^79O@E+q1jdERa~n=}__N_WP*c|3S(5%YP^# z!ZS&k%{1OPUqf`$eJ@dWV1<7`g`1qrT!PC|8;UCKu4s?G9G~6wiVCvC)WIrVx-?D9 z>Yu#`Y|8hQ$b_@R=e4rl1#Ne(WBoF=fF|FlC~6LP_Wy8y{Jx>d(wvR8q173oY~-%0 zCw97s(XZ=*_S1kw!Jw?yh43h`>+dQ<9X-$Q;Xsp+BR;cimm)5k^oRB=5b4R%D_r4N zfj?ZwBsY6iPc*~aWsckNCke77a9+g5D|{E$y$O*3Qm>BxP;htMI(@Q*K55lrkRIxX z=8O02@ia1bD3@Eh-{1`!^xD7o{ISq;v9@tj!u0NC;Gb{7)S9jSw<6E#pQL9D+v=`K z7_ftNf!8BlpDkBtVjF+n#)Cvv#&D_;DE{V$p^Y9^Jxz1*SaeQ(*p(Zs?}+dXwUZY2 z$Jo`G&0k)iJX~I2@FZq`fmdf9);<}67(t?rg~q7DA1`9O?7o7v!~2}{=UdI9WD{X< zvEj(|Y#wH}ZP2E`ELJ5?@>8lDyK0B5aE@LxK&|tNY zpp-CXH*5YYNj}Oe@Q4#2~fCKRzNCe!a9_qwdLu=|O5H~w$5-l&!;(d_Nug*J5d7ifNDvB%e9b-OK+Zcv_3f)WZw0)GT_J<54& z)CETqPq7c3!Dg}(bh zGYx;OhZiB#0{S_+GG?*Be``X<5+AV1N%^Gp$JSV4Jh&r-^sC~encrsq5!|FSKwDUn z_f}jPi6XW3x>4d69PNQpR}&#kFZjJgK2pC`Db4{J2nKl6S}bGy$*pelc2o{#{AEgY zMi3c?zi%%9tMxRE$ds z!E(n(O@HfpxCx=+`etk7D@xTN%;tWQ&L*mda&z)Y|CpE~z|Q*OdbFO>kuM|Ue+$P1 z5=T~+uP+8D6&h(+S_rV_nuwEfn6fkHTl_q0(0ZV084vy-4S^bjH+h%j{nuhueMK>; z5oQ~zH`(*N$_a{0k;p(<*E_h`o17XTo8L(b${3dC5KWK6hL~u2h`_h;sF-1d29lO^#0IEWXFi|-0+7k%PqXU=RrCa#XY1hCjK8zUAIq$@TFPkhEN`iZL(o=ihhfBx#%7UUkrzb;@3Cah z{`f0LvY=)h*4e$x{)WQxb!5qq1V^8 zYRzFLG%$LF2E}bcQ)U1XsB+^mQOpmW)?-{tcsBRGM+&qs6}Nqu<644h6JkM4tOjS% zrl?V5=KLTa%@Ym%r05)f4Z4(2v?pY_zlzBr-g1#~Jbj^!nE?)_8O?o8V*@k#^X?)s zd4N#BUY(zdvbZv7a(M9PFb<+;2Q1_V(u)}hoDo3MwC(hjyqQjn^WvF8(6p~9JBf{g z&{%IcI2%XYMM>5@RD@)dK+KC4NKCMLU#wRuU^AX8-$5~dY11h#Q2mPbi7Z#)(`Kk$ zMQad-&Q4f^?1%-lh)}i^f`g+((*}IjWHh$TWNK=*vri`h)r(?Jnf4%tQ8p zaTZk;@b9E%S#N|Tzh>TB!>&h`?8~iY#us_50ikz9A@R(uQdsPFh@bIF8OC#_2eQ7N z3ajr%fB8;&KwRkNy7)@3cd_Zmd*B0E>VhFGiT-DEC8eDtW}UQMthJwZl<-OpH?T%# zWas(DQb#A61p@g%*-h|m@2mPr`qpxt6^-d5o$v5S4u1!oF*cw1CvK9zhu!G+fK zFj@v<4>n#*@IPL?1oBneZTSW5jO#;#`Ts+a^^(N2kset93w8q;y<=kb}?_|d_xHYOis(WI2mJaKPs1de5S)s9IIdC z0@K>`3JXvs?W3{9bjX~F12Mtng|0)K95{Sw%pl|<4sBGU|6cDQo3C2lY$W|agMjU5 z#t+MMVhzIR|C5R{h2oTZlm~L!wz)g)D8%3N4*pnHXR8Hpq3ON^nRy1kRstNj3jX8V zsN8aWEwy08L|@||_-;)o_~44lzh*&w%zm6FuZ~gpbHvD=6Vfq|(}ngzK$oYteU?B< z3-Y+{lXyQyJzMaFf2qHd0?P_Nk&EwV{1&DI{Y8KK4BrwzXRNL7#av2(0MRdbt-O`+ zfc3UPKVI1^<;q?q;5?=(%78T&3DRSikIHqT zpZU0EZHm5zL*GVz1_@#>15ObfEVlIJf*4(-uXr4^-TD$q6$A8eO#8qNWC+Sl>ASiGo zaZX7f9s$OoP6%J&(HBC=%Kd)nj%^SVR=FvAO6(~Z7C^uhnS3JYd;5>dzn4=@dK(7> z3l!;josm5DdvCRF%Yp0Niki05cLAU~_{+5a8#m3{=W_5HDx`eO_uY5~l*1YMWR^7f zkds$24tu~f#xM*Zup+$e4bA|mL2wmgzqFC?G3F#W3!Yei_4o=9#3Yq859y?&Tfmdd zd5F{;5`aVS9387xcH<99T^L1i*x9PvZ^4`*&OY%}HlG7$%iW4?IE`z%k+kLg9m}#Y zJff0I(etPRTuViLpnP5eQ&P!9yBP^C#UQgjR@enm@W5sga>=7bBLUi9%6t*}0Dk|K zAJIp~PoO+-a94mlm}v#KQqb5*T2>P^2}kIu{RN1$ji0!~as7s5^O1ZLiXE`D%^F)= zJeJDBdwW*ET<|*tvVf&MX)Q7ky22Q05!=h0rjhi@d&N&HHeCxQ~#II9AS{(BGr5>Z{lC;D zQNpYG^8SS#=WH=rGMXzPlgCi#R&><3I)#G>wr4DnO@7T2rsCDaq74;|F$;fSnsgH) zo`syOg^c!1xS-t}!Q3XjGYr!qYDec5DISQ{SlA*0P@{rm@^A%6lqrPsZG?H0c%YQM zNat(yDn@xpe+=RoDg-FQ%RkX)1+XCW5J=>o0qw7GWEF4QvKbbrI7Kix+H9JEBkB44 zI-Xd%KgN|LOnGBk#xrw%v*trScTNq?oZZ`STSPQ#Fkn%|=l2SuHeKsKPP=m0M@ADk z7&C#l9ppV|fHh9tAGXNE>lhoTaE+w>ekyW6CKUNelL(<9oE8F{o`Q9Fd;4(s<6sRB z;QpS|QtkCWs!j*!YsZA}pQQ0qzGh1bXJhHd)MRCqpZbDA?ICPq-60nfxe|G-ONg^) z2Yk9N#T{1wX0-UkXF*FA^|Ln9dmAWN$Vj0Zlv}_`D=2^lu>;S%hnks12dp2xwf-aI ztmioPMD2_K10-Bd_?O?jA0my}!y=1-`e`EtR@0?;L`h>TejiUQWx?|nzW*j}WcfQP z-P)mZrC*273u*tXwIxmcMBs`2G1YP^ttbNel8#elDyqosYyN(clht@dmRRzUspH)Y zs7`%)^$`>o>U`$2`H2Deiwjj?PC0=C6@=(W%AW9P<&+93t`h03m|3m4ecVytpT%WZ zuzzl~n?iK`hmwC}TesG*(R!&nD14YDvZ~AE`WLZFDLq`Hog%Aju1ohjXI8(|{HSvr zYFXSN^M#BTlU4Wj%C}8K(De8YFuP0#l1Pf@9<2j#=Z~eRxb>?jnsn0s)rcYy?6vego8>_wYbJpOQJP^@_glf?RmQR6Si2r>XsY!Xv&lL&t=wi9vzr~f3K!q4#)QId}C)!*1KZ8$llK-LXfQ(E# z5yST^!IWouojM(kzsSeQYkxuBIprlna56bUIGsM~@qa8F`wwMW>10^5sQO*>UKi^D zN?h7&EV0SX6soXynG9UcRLaT63cjw-!S|!yWjj;d&fn2w(vf~Ttrva2t>kEfQ6J0q4x?(z$E^INKwgI$ z>=IJ{^%udhMuTRZxz&)jZ3dWoqkiHI3iGpUlCBrylz44nZN{C3jD%G_AT+C~=L857 ziFRO<{zJ{A7Zrj4>59_AKkz%(pC!E|>1|kIyHVZ%cc%B>6EAH)_Cc?R>*N3Ytr2xN zZ6ns)H>9DP&DM{8!irnR;^lE-yB<&c()vTi*Ol0=qnpnG9_{iwmeLEE597+Wn)Y2y z#5NC~ReMF+y=BoJTqoyNMkU7XOSt$WT)!?k zp1x|qgxy==n^%Zp{YjWJRWI$l*+3lc&q~wzpvke1iITHjCm?#g;NUry3&UfdlYz}A zwbJ8%K0N=SIIjft{D%^SL@RwvL++vXF6go^h_EhnQ-$+Crt$Fef$mE#CGvKDO%d_* zKM0-n45y7wosAElSe2Am3iyox89W;NnBCsrp_m>b~>|+4<=5S->EWil&-~EIo(SjqE z_eWm4A+JyxtfEsRP-2zSQ+p6Dt`Sq;B6LyG=U4o5jTRppnHvqwcs@md-!fR0p>IeKF75 zQF^`cG+aeqrp(j7G!tH-RhNozsWARZutfbk&+`ChcR$!*%4zwOSxk4u*}4R4+dTRq z#w{^0@9TF8P@Rsx48N{J$vliO@d`fyoK;$kHxZF2;&(ByYRH@NvYqNU0i)z6h|gM} zY7u!o<9o?!rN|sD2P!7=AXvN^lL{;sTn{Cg#C?>4vbf@T4Kjt`5 z%%3!(;(WD+1}oU% z%Ndt@eFbrDk#THuQOULB`t$?BT>(oUUDps}&{3Zz;F`(eDe_@P?Lx@)5hl?y8L7~R zQ)v7|VylPQW3Ske@iMKeSZg)rtQfEqZ)D%bqk9o?^0ZfH_>{m8!Uv#HS=CLJ$ddDl zsWl9=iY_T;tv)mFwLKD-)Xf?~5}NQNFLKIiVs~HoWBq%B{RohrS@^II)#fOuIZ;#~ zZmho_cP1?C?Jjxew!a}qM0*N-?zo;`r4nv`{9ngAdIzz=px$32zx7b}lv5;*uQ+Vn zA~SF>k|1cnu1~3gzXa#+5~YOtnc(lX1qvrXbr)0@{0^!9?ODl5nLkAY#rm)_h)&YG zAOBALR1Tt7n16#tkTT=3gCfI&p6H=_SQwLFm`;%~xVurC*Kr}!7Xw~4^!8}wkyVl2 zHN18F1XZrA9%M4$;}zea9|lwU@7}f{6eUT_c*c_m%q;Qi$l89kO6oG(eXxX0DvPCq znuqu!H8a}ozj-$2=J!zl;0aav@QKj`y?HC(Ax3mEgnRLNDpv3F&@)#Ns!z>(Z@S#7 z!JQ%|j$$A>+@Mz`zXMhlg7z+ZJNLXoL_p%tKx)y(&f=yiwU`0qTUC6ifYE{A;?FqT zWqzpbZSq8>uErcwm*(RQzgBFh>el;akUn99Y{RthQLc7kmeOj70c+s1k?OhN?c2{A zLAW3GUZR;*+y&J4>30cL?o&m&zZmHZsAY->s$5)c{wY6b66951xn5B06zO+Rb;}-5 z-U0u6#yV;M*0=jzbi1m>9?fhgX}ZIwr?&MtPi`e;s}tMPl5A84srA*rzv6Wb16_ex z)l+sRywO@uMSWw5pGHJ5?FfZ?l}-5G$4cxEK<6;M4ya-=9C$FAUPbQj*D35&8j~$# zMU<6;r^WJt$eGZxQKIX#yAV$xt?r^4Ph=uOs5pbNfXP>7gsCGk9?lwXZaaO{R@{B& zdhZG!?7^6@h@3N)R`SSMz{BYS4IvX-^UszHrsz`0?<1mF<1WN=7k|8ncs$KWh)#6- zZILKNX{Sc@>W<34^b6^UkGW^euA5qc+0_`a4-Ay;G;2P7{o_2<`pFvh#;P%tl3VoE5-+5`BqA3gfm zDiKs9Ml|%Zc~~LVf#mY6Y_s2HB*E1HT;H!9oVm9bzg`S*^&8{LdS9SGiM=X^m=z)X zS~yM6HBuG67*#T+Gtj9waTlHVg)lTU-4viDp$e-OI=cx+U<`o}p9nvze~#YA$O;aK zM_$?91|X5HcIOf9RZ$2gLpQ6hY?HK&-Yq%RU_fgWmni} z$BBhwYqc55$Dpb8^-o_^g9CW;=-+`TDc)fl)O7*YE1HwGAIosh+-@XS^NbbBa{Bco z%sm-52h|YK_VOi5V;1sJmIMvmabf!aXFM`hmDU#}bp17x0qSyicA77 ztdnD{88q)39#wfOmhiatip7<&#;h>N=8-#X=*U+W15gK&H_`7YBOex=tz;HC<=dD@ z+5Q!66tb5XUjajNvPpHdfq;<>})>_5a`*)V)Z$01%n^Iv++03#4dnKitW zDgS$e>bkX8IhO|qlU9xBXkN_+&w*HHD&NG=ybFrt}!)bDqrh z{P1z2!}cS;jdj?r|AYv>0gT0KQ#lpBGb$wnaxD!3Lq#aRI8!ryfgaZMe1cs3diJ{U zQxO3!vTif=^CPhcGypsExfE%A-W^tfDW&yux(EYcqxxCylWV4*61PQDLgU@sb`qOf+hgq+4Bb#L z%#VFlH^lm}Oe+-@Z`m^iPxL~sWo6qGoEyULW$EQ%t_lABVnC<91hp`30&Nce9z&W6 z97pFnYyswaOf;TahUd&+Hq-JQcgcP79>C#~Fi(%;Bz=yuwRhJ)s<^d@{t+bH%({+m zYeZxK)#NPB4EKVs;Nx@VXpO1U^IY}Ccq{jtygZ5}U3vqmT)Hb0+NH5KNcG5RE(KEM zR)#Dp@D&q`T$a!~lG=%aXvM7el`x3?bl^uVGLd!#j98Tjy+y3!CsZM~pL|Fd4_2Ad zr!S7GS26{etcuWJ(E}ePo*pfDQi}-R zC7G5cNHJ+GF&IM^r?<3y6;M-UKi=>Y1E0(94TI(qi9Z5TuJj0UuHKpI3osy@+LgD=FAH?RN{b38A9h6;o4kBKuzz)FtGz-}n9$|koey(1^s!K$~}ylHuszL zbtusq*@3Skk?291oVyz!#th4z`SjkyS2$JjHxak#xjoSP>oISxwi=x!!gnxI#1FeD zo&nxrI9SqA>6d2cox&ge7K>5Y<^|z1%i#zqYEwUKW|qfzS_XYeRxcUsq@PKKN(8jd z=EuQ~>p)ud{cmgpd~=s7Yl9=$xLRg8ctDT4ee(xv>@zH!VphP~fLuJ+m*`BpQ2!f6 zPWj|{XCF|Tfnj9Jt%bWp)&y=gPs_Q<1m7D|%xwNd2I1q{YRa8hO_Wn9uU+K!(|kme zIB=WjEjExdrpg5)@K@m2w&ogP0di%+i~7Uc+(W=vo|o7qHreFO&@+}|jQDG1dcqnb zE+Wa-P^Ua)bpcIIYsJVM7Ai`9KrUx_E%{X0s2M2Ig&;I<8(g`S7Isy6qtw}^eCl~P zYe8wC0?ix`Jg%V*%odf(gmL9B6QcvzlY+l@y1{@=iGo{)D$%iPrJB5l5%yl?c2dI35LfVz{ zo?40aClyGsdnCaqL&Bx}&NxjLs~B?e0OGQY!eINT0Z+7u|FlHr_-SIibON&%-Pew~ zZ(Wf_le3C4J>3;qLyjd@HZq~<(H}akVs!E1@j6EyLPCa^Bw7#WOpy-s9XSDAKV5(d z%nMaEzP$K&o_rg-@hl|TkeinQAzoF|!!f1?6{-p~_C)#2Esquej4S%v;;ASVXprS? z=}(gQa+(^m zk0hY?hXeIowlEntju9q^7|KQv3eS`wA}R69D2_PxF;G1vj%l^}G5}0uuLD7nyG(KT zdc{N9*U8DCn4X4!m_)yy=U|2DK30sx71>Q=OCe+$rLwxRWtT!Gk5zOosfuEpwga4U z>^+QtlpNu0A`4}Y4qz%Jk!daUpxB30XBE%ctA_Fb7H(;eWqjjMZxAxN{8JI9NzUUH z+e^M;-zgh&2$r|ybP&TChIX`yH_2R;fWL}Pyw~V4X?Tj;cJ6bKI_HJ!g=5H6DTF@D zaDo(^iyX+?XRxhVdNme=3$A0gw9rfE@|igXVdZ)m^V<^Y2a_9oN&lY5Km?-zs7AMS zi)Z>%@#NLgc8j_>fd=*6+3=YUL`y>+l3IA*BRub}677BV$S2iGuVKb@NnZ2!{bqI9 z5*%@ZOxC7M90|uqkniV2-p)m=%y0zTtzN_{B`H98TTAYCO%GNf|LpY2nbcz5iSR+* zu;|a!O#4wAsDn-zXzhdH&ao1;7Fn$!M`6I<)nc|^s%dL=AOYQ^9G_M!J@TXa$HDq? zkr{V^`?*{6f$sdj`B<~B4$XuHZh6$pL$<=ds1{9&LVQCUX+FX8^0R4$UbV<&jCE>G zX3uK>RnQuP@%;J4%J)_Nef(d9*XwfK zOaDqHZ|@_1%RY*jYRo!H5G0z1`6)R+9j|=<=h6Ee=g4nRbg;n*HVHrS&MCd)$se|R z2D$c(o^g3fsb6?z#%Kfka9qb~tjbF(Mgo27I5Zn5i$QX&+H4)3Y_Av8a94*~C@2H} z^anpWUzw?KM8zPa2(=B4I%U28vX>->9@w<%K}}t&*ae#&7_wx`fed)nvqDW2sX0bk zx59<4MBjS7O6SD*aN5u~nN4?R-$;Af3obv54LJ31ebrf9zi^$DRF(Jga3 zg`;x(wFcD~yb1(z=I-%6hpGH@R1p{2Yei02N%gE4C?Aqg2gmblVVGao{bJv?p;yRklr_%VLRDJmVEB7@;Qln{wQP^ZWF)CiHAr3=)|UYXKPV(u*tySlay2j zE`Lzg-vDnS+fozk7GL0Zn@MA8hn%ez?1=@(eu zkEIqwqQJlXm(ZES9xv4!UtUttc>^AW+=GRA^p8qw`grd`tls@O$g*}w8IorFHjN{U z{`V|MH5T?Lnf8mW`!+?qgRgsOBV}^zy&bp@tSNp7Yi7K8-+VOAj=H@N?y=4XT9>un zBu>FNp_^9*EwX$2AJVtq7*zz@p5`fxRPC*6f9$Ctdf9vOipZ3(Dhak4?!cOOJQr)| zx7TyBuv4@yKSQBWcEravV%03=gbFqu7naNG^;)J6*31m5&oIZ35hH1f+L=uD813JQ zsbiLe_qNO&{V8M1ti>oD(83)+!c^ete5YgM0Y%eEpOx34F=y!}dP>`rY*p;s?TZ5h zkaU8Lw6EV^`Q03#{FVKaqR@fI$AEP;sgX`;`MPUz~~=^;^RsLy_Ct@wn49#cU zn!1{WL7hVCywZcz@cM+}HUhR8mr0w&?Ams(UlL;>e>x z`D;Ya|I)!A56wpByUGvkIPd=0kEafqEXs_FdfE5=DdO(Zc#eo}GvSHk^~#9-R8B}v z<%A!yLQ0BM8=-eeTuRRPi9M@p^oQ6|__2GuK~9eLN?=PMp2{5pm_8{z4Rp}@7YTUA ziusC8=)-cpCoQX#dS5OYhx}JZNTr68IR_ip*W2b( zHl1CwyRUofu2xhGEm~zntD_d^FfsHW~Zw zmG7HjE#QY1PZ2_|IlMug=GC1-ucHp_gblnG^O57j`u|W2(h4RvGw$B5qMv>pFCr+; zuld3TI;qbOjo>3#Y$@fxSAO|Mmbq2f3tIbb%P+TA&8O4iRe(oC=IkD-?)biM*rJfU z>2*G#^Z?84cYwUP>XiiZFoe5{SNGayu8&RYiVeoJ9IDDop|6KzsVWN^V1|VeeB}Xo z;2IONejBVr+Dc3rqqUk$9?Ns7SdA^$RgYsq(be4{0ZH!&z+bXFo1g77-f-n;dxzd# zZM+)$jZNBkf<&??uvQ+YE3s15+U3t+mRHNz;jd9ss0Vcss4{yhpfvA1+PwfS%JxXF z7$_lZu~sBPO%=}{u6Hq#98vjI>K@!5%Y<9L1(1IK=piH|6tG84-sJifK-{!~zN1V9 z(wb21@YKKA&4a^lAs2>xYAY;`1{Qw@x|OI=MymVmMsA??S(lM)D(PPfDsivsKs*r2?R-pRcKvEqy zw9dYD|8z*e1BXI?xBv16X@sy_4`#JN9=@F1k+YmOB%Q#%GGI&Y{_M(&f!%nLXA?dY zgUX9>Ayn&+0S|MGK3dF+!f49-8>-@Xq@m|4DGku(RS8i$a;=JcORwUpj?aY0-7ee zzX>f-XX#sGewRv-n)P$D?rP9;dEFRG%leGrfZ;(?em};S6+8#sebU9I{0x>%+3|?` zyxnL*($B{CpSrmfwwOgvynE#~45%Cc#W}1ps=Lt9k0&H;#4}nTWM?*?xRFS&9UYHW z7bZ6Im1494^TIqrkgXr$ZTd4Q=~0#cB*?&#`7<))k2Q*=I(dHv*~4!=%Io;YSri5C zq9E-brcQ=2K+Tz@~`&_yoN%3B^TfN~_ZeV2tK& zF&Uaba`~k&yoy1v9pog1RQo5o0Ex<01q*vDH1~e|Efv8@5KsXp>kVERo|b_g5oD_^ z?=ya;Sb`pa_e~A53gx1y@4-|dudY`2O2!P8`c9XgGgA9vp}VU1A152$hG0W_cWFGX zS+n+>=l&uCfU81rqNw))NZQIvWe)6?B}MASRdKw8Y(am&tZ~%fF8}3r>wlDvjV&f>f33kX&9syJG4(8WoFgU0TJ)E6I;l{oZJ8i{l;lN%$hJ*P>aEGMR z?T!`mw}eSV79wQV(8DoF+DFhLdDj2wx0Gu)o0WfLN{T~ayeCwG1=4UKg?;)PTjrXh z7-@xmN+2d&A^SPLy)epaWV+5A+Ypg=Mi7e}VCB;Ee5fH-Ye*Rwnu*W=;1(%$jjIIw zQpO0~xmsfT52cVR9+RG!!2`uIoN(K%cwOo@GLoj7pk0nF5NKwZ@u!)H(odQ6tWDLb zJEGjK=C}2awAW7n^pTy^(Vuvs{&0v1<_#e=6X^RYG4SzLk{GNmtjqR)9G!(jF&+q*Y_&jX)p8LGc z>o~G#fiesD3vgDVh}mM9^!eMYBa<-UzSU0P5+S-SY!-Nvn|1ehJ5~D*8i!=vxxIm` z8#Q0;)Q!(vQlZTOuZrmXt;ET#!6XiSY+X3)-Go5QyxKnB59QXPm}@t)XEmFOqC_^B zhsNXRq07+s4i>O5$-phFM>?#GvREHp5vZf26NjDL*^7L~=A7T@Cy0@RQ^E;M7j(qnwB`I#iA@ zZb%5paH?*k{2a}74m@eTHE`3T?VR#a;*!IY4$jH<+COctQTb0@(fnP&$R<&G(v?N> zKMcHR{=q7&cCCm^`EV!9HSD->EwLwVw8ndCsR5aXj@6E<|cVV&1m-g zw`mDs%h%(o7by}&(0Y3$UPC>7|4HOe6@x;?k@d=+HBjniz=osnXN1rrWX(y#G4F66 zJ&0@3(KVj~&2e4z6!Ek!+P(N6Yv|)#%3N|1-o!~e9fK)}oEjynf|Yt2u*>SS8~R=u zAiQGbxqS}TYjCC)A+A^VX@8V0I*_9OW?^!YkN1U8`d9qiEbEAa_|bTq_+qUneg5G1 z;tt;f!qS8s`6g+joLLl-|+0(Bg9l~pzzBZxqh&T)dw--@@WH5Z3(R!@xQN=74*1;xC5b?h`$Hp$#2Cq`0h{_Ik(8Ni$i3v|b z(FiC(KDrn5zMDLam3;~$cyaIuX>h9TD0IEjPu4_m2s^spIW+{EOgBtwXm&`^l5Am( z3Y`(}Ozd*~*OySe!*ka?=$+Se)QH$kJQdf&NvJ_24a473t(UyZ?o8P%;zK^NO<#9XuFH zab4jpY46_nkMq;w_Tt1X{{^P3Or2HS+J<4-rW=|mNvZeY4sn#=*EQkcwMsbj+ra8r z4E$haGp+-3ATX9|S5A?d5N|2sL0Eg64U2*!4$nn1i*=ZnTxVK}sbB6Diah(XiSMVc zK4&R1YSHaK^g*y^aeXD+cJnri(*o*y0jdPkn=Rj$Nhg^lk4Nm0v^J1~Q9*DuoM`sf zBh#>f;ZA%*zcEDcoq5hSXYUaZvbk*o6jR?V9)iH&wXeAak!kxO67SJwQ9=B%CL>I z^$1isr8Je#_62InSukZCqrIbLxx5;3P!E!=u+P79eCzo)ZG(`cy06&=yQ4pr^N`jZ z{8^WB_Dk>`WMcgsnaS;?XJtE|h-7CT{p24&gJn6Y@(0RP)GEg(xP^;`h^tqUmr7ZJqTL-bbhFR(oBFKmUC+EcS8e+%j)$I=hTL)D=vMtK{WXDF}QuPA*mH`IMgLf6`*|z}2QM<>m$jP)u zwEgOa+P!1v16xMEavyOlAhg)l-dbde)G7;BTZqvQ9Q%}%x((UH7U|81h zFO51kxmrkW;M&4V#ZUBD(P(Ug$f|ZpfjasmeAet4Dalwd3xe#&=EIq$h{`Evxc|y zV1?*_-hOW%1wH02K@MpqCf>rsYB8Unv%O83& z*kSC3U|*Uqp-I|1 z97x-wuheZx8I10s@w5P++AM_L?gN> zG;zz}dDBW$_iuQ@VWLl)lz-MjRJG-RMf-0Nio!_q>5C^Cu$u96gnNr~bFFJRaeL3o zo%x9KLXe{L(n`ujz!(f0p=cIe@OapH2kVlP?~LRSHR1DaE%k;IWJ`IvvcjP1(GBwM zbgA)J>y&xEdb~EO_8!#&Uox~b-ZR+{$uBQ+jlw3Qmu5)})0syj(T}wjW+FH!M|7y; zVSvt7{7-RzF?a7hH9zW^1v9Q+0`dct=Ymc*ff7eA4oAfwYhe2Br9uP^u67;m=wWSEdux7o{&Q>6dRVy%kF{I z2wR23~Jw7s0Ph&Xe>O z_58U0vN3~23VwTwO8<6+-wp-$?F2;0l_u@eEFMpcUL5it@8@*UQFuF39dfr&dZ&h( zX+DR_%e-6`9WFhjoxK7_6x*of{8_}2eO30GQn}34ix#FqZo>UW%L#8XRZukXjefIH zLcF)!g17r>*vi5esjBOfFWK5#*!_P3UyDI~78az2d9vX9ln>h3qQ|ksS7a3Q`J1KlT*zOX|hf3TK*pN>_At} zyP7g(n_@85(i42rzVt}Cpp@efJ3PrkN1nQ-2a9;8AemWn*g>cF=k6B|mnOi*?{D*V z2pA$EOd;pb4g3Kmmy+IH_usL6u>7q$_ia_cGx>UEQ9p$^UhW~T4%_RS_*!6WO8ZPn zp-e=54&J`{RbSJvax=(mITi%1GNx~0#fW+(He{}i)}zobBP+>qtcrIF)$8EtH!r{J zg#DER*FYPi+u`2fT8z4{HAbY$BXIIHb(*3L){s>u{`t9q0o_(>So!KBFleIWE8iXv;cagcw$v$(gJ@^h>9p-^1+ z)Vkq4HM1Qu5NBs$4YOz-L(@cO$L^v!V51`E35QNuekIRh9aU~7<+b(X`p|y9UR8Ow zWphshtKRHN9uAfiIsKMmAX2hMv56+Cy?OqLdL~{MV(!*RH+mU zaGU5<7V+4)44^JL6e&`{#3hr(Fa{k=D60Ycs_*I9#iRh6wZB04ReQO3EDbLqdCOinMD_0L9bU1ptq*HZme1U1)uI6^f8X&cPH!#=(`qrcsd zd#M-1W19baDXrc6k>ahcu~(qq%E__zx<#;wrunxo6}K515rlb@%juowCrs92cK4qW z#XWqt6UnnDc7wu4pQLT2Q0tBNTX)T#a^c1f^SX^FKnm%aF;rC<000@dlrd#D(~^@V z&iK=IBRBi2COI9?b$&LaSw)e2fd~um zzebrM6KkUuS##M_H%7l$&->2%#yhO3UiJTbTvZe5r*sV~rt5k@t75<%EV~m;fVVYU zMw(!fskN@eUsO<1hp1sg38RBYq&dUq)t>l5Xstp!wI<;?!-iZF>}#XwGHIeLnI*CQ z>mv2G1F!ytptcF)5IGJC#!5wb zO9DnZUmNU7lJunCl{w2Gbr~&@>0Cw^<;E7S?kKA})}R?-LpjHTn0Z+-azg zygnhmW#7f;TUv^sZVxwC%TH@0^}K=atkelQX5htBQA)JNO0%jv#9}(-`{@dt&cPc} z=W~o@)jm2meiNk#GW*O=bm3uzvZ+OK%P9m1sL1D~7s#pLQaQ9b6rXXjIAK9tC&gKX z?qO_Sf<-uWI?-0!MD3sax%g@W8%@ZEEPS8uwknNa%QS~r4=Zz!<0~ahj#QV>fhc!NEiBy0Z{AVdt+b`mA^Ja22SWK$hIC|2V=~ zvghKY;3@{3&r@X_aTnxlRX}FAD=J}3k1$AoIZ-}Sed$`9^urLta%GPWo*Py#RsQW>cw=bFg)Aq zisQ)SB>R}a^Q2}oqHUSJ#vZH#-BS*qo1=0OLq@)8RH?b1!3RLg(I+?xAoOtyVDUP0 zM-ewlyhg~g&_@>e>Qo~E@6h?Ceymhl?1uO8{GOS1^dmEoF`u`7^in^67rHr*zsRAG zxP|OheQCpm{RtO8sz4B+;xG|ayY3zK0k`5HhZoJBu+f}@C7GlRVD$+XWTJ(7NRol|rim{I{}XHh z@=E|)?z!@45M$xb^^PB?fUFux_g-=zdEui6=On&nh-UUY-Ey~96BkpG(0@K+KD?9Vl%t*9!btQE7&7n1+to-(PyzO5ng$G@v}ZxetdJfO6KvE@EyxgFOILQt1v7jQ24wMuU5hr1N)YWq z2H6ACC|UpKKfx_vlvxTZV)~&;l=PGiKMjJ1SDPA98aZqa z;*E7Boqt!b-?LAxv@H}|b>EqOG0}tBx3x9=xka1U=WCWxOaY#;Mk3PtM~o|o$E*9Z zms`xv$?RJf`7SC=$L4f?kkOjUJleddur%}TPQ#c zdJ~T|Q5vCP_cjSL)gfbGEkF%7XuEql^;J{z1z!|_tSZs|?x2cH@uGpho@(bt(t3XN4J0;=f|9g?GBIA*#d09{{M^E!UP*P~bx)^;M$2qg`PZ`7RA5(F3brnkm&U5KHURiJbC4eJ{KyY`=C@iU8M0a*IncOxfpQL09NB)c>|he0Q{ zEynOhQ?ct?5|#@9^+1x_O7b0?rPuiFNBKrlj3FJMr4FqnK5fiqM6 z>B;-<-|6NO`aH3S`tMBkoWz}ar>n(g$|jowlKN*6rnj#gzrF6@FbZ4@5!d4yc>B^@ zceL_KkRoTaK8PkVvfu^7hz(=CQN5+V0q$mi(Mq!J%8)dutkw9)qj<#%IgrudBkR)u zo&B)on#)ReZVV&%@TgNV#r?(UzAX*`P~skPihj8F?}H4tL*1z~spk^nH$5lnI4Y+G zp6Pja-87l3SNpa*+Aqrq8*Y`2}vpPDuZ7mSjNc*(FHBrg=EwR;d(9B{rtMFxh<*j)h zRz;KeZ|pqEH`%FVT72s1mxXjqAVcpF4QB^A^`oC$rTv;}cyhnQGTT4JM=EL&_gcX| zEx#ocBF=!d(1GY^df?UFmYL8f*YGOIw?EgO+Pyk=j%fdf$aCJ^tqZ>X;Zfk6Gq>3P zy)@d{MdHFXiKkg~)pB9=sANwsqc+s$XL>{s=kJcbjNc$*bm#jyt54YFn^&t|tJ>)U zI`h+uMZWVU!As&G-(wFxDP54z*Z3mKd?An^mM=J-s#;|0v-H6j4qsi@Gz1s zf;TX`$}TMKHuh5gccK;whih29Ck*b>PN-vA{jl3Q6=6D}eH8gh3ZYa<1ky1!L@p!c zjpt+~MHp&huo>N57E5@Br#12=b(XEiOioVn-wr_IiTqBk;+wWe$kq^=!v*D-OLP*$ z0wsr1&?K$#Ok&p-(d?k9N714^iV#Qb8rZMGofKfjw%YjIwNvjKNR+LBXv3yBgj$;O z7tuahawF;>i8zG0lP?)K)36(?dsh$sHPN!1Wm=N6^-U^3-b}aw-|Cx8I|wD|*)8Hc zFJo>_^kNcOj`3j+2;E~|!YkCoPtS$a_iGi&FGXC>rOS~uD?ZBH_&*aWv`6+~!Tu5Z zrS{BE&ixLf=@gjR9)5>&Q<}3;Jo#@#16xg?v|&b$*8y}V1QkYq)kfXeSWyL*spa2Sx%oG;5`;AsF41SKG^{b#ukhFS0Z}1y79f8yb{eR51M&7L$-@pycbw=yQA_ zNrWJn*v?Tg3Lvs|rhtJai#h82bnOy&E~adT3v<4rjHEl*32fq2JVTVL7YN@Q@eTBP zdWJ@cP~=FPS#eAlKF@QER{^&^Cr*;htb7J2<(>ymK*?y_>Sj9#cYf zFD@b)%!~^B{v#p&+_j8pFjD6PFVhr17qI*o9&hrJmzf3auxxjBo$TW*R;^!XtTlJv z)gn-=Ra@jz4_O$FzD2; zF21yg<8d zGQH0u&pYvpjxi}^%G)>fq!iJrHWlnGOvuf*JT~+C#RMpU?!r|8>g5aekyj(%H$DH% zX(E14CBkemcAq_IGd>Oe&aD$#TaH!<8GA_)knUj0o15odTttU?qOs08cgEnI1LM9FRxu*nX+}ZIfNI z@K53EJJwgq5}uw@JhJ>*zECR4pD~pu*n(s1K3S^KPpU#?#`FBv`dqhHpsi!;kUIV5 zu(NX-`Q$HWVu@u)73d~~wbzbz2Mv99bk2VmpH@r|#R$u&hA@JA|Lwq+=+a-5=d4Y7 zp)iHjAK>deFK5pX0VS)Ai2_T^V%JYhA?2UhigE(*qEF+`YF6|KMO<<|b_e$r3``YW zaVJf0-di~l$>9J7D;9+(9eLukH56Bwt?Wxp)o#Z#Y-?(|7kXD8W;bYv=*Ptx&8gJ| z^uF!W> zmUN9VCjf9SK$jifmptq6+#r^t-c;q7As&$|y&CA4KmYDvyXvE=CqFLHggZ!o@L-_-_oygC1BhIcf2GX%@_ z0TKW})WVt`bkcxdmC1ZtR6|wT(Xd|3R8sC?V8eCd^}FWRl=-1VTgt@g*nL+1!^>OP zTW=AU*d9fJP_<5& z%hLDaH3>-9?mQO?n0d}bs!3DLMyjjVw2;Y$m5KIu5aF@ zl%-v)(dp@zf|G} zBzAMoJ8kUtx^_2s=2zp4qG}B4KR=M$%s8 z9l0m>*IaE9cFJ;beQrgUPu(;Omn-;LrtpLsrwUU0ZcyyMy|l)xjP5c+Vt;KT?7{l4e7s90D8YOFMO%dy&wsC8KJPG zk57_E*_4y)hmLU_3h)Cjx4quw);4G~Zqef@`TimCBoBxN^b$2;XnIVs^)h2&!wp%{ z-#nUGo9UObMAfp;;@wtUICB^Oa`P43jjc1UUwY6%z^uwRFsh5I_aJV-JyT7a?_E

nJpa?tY%78dNfTr5#}0F%~i4Mm)>n!PJn>$ef6w6($R&bPiUwonjX+6w?6fb zG~zAdC$;nokE`sW-c>F^&m{P+>T#viw+I_TK?pbzhwG~U5^cL94p!gsr7B)+pe2vXy;E2^PStmtH;PHr-_<7r8wan*`yFH{nL?-m$7$ z(4~}7dcrHggx=Xr6cN6GEW(8ea}GsYw?k&_V+|Uq1Yy%Se3qh_7UnFL)ACbjas*>Y z@BXQKmY%Y`gq##1U_d|*^r@Bc_uOmzuP5GRqPL-nJ>+6g(rcnqM;P84G9-vlHYUz4L6O$LMg4=`tWEAk&8J`y`E)s=r(Bg}vmi;wp+tW#8wH3scK<}s zTRnqQqs9$qg$O)DYRp)9&tt!RiWC>SJyxBi7D)VWcOfzqkv~`R@>{kyS}Zp%h$ zYdX@1*~A+@bq5uh_kQLm%DTQ);=FZctBjqDvV)bTBnC(qa7ew5dm= zp@<5gQ`TH0=4BQf#bHuf1$zbpLu{vZLNC%9u5uCp|250pyzhP6AfJQ9S@CP;m45>6 zj`2zEV*Z9ttG#13f_J9$f^Xk-#)?(14zE>*d_pAcLv+~an2A%}YFo`ph&!%UrQnU! z)KlY`Q|d3WIN>ftCdvy|U&53(aXY=oP1`^fs%@^W-3{>0|FnmR?f?&fpSE7o>$jo@PZxEjo$w z;9~bgn#;jkB4GG-kp6(g4q;@+gsvWMO2!Y~(*cacMS=(6cj*~JOUz1M)x|;pj1ja?nAL5FM_NqD z0e%TjdHy6!1z)slQL3@~n_^9XqSuzR!r;;(R2?jh?$uQ`1kW4nIVxisY= z8VYf0r}S^8$#=abK5ARG}Bt@a34q1CERAvV}F>y&Zos&sk(DL{6-VT>dNI+zoytGeg~S` z@8a|i*cVtH z?YysEPUX$yJw*wVo^4aq$wLMahz3<@epqF)XN4Uz-+w;&2@7cO@4nssyO@9+(|BL& z%YtC1ESRKP9;o(nqxpWk*3zP(@-J(q^3ozL9!JVdhf-E?!*}|8MF_adeJYiSreJqv zdZ8Gq#rWjL4q+@j>rmKIsH@erLlUdsWjeF|#IL?Nz&Zuy?Dr%! z0v*l2ZXN%)4B(C>_n5ZSC=uKnRu{kCt*1I`Jn+-v4G9l?;YSQ}_04QK_(3exm2KWX zY{sG=+cRO>K;Ya2eJXzc_D^B(cCTcc7JNuvh+EYbB(+>AJ+<)O{^F)o0szGwJwt0x z9$TViexEg??w+IAKjHth#92YO)g`f7@pI#XdL? z##51Yp7?&EeeFMt>JO{qf$DwY+ojd~f3WMEi`ro4PEww=GrG>|pS*MtySPbDgC#6l zFNCNQrRek=*BMxLW~c_Uw150NHK=eCJRDm!`6RlIo|@UBv7MD5JTBn!*``Z*DYA6P z)v)N}VREA&7wKudSA3gSQC7rXQF2IiHpCzF?*{eArVd}zdyjr`UgqAH`Z_dG9_8=p z9r7OxBRTIkKF5#6y#aNtwgUv6ev&+2n;!Ob<|^W--v2>FxmeY2GV|!9P6ogpFn!H7 z32!@x`{oLpT`eixSMeKji1@pW9mG|eiN5tL2~c8dRL`F8A4XhKlPJ|{9_NmwJ_S(D z^!-J$t`CL+#!Y5B-|SIXR*IJd+hvxVj2W=eiQe-tz{1j83dXUcm{AwV7xBG>T6dx7$t<7F-h z+~h;drq!coM<3If!o575IMEH7*VKI@^q{nwam6-wxebgLEVXN_gTkuST9O?yUEHTcKx*1(9$JXG5IX&4ipor*c-e;8%uzfZrN8Yf8N3qTV} zjn|B*M;-Sq00qnUEWw#Cu5(9`uuQUp_JFOu=l{O@`mSaq)_@@j6ldv$Pj7MZ5HCM7 z#OY1pe(gyhYfGjSC_6-d2(YDd)H{BY6?<$U66#vmz`|OJB2cG3b&aqT0N0%mk!@03 zqxjbB_6* z+ZEGbqA4}&9h=6C9mLWbkL$;DOQe#vRiS4Yc0()z^mZ?k^0xKp>L-~ZKK9k&_^ynS z{|*fi0jJ-X937_54%d6g!RRou;38d&PU55|O``JyMkQEuSO*yV(=bR$T)FlhH{xl+ z>!6HzDgoW6HByvWl;KWg-B(J}AFcA=T1)VK&FY%sw7mLZq5V65zJ7d`c_gsJXrZ5E zSk8s3VDtj9v`Z4Q!Ap$80!@~XKIB^+pL^{#%&Ez#S#g}G-Q8Ss1lv_)V39{-x`&@$ zZ>UWeny*m5c6!Rgh3F0#4 zdJD%!wmwPSG)F&GbtNSm`5@kq0dNwg zH!Qn(yw&9w%k2ed=Z~Cs#`{FNf*7w~B`^7imTT`+JOqg=7pC-ue7LF+g}C3PuNk>m zs@pO&;vIxDb^BfYR)KJT#p&TrG%c!R*eYA;T9T9U4?d=p-gn^P$89!XZCJ`n7A7Mu zvwllnqrzJQ%iFm-g-9-F7VK7$r{IeSJbZN63>Ir3IB(!W_?8L_%?{4>Rok!m+MOEX zj6QM{zT=|~U`KdL(poaA{VCWO1)e!XRujXI2BNDi4@9lo6jt@tQT z5|>N}DVUi%rsBlQRnpX#`$0viB_aY{DGu=BLWD`f=p*klIC*^*S2lb$odmWQfB7D8 zrMlC-B5Q7)UuC7!+dXr;{A{C|3ecrIc^g%{dL%u(%z@;Hm4e+mRUz7+L=Y#vJ}x$V)tD7py|kr?=f?bcjD3m;RV{EksGrt_x2?G>8?qJVj!QBoD*8{$Yq>pXF3z?* zcPVBtC@tJRrKE#Go}YtX)Hp>iFa3Ip%Om&Y(1FUbM%Hs#qk5e?;uM?_kL+#tT{@>w zXsOWT5AoDb7Cj$XbHM0~9k~UN`@)s4WF!tO`;9U>OFe5j5Ll`HGF&|sV8eg#iB^Re z$~k~qjq_ppt|`j8lL~DIso?X3DMhauLfPtP#4Tt^nW-ns$MdxlNks!q68<>V?hIiT z6%5%^6E{;k3-4XbQtomW{bI5i`CBzZ6#mDIO-vT&c{8^TE~OEn`lasYq@{qJHnLur zSiry#SywRe7Zb7G=d4jgIXth!318*LWExOSgUgUX#$p+BwJ&{XYDVeT(w@#3=eTrd zj=OK>(J?mRJ?@o;JDo_{!;5WuO_~_WR3f;|x0x@6Mg_(Ai#r|r7tZ|s#q=Pr zGDB@5ze{M;A!_BZm-%t#jat69`K6e{0z`b?h}&)oa7fC+BwoVJ_jR6d%L~Yp21wOh zK>|ujeHRS(z!zO;h1R@&{TXKsy2_imyxOT`BjsBzyeMEo1#|JR5{@NDGW&=sOw;3tPc~+Jr79=K_Y+!`^KTD%s(CpLoaFZGz`pwSNsplmuL?twr@;G;mk3 z`}|o{c`$sHL?2M`4FKcQDEYqm#5~@8x_EVX=ODn<=&<8AHU}vr;!w5ukQFjvFmQpf z9R!p01;ma9b!7ZPzOPYqPOySxqwjsJ?JAD%pJ&wYj^~hV#Uu(o;P@%JZNtj6l;0YZ z&c*Zl#AN0&DD|24(zms9=~9BfL%8m!_;{-Kd&n6p0_H;Oav$lNGBCax;+R`g#3hjH zU;WN9VN7P7B61E5BcoJpu86|DobK@I)5<+F2WCfHntr#4Dt zU}6yzG6jkE2C-dd^!dAnk->>xB~A*P{h2l{(@7^8)B*RkdkYSw%+2B^TJXP=6EWU_ z_}6SfT<@#Z5@-#2U_h1&s8kt5JSd~=AWw5lFvW&{zbk-{1-&mphH-GT=X!_dXc#g% zn1xzMF?LG;n{3mrqpSfzJ0=gAQqFT*=ZUmj?}iW{%DDG^Ql5|Wlpn5p)0PT8Ol)ch zW?JzOb>L-#c8)9nH9|VOV6$ z=A8J9d9ho^+E%XR`u>M;98+-|6KQpLc9tqXEf#6FE@~IO%3vLvumV&+ssYj#cU4xT zeMBQV-pGsKtXZ66cCUgyP=q1n_Y5XeVt92nD(9QlPUMYhB72f+G(BIYR+F3S47SpM z&g)}}#9x4apcBGiWzc|`FXoL2*uSr~e>Y8MQqC}vz5m;A-ZZ*|qF5h}kMAEiW zHlq={Hy|a4DBgxdBtYe!JRPjym0yHQsd$rCQNTzqe#DWe>r3|)tiNiCiIp*I_xWpx z&)G^cGZN_oNz}dY`n=r;OM|l;d@gejj|3^DUSJeMnX6ivhR#eL6km!c8_N#$#2X|~_vcAA_DmW)Gr{U?dU{{0+!36u z-w6}=>i2hZ+=Ap8>mnlf3=0%So_lv6au(n2L}Y$fMcf?c&bBgXNQ7skGo!Hj%o|ky zrtji*l7PbaM?V5}07=| z$iP!8Yk=O;Lx@mmu+)eMu?^cwHPA4+fYB*@zjhz3qke#Sd1t6c+JG$%6O|M3ZvplOM4=fqWUi`H+R0FX_MO=SRQCU8A1UezqN-ptqlFBJI3iCq!0gO*{1_qB0Vw)XlzM4Z% z`N;jZ{Fp|b`pyzp#m|@*{&O^D#H%ZXTF)g>i8vW)fJOq~Lgu%Lv(z7JV0xx=D#{5M za~&&wR+iyTL>EsP%J+$G&rmc?Wc?8li%0MxyxOGwk&7Bpih?+1RPPsq76g%d_kN2S zwn#O<$X$O8Mw2TybnWRFI?bSlkMN}P>;z9TDq}J5Dgg8o%b1QEX!m#_!NHON+bS)` zX++G}S=Md)UF0%3bhgv5QGO?4=ZWd7pXdiW`a~pFIQf!7QG?TFZ1?V9<0uAR5SVwy z30thHwqfLtDn{UTSm5~(oSl$Y5?EfRHd-7{<*a`f(aKmLKf`NwiD`1s|EkDaJz)(b zgKT{1v5M;xo{@^C6FZBu*sQj9;AY+T0tQ(tLi)H8Q=f^0#C{Y51qEvbv?vybNvDODISSW1n!edc zhtxVK8GygssEM#A3*`QLPPZrY$Y^U(VLce%m7rH50C z)`ft3cE}l!1b>nB&!x3Lb23+c?MSBQ`};wJ80TpB^;<>v&9vswrnQ-cFRu^M%AAgq z>ZRYXdH~o>Q_Wg;Zoz{=zFHGPJ-bK*iX`QuVcxR`GuTVBZCryBVf@r6_|f@abmC9V zjg)xc*h43`B=uq(1RN#4)wS^A)Ka|oOe#^Ig)@~W9egoWW zncFWr+2w{t=V`nRl>Cz`AuT|i?MRN7My&G}_LT7_vb0Bws_JC#;lvs{{uRF@?0(0~ zA&Y89m;vz2=yuxP9hu)(_i%rfeW}s*@%e=%B9ZwcJLvP%IFTQ5>Gk}?K4a!3H)Kf{ zXxRdg028YaI04^Twu8x3qw%h$50f5n&hPQVUYf=_^6_UMPnIZcls46Lx7m{c^4U;- zU&^5C@y-o+lIGCV*Jud+g|yms`}%(v-WEt0*sQ7k{eKu94A~}ZTY&4`TL*`0q8ryZ z`+m@=cECEuR-;5ohNmOf2lr|4eGNY@h;!0RpjH;Z76bkslTfVZzijV^MO;&9rh(rz z;p8&k6N$UrZATFt(Trb0bc!e(u2K!SDGn;C#%Ab(X@avPi|jUuR;s8#N$zuTw=m4( zP~q(*3`Ab|3L~HtDRBVd;(g*!;U0z~S!-?qY~RC4-IXvTJ296wW7yWtUi~O;4u}%| z=@oB)x6rr-WH%PFIE3@rLUt}%_#i$s^Am_j4RNg^0D-`3ObA>b#c95B93Mj5C|T5E zefhTiS0uE()Ie5@MFv)%_GEx%4mPsfjJi~wW$qJ>i?rY%C&)Zab)F*;0-6qh7QkMm zM9FgypG1AlIrFr-CU2l1aL>kId?GAyqM44O6VKndcfY33?p~w@*#% zLxuLuJ%uS#M=MLG+Qg^{aM+$G^}K&n^$Np{IW;kpCZp`ae^xcIv0~ZSt~|!{8a+U* zm&8J{Z&_>(qZ{PsjRg zVq0rIj*V>W=x)(xH*#~$E02&i197KzaGVjQ{%Y9P$(PAkmh^4dEhlcchM?TrpQlsu zcZgEr%ikI^`KQjk zy-k1k!l-A{cL4>jBUHAy59Kp_In+i9BE7j`pDQvj@9Hsgm(6v1F;|Cbzby zbQO#2WM&6>#o~%-*c;eEn6)!Px-P>Ew)NKyy2Gw#3d@2ZDdM&jVS|R;;La}gDQX?J zEw|bzg|d#sh<<(;3zV^-g6AGz;fF&NQ0=&b%Ze;yK(Y0Kn)rXB_atDgm#ZHy;()oH zUZTf~MgIVr84^`~!kZpFZn${8n5JT;6jNc5>RLR?udUMaExc(KeY@iS`67{8JGUp6)l4zbgISJ&Y{^=;mgGm!In%1g6exg zuQN@fNX%3xrU??j8-#u(UgU*l&x;pZ2yEMGs?Cn;^i}#Y<*>_A_3U!vQ&U8WPm`0R$+s;}lWKoPRk1-35$eh~lR7)& zA*Lt7P%bjG2gF=3GQ%rUw}>{QEl%NM*AVc)?g-qRNTP5~HkrkhCji?9+iZG=&IQIqeX2ML@RP;Vw!|^J?3dt|mM30#S#7bnh>Fq58>tq) z=A+uzq||@4Bp#>G{d2{bll~F;Ks%~=0hf@t%?(hhOx7pdn3;IqdE&T;qo0>H&UCvw z2yW2=Rd5^c;^7!aRD`Q{5i2~*!Q|V5=h-AErbdq;jJh4RqFmt<*b&QVkYyGyFpqLT zPjf(U=}1tmK0R)wwIgLPd7> zFDT8ME{^^jNggBm3)IQOZgJu^+bpjU;xD>0!r!Q7^e(YdW3l$2dSS%Az-rau4{93g z^09E_1|P%!09jVs7N@4B70k?2~ra#lz_*M0m1_v19By;7%LOyx7c1 zh_6;);Hv4QMO})S&>DppDYG{-dyIa-@Hk zXz?FV=yP=W#$Txo($hdf0y&7^?7&m(4cLj7lc<$><(VmWBs?GvU2=Q<@HYPd@gzGE z)HynBHQSk&)x+Yuh==}=aa9RWHYM5ZKM_@T#Wd`gb(3MjB(PN!(V(gt$pP&?%Qg zCl}ZTD#p?i*pN^eeoXgDMR?j=`?HFcEl|UFse`7GO5F3BONfy4=i-AGDPgTbhElZ# z(c)-Z%Hu1N0-Fg1O3_SC>22)PyJf>~2#>=QK_d8HYpsBzg>|RZnnMC**yevmgCS66 zVacZ-ZO0u^Xh+5IKuTN4S5yw9@I3tF%1HPdYkp*7!;*NfgIqf#W^a(jdkd8X;cVQv zULEdY<--?RgAOvON~3|HZb-PULbbs}@`bYfLtlRqHNmVqGhr0O&}q1Td2tS((kjk2 zeTOuf`_%b1*m<{Rx0&2z;_rVmPZS;63EX)-h@793-Y_#0fFz(GTwADB$xts%Na7}0 zmU3bC-Vj^FM&T8PFm~687e{aE`SN3YQw%r5ZM;IUnMka~?we6Ld0|P0K!L%zz3BCO z<5;#`jpwm!uz<_D&7whY5Vyi#HSU4v0=6ZnZ6nhy%`4Xvf|iJl!=!&5YFRMNXhwWy z0ka+@61cdsgH~8>k!Ih)wI8ggjJe17yra?avhTG2-BfNhMpfw>w3>>HN>C+SvxIW6Gq+Is~SP4q6J7C8sg>EmoEJ{+# z^kGQH%Z;(m$~y~l(lM~E=0vw3dn+Btn~NM&=`z)+ zQetV4d9v`A6a=El4PTA+GciZrO;UYx9Yn!(56Tv<$>dbR$why0p3!g^n3(ke4-iI` z%fAzylLb6VR@gQ>MB63_oS@fbTAGubZAXYuiKX_E6`LKwM_5|1@;B2{(1x|>!-|}7 zwq2aVlLz`d(2d)qUPyUq_r{J7F{Alee2#m!rq%eI_VZ-X7pJoam zIaQJ1OObN%4bWTA6bubm+%nq_Z2?ua@o@}7SL_kM7Wr{R4NS5ZIj^(AFsbZb;K4Lb zC&d(@So30oZd@e}274HCUe(P@UuFe{+e2xQ@3*V@z=3~KDc&1}2g{}or2`ST(>|}^ ziBb>brR@uopY)4#DOfPHMf)%{Gc4U(t6bA1ybR}Z#Xkvogipes3pSSwEAKTX9TkYWvSBzQP&1fcdz#+NZ*LCXm?DBp@g7}VFw2loxL1<U#6i3Zx6X zUSG8WtX)ETliIo}r4B@`JjaUfW+_!caHGd>7P@~$stQX75%b|F7O2jk?6Re=v@i@% zMeBu3Xb!;ye`+W(d%LY3=&79t9e}wmndkU^%uC5kAepk@WSjEVe8Os@Y@EW%#^5>ivloHY}=|t z24sJ5&#V6cT`>Y5N|y%@P{L*fIuf$}{wPZ*5j70hmE)HPlq`I995*f_p+GYeP-L38 zGT^uPpkaq90am=?#k6Q=JuktS!6_w3*~V4KAY-u{^u4FA4>5>WSjuu)6uNZrLlQ-( z!eU6C<>8G3Dn`so;onqU=i^?4_1N#rCr02ha@m4&1+YOiqH(a@%{`avmS+0x{5cF4cFrKFDyP#@T;4>~=r+g4Pk;85;D1hx?cHGwOu|n#BsVCg$Cd5lrkDQ)~f(vQH zs69}!gErDVqWEA~Tb@z3mk57FDFwFSU#P^X2P}?)h+ZfTfErXgE?>^ewSg&A$(CAL z7Q_mmp6k=Y5D8!|5fPSfYQb(yTtu2gW+}agEx3rHo!%%N$ST_%OZhBN;Nn&WOVTiC zK`%BOMfgfADuO15_J%D>nAkFI)D=!Ey_k7s9%V%iO|*HR}g=f!wp`Ll{QDs zw_)qTF{Mdbku?L!!uWhX)LhPhBIZae&3IBQgChd7PM8{yBTV=A_+l{!jJI-*d@v*) zvOUoVC`#=HAWXU(vPouFb!Ec|s1q9=vNv-s5#fR@RC>`DN^uWJ`%t-sBPi}%twCEu zgkfUG^0O9(SAV62P=kMlT`rX_7+l81vW{7pQZXM404r!5wnKbW_8Af{wOg+Y020T$ zQVHq9YI|ML3Q&yBT9){~&MH)<6~4OLU}{L67;<9$Q-xwgY#E&adT@v0g9gA>V7ae` zAsTiJraPg5qycfkE^!8LKvBVP;t(L^-2)N>Sjzgf_^$n^Ag+H5oDunyQz$GY!nc=; z`J@00V0c^7AyT2GeR<@($BXSmD0s>UA5Gli5R;K=77G#*?C&E$r0hvtUkLq}dLU}T z9v?m!a)#KpF_{t+Ed7`=DvcN3Ju#qk&cHowV61!L%5LJj-l!vipUMJ&HG~x64xc`#B@_sz z1g%b=5k!RT2p1`nSc!{yj%b=NRNPvv3ny0k`V2AR}?!)3JTM&0I*O73=6}TP)eK`%V zJ#lOu>4pvpEctZX z_=bN5Bo;ln63}^h#IP!$$(7~%{84Jd0px>iv1LzT0@{_{(D4*4n5GmXZzwAP3@B~o z-TY8>3D;oBo1M}HRFHuJUp*JY?D(LREkHM!PHW73Fi|@!WaPb^x=5n#1184q%ks{nQ^c}Vo(f-6iNnX7+K zBrgmNij*HP;FY{wHo**EV2rumunHcaBQF;+i7X8F7A_Ynw9*VSE%t36zA9B|I z5g2l~wf7LYml_vP!NUT`CwXa#K}77-6koekvp?$v6Qeha1{sUF^?0HEi$-Z+qT*&k zoGAvQSvD42hr5UTF<{oA1ckj+wf28ttQ7YQ+rmI7kZPhTFMVhdU24SCjZbk7J;#@d z3|z*=5h*Tk7Z)9jtx)N*NEgXK)pl8SqfiRwJj3{yxPqZ7(M)?nXqhPd z_uCsYCDcYZSYo*CNJ zYFV4?ZfJ-u@{H(>Q&wbEYN*aeuoU_=KBq2L&QWU2Oa;DKj&UEvE~?p1jmDcc#C7eb zw^13YjF?n_XBc1^w?fyNroT8>A3l?$$H~ak^8Wy#eZ>@fNln>Xhgd0&%}0Dr*Z4?~ z^#1^cG_Gy8NX%~1zwabH4>5m68KmvkDQUxzZlxgY_mXv@XOtf)GrlgKuENn)GTZD< z<0%@Z)u>gi$b`hbJX|*}E*!tk9Q?({(lT+gwN&QHOt_5u-JrO919uR?Hb5v@sV)*7 z?UuIij8iyik3?N|vhy=J8;*+orHh9xtv*9G>SX5^SH%z(T@R`*6vcl$Pq=ttVJa;3 z5?d+6M}+p5L7zg=nTE)}F@>`K0J=nFg^r`WYK?_IY;v<48>+Ou!b#F=-5g9U{{Yjn z{Y_5G>Zx;_V(BtS%#GA*j-y-R<2re`ZSa(C>?O^uO{fka!7{U7?;1ic%O#E!ucw}d zybZiZ1AQiVahf!n)pvh*S(VJphlZV7^~=;`wDD( z{804`t05bgyGWKR6@Fp2JG*`umM<+nAV{vcEwy2m0w4o~g1N6zK@FLpCmhfIUvx!b zY~Tsi9Xf{SfvA5sIJLGQAV`V674Y$J!~qlyy+sV(gVxO0t!kZC!xe#qsHZwE?}h{v zSzZK&&#ep zmGT*bhm(>0lJx%odGtqNK8<)!(dL5Ots+KTW;7#q-XecrX;-~t%W^ZeR_Z+`3-Q>n zc(3`FsmhmVJlBddyB&Cq5f%JRFGH3iG8?=j;wTNkZ0j9NYivi4CTnjHp9pwFN*8mR z$>uZk{6>@-EY>EU7FZLq?jrDtLL1z}i5c3a=fOBEDRQmLOAW?Cmyr*AT6=jN{3y7V zrpUwe7O8)1kBQ>S;(}hGZtOg)R^>4LHOaZh0k&J>A|i%DfgMLRZI^EoyFs5RX%`)r zrHLroIf*;(SXNF#CyA3cBmV%jPyw|S%}4P^J?6$*m5gb%X-q3~O#YEFKe9hK*~a0v zU)pcO#@VZJ4$-Q(l4Q54qivI}*E)$@){UOC+U zp%j1mM2Be^RhlVM5ndne;WSw38ouEuv&_(HQ&KLu+ZmV?u#YYkg)018sHQ?*pD#|q zwAsp=QZBhOHR`&!T1Dnao^Vo#g&`6+fGdv+s9WG{t+cwJY&eygrkuqzZD!V7;sP%& z@q;R7MOBOkQIVg@p5gXq1g~hiT7|+ic#?neO<{U(u5HPg=B4`|Sdgi2Rt~N50lmjn zC8SkloF^H&XhznZ9vwbVy(m>^H6A*}{{Y!4<8lkz-gvDsuvXGVjMI_fjab&@_h-96 zc1CfN0Wr8ObPuKpb9ENj_FSX%%GVIgKrz1KRA>roSdo~n18dR z#}jhpaD~?Zs>Cn3Uc?QY#Qy+lke7dGUYI^6gtuZ{Fbuy?`v#h()$shwb5kR3v|~Amu`Gp*Y+9lqJFDkb#bnB=(a(IJIqp!z!cSRr<#Wq~*x(xuWTcl_EJC zR-77y%+pO&KC}x)$4qT4dFKPDBDF-&>GDmyE}LS^$mR_?(A-g2lSFqXot%IA@X4CH zah~a5_kt$u_BUL6st!AZ7G^;B82Weff7lQ$0lS1!2dV?M{E0UhKuRnTIDzRpk&N25ZM|}cfsXFg_F|=1$Rz!w zD=p2-yyCtt$=ZA!ox~!GP_7(BW(BsESF{+rd6Bnm!R)HV)npqFWm$h>O{ayqId*0y zok9U;V2M*-bL|i4^&ZFX&JqO&YwHl+i|VSnU_*r zO0cSEYA|FgVK`1UY8*1*R`Q8bO9*go)nGza1mkIDuu&syG$G8P!=iQqVqZ89NNj&7+2%*PsxFdUM(#5e z*de>a@Wm3^7f*PbxP6#cB0j=0N{??}bYL;haY9|`0>#9V*gB-Ll~HfN_X&LCcM~VX zjM^^3UQyB1f!H9IdPNpi@rKOgF=J%r@3YN#U2h~T{qTv!uk4EMYHVGBr*ON(6HSpc zT`q|5m-#`aZ?}JuhbJ7?`&ijcFKReSYLiu!YHCtw6m2289O{kcb6*zJ`6cPa=rL9F zX~d3BOE&E08k{Sbd`9A9$PeA*uuL(hsk9BIi;^Vlba|F=7f1DUn(^dUE_x6@}Wwgu6SlEV!aK zxuF;+wpZM?4-eXltx2dBJT7J31Gp5;quqLR{4mjop*Lc-ye^!vlQr2`BcwQX+n7V(0&#_!ziWLZ0MoD;++Tw>+q#R3? z#y%Kggq^~RXzJ&2{9G_I8VTSlV{a1h!HQ)Fo(M##VL-?5^u^R$DCw824852gXkcc9 zc`sB`KEbHzk>klaf5QL}VHH>Y8({SS&@wk@WeJ(sLcuLPU@TUkz?G+@U5c0%+k40X zF=c-gl;H3E@eKkyDw<}TFWdUE`t)42$zrT2ek9<8x! zlxFn4@xfl9hSw zB_u5jk`*#aRCvsucKa|A!p8#79%xqhU<%X(7JHCl2T?^S(!YiV#Hgm)5e&!00ti8G zAkbnEUuXTOt-%4f9C`Hef{${w0xethh^tU8Vaa|B{4iq%#5ZcNaHJVbb>ySd;f6@;=0Jkl(otI509@WdOX8n`!IpHF}A#lnUT(!S*37&;Q-*lzFe!Bk&x>nqd4?|@;8 z>YhX9o;jj22C)2;KH!>e~lU3Bpm=E|@rtjH(!TfR^F= zP+$UI3ZVYf6iHfvHYdI`C`@Ov3?<48QhTu0DP@Vh1GekEqKDMBotP%k?r?u1`h<&n zv*GD3D1$LTwU-jt{S+)?pogVIt;_L407j?+mnT-=2*S4mT~<#NsJLP%JwL?+p1}Hh z@jNo4n6FGZL8Ap0M0HOTSTzjD+I>1e)OxVwM|c{D!urMsZeH%87`lp;77g*`)A2(@ zuuFYjXgb6wBW>>wFU1Q9S7v`oRaMKnpu2#|VF%Cg3<+jMu`Lsd*f?NpCEg<&ctnOQ z7GW)!U#u`#w&pG5K5($wbS!3y@m?YOFvy(%7q@Q@BK%)Cox-W1g>L&pA24Il25eNc zy432nC|$<_qCuaA6*E$q)b|U8mxcqeQ`BJ++mS!g#qLE+Xd?Q%SK5DyC{Vf8n7~Zz zJ5=`Yg+C3@WwQzr-Y$OEiXgKV+@3g{hBFve#82I?;)pCGXLYIVaYQ1h?U5$~_;_Ls zj^Ttj$Ki$yQwPF%t%Lsn6hNjnd9e~fROv6_f>OnRjk$gpYQ})5IflqCOKpZD4yp_u zT+cP``2A!@>*VKZ!5k#bnM?o~{07c8I{DM2qhLJ&Yo z4IFs5mIk80lh&?7#ROXd?F6Ng!wF#{FhXmeh9N5_ZQp2qD1l7qBQ9>v<>e4u_8m*PoC^zk5;8L!Vvs;wnWs5# zA>#g!N=0Gz^Qn9>u^2f}a}TF~Jt2xWdm79x@ctiW1h`R?&+r$u@WTdFk1yTT8UlwJ zy)Y_y4Y|Vr8ND!#n5qmxvD{%e@TQRky@;xl*K+X07Lb2ds}qDTl8g;ak?ck}HFe2% z{LxaOs|s!FLo2?`&^m$z2Q3WKlubbg4Y(o}_e`7H~BIkw(tUYm(d{-2|&)aT*X)77w)*>5kNiaqZ$~(ks;$K|q}?=Pr75!!-m} zGBX5RyfHNctR=@4;Lq^Hj=&%?&g|mGn6}*)r>1`)1&yk9+o#143GQH7-fFx)C^(&z zC}sCxBjJHylu))jmMDxB#`i7!KeZ4QV*=S0kjsT&;swch^xbR?f*s3^?g*vdvl7G~ z%80vef6~|xW(Tc^ng0OA39v!Mq%I-i8^3g2z@FiSzNJsa0dNt8?)p)Bq6}esyS2Bb z0fm3q@eW$2hA1nUMo3R2;!O(ZS)9i%ULKe@>{NCNJYNd?F=K8Z+qYY_7=^^>^|tG6 z(OZGDAV_@T#Z2r$dc(f?1NsIiW;ytkccz>9Uy~N2e~eHTLm&x znC97V@fY}^broQn%e1sQd_HhR)JX#0nsATVh#sWLi3cv9z6nNjnQgxh2TjmL0dSCj zRbm5CyN%wtsKuy5M;zd{sbRG<#4yyYZM=|rqNX%x0^p$NoG{fEtTLP7F+}~SaW8+d zv(F8rsytpOI@nq&jNP=;J7A#=08p*v^M^!ef>eWV9l4bwBq%MwJJ{iulKWSR-k+2-JwV`QH^G&<#0GzRixRrk zLh9m(P_neGRpwkp2VsYoiOt2$Lg0Tu!ryP-22v%TterThVUPstB+WKdB z<1b9=r9WjDPI^j2XDYP1Xv@rPMw!D%)i$sV7!tuERMm zX$H3nq;#`n#qd)S@@`NtY~!?iZ7b9!+3=OhCGUcX{!x}OC62+^9dmzsGgPOI)>I_i z5+7-Djz(oVn5hRX?Kz{vxU__M??`_oj+Ys_L^^eJnU$;+gJBq-wFe3pVWuFOIfmO+ z{7^~U7h}}&x>!@g7Vs;%FCtx{LB{+rr(u|rY6+6Ja^P^d+oNyi32u>dhiZ41)(2SG zMhUN>ddYBYqc3`Bzu|w5Rw&kYK-o8mSlV*0c9}6R_k(m|Xww?k-AXRdDeB`cApNM> zaG7?cuVJ+?uI%vTP_8LbCKvigC!Vy-tRDrohd+HHTQO%6Y-Xr7nH<9ohr^nrZN0|k z_EYoyiKWhyS(=tHwB_9Vl~m1Ej^siF96|7(u|fcO>F~D_ijn2W`r|Q3%R9W$SeD6fi1=Wz62{ zxV$j3RCg}ByRzh5IYcCMj$3gQGlmkO+a(ps7FOglXLg-m3_76gWH6APw)DXlF;P~N zVV6&a2BtUn49|ZTNe|l62xXKvPsPOy28{+=aGX5XozQ!LM7ZXkY9#g^y(3q-+?M6+ z-4?#!H0%Kq6{;f#5usp6wa>~dt5n27^{`|&ad^M15|6DXy80O5mo7E9q=FFT4VtjN>lW+$fJG!SS8;`@K$IWnSo0h=uj>GVVTHCUyb zt{}$Z)Wp+#ZAtY5OT*1O*Wb!K?=$jD{s&Ks_^i2lxjR+<~WkSiHsw&*}PEjxb5uAd|gEut~ZawY>m`y%ol@Ok+xDr zkr43}ykDFeq1cRTi^SP9S}Sy!YXo@h;x80Rir&XF)dp#D4nFYsTs`$=Iy`&^q_0)3Hwk&TR}p(_9j=r*F&ZtK71__%kBwGsztP(-R0KRk zzSMF#{O?csN@e77J|_C5;F_G&sJO(7icJVsO^%pvG($mw9 z4BKZemfA&a+Qts%h5ezN!?JW)&e7VMnrMHxZ5|R7gM^_-q|HIVn3mi$K=ikSwX~DE zE!aQlDs@U(VR@mO0@3XKSB5idK_nB+p_cHBN7{{XybRw`p-Rm9HL7~Hhu zvU6fv(kps_IgDIR{7P0*9&yKJo+lYC<5sQ0JYSq=%Hv^_eTA_gU)kSo(-HP~pjCg1 zY5=TZ_puFO%+ua+xuLn{&eAAM#YZk(`5PXYji#k3 zvi92LrkqAyBA!vXGW3dd6E$A?^~af*oI6CXXa|T){&7n87TBH-RjL7pXswjE%sAl% zubB@@`@PXq6&c)w#1z?VOwEAx$|8S+SB;`NLb9&Nt)ZSI`_M+5|pHn!By(=&qmx=c!2ZS69_?OLjDS)3QpL5+Bq`YaV{Eth4stn{RnE@8K9mpd&sty{DI06gK8(MXH& zDHOa~E!GKMF3^qzj)8QNBI$nv6S30DPf-Z!l*y$Th9$wbP`zndA`9mXXxU7=4LJQa zhfi{#Q-I?w@SfwSzs(}OFdL+RnwN7HRHf$`9o&S_p|nH7KX!0lhE@efPyBAVMtCmcXeKTp^W}fv9d1AH?i5!>vs@oEK}DLXPcYdih&0~pe>IMZe+gyzHB68Bj33l%{B88Y(`T7+ zs#XaXg~GUj3aw@Sy9cW7ZWNDWk&6?LYy z=6Q1a(lJFa(4p??~}~lKfVF z8&#C+;N<$A1YRG0a+4rUPRZQ#sV}V|v|mMzI{Wz=`-YsY%24H-eYvA}p6LK);g(+| zST?K?RD$~H`I+|x96i})MRY1Mbq4<7ovlj@(I5#j_Um-(q+frn`}?9xkq~7giw<6V zNggtGRFnw3Ilx2Idcw&)T*TEH_U?3^C%41IMbl@x42JOyAowHuPykqQukmle@r zT7m(ZZqOu1DwiF`gx*1#!J8wv-RpToBQVj?vynvXG87%ReW;zwu+iq!k0gam)Egn@ zS>5yMC|@FWWXmB@e4`CSD6uQFiVo$Y24S;p{?zFGfsKEdFoWE*?(B|%EN9%jq82V7 z5gP$ek!GPRO+MJT*>wqV3@y`Pn#Ue&T-paFX?#KAW7+valGx1ouwTT{+gRBpD`^;F za*bM%2c@O~K)}Z07;}Fxe(5|8A(~Dd=Hs6}VvqYrB_NfaW3dX)4DUtS0GBhD^r3Dfl*zlJbWy2LfmG&0Y z>P|%wRAexlO7cbgFwW%(CU=&qaKJ}N0L-)O^ud3_17V3WE*`L92a}h(NQ50W$tAvD z!vf2)1>J9tHG(R82}yPT07KOTqipAiyE*WTN`U!TZ@IMe_M+-5xR(b%zA1&vfV7)Z zRra7)+#3omS*H>~_8N(x4-LGp;)+n6hL3)`JD`m5?gX4i6Dwj8qD8{wzRWP7tT}hX z3m|`{<^-JhV5mAVgl)1D*OjppQ$~XdCFF|&6Lf`S1cP66696oM7wtreAV$2GrVbdp z3u}j$f3*b@vNY@~ejRO#BF=*ER^ET|!Pt_+hRejG6v3+mZ;k}EsAO+2{{RmUbU{Og z+srafA^4#zLCmXUL7&P5gO*#jUN2_YgdBh6cf)AH?m(i!I3MAHuqFUT`xzA>fy_J{TpjT7YWLdO=Te#1y!ds<(f` z5makt zq2#li8t~m2odK91NxV0TDMd_Fq*36IxDc9#EQ;MAi-tXQsgnNyq<~c-)NJtilwC$? zMvid(n3M{GNy1kkg)qu!F4o(jfRS@-+@y>D0K*JHO*$AkFKjVbXcBJI_@Xldy9;a= zx*<@5*>*)WxBmca12F2K>9*4Gx5IxKiyzCeZi+q_SqD<=t70&)SpvB)cBA3SAR>f& z)+vZAS?c>R0Wh}ZZt1!pA%(Ob5B;EZ_AaIcyu&X2VyIgPb%DD$Fk9Rf;9S*t!vUZt z1hXamn1KzPWP?SEiP2NoV>sUyXfS1s2vX=_QP^!To5Q!_g0NK^I2O2BK`(!}VlB?> zhSGvzVOwlQ+`7Mp2HGt{Ha771y_i_Zor57RcJ)Cpnd}J1j^+KBwJb}(E&!*C#SBsn zUM=D1JuvkKz>hofQHH5qMhLFgtNh}|LYYPBNH{?+Xz?)E?;govr6s;Yw&ljcQuhpJ z8>kTTC4Uzcz9@1rH2?@li93Ixon?w9?$uyOCv$P|#4R5})w^31@BaV{RZC@ITb>Nl z_@XySRXw;t43^yCZ46L?mrf=2e~KhHO8ODIvfS+ZF!gN)-9m+)KAs_gxKOJC*k{)Y zFHA5OpQ!R(vA-mRvVxAmTWm?;yO-IBSVCvk4Zb0|DOr?QQD)y147q;-TV=U%0T1GX zA%`159w><<65xqjXNRWPDv<%ampoo@K(KkJQkC&d1a4YJ@?qXa+q(Y`PEPa%Ou&IG;rNOUI z^+j$$Z(yR{OPch>6wQBOB0Is;#RFk5r<>1`^NL~d78C<%=Wh{35FoIZ=iL_*plWA! zoh!xS9v+w|V&xAbX`V^b{US<&h%8$c^iOx?0IFR>GTpZ&*2Ckl-9Y8HEen0DqQX#4 zWa{Rf+%W?d+hxspLldN^or)2)GP{N}FSzuDkZ(P~l6yb8E~J0USRR4Vlp`U;@gxdJI*H9J4K$W$gI8F-k$x z4Cktk5b(vZ1vuS-niL5FH!7sL+Yp1+WR*im8jKz@F1#p(!d=j|7V@)J6DWs#QX?R-{&>31b?Q9{5peqHeVOWpKdQhEc(2$KvXM)D+RM z`|`5y7m6hI1&}juCkn3(&;vChx7ON$Ww22IF`fOVluCpp$E^&!Ft!1KIAZc-rFEkc z#I1(!db87}2&q6NotfKj`N69bO9B9kmw!}Ez_7Pkh5mmis+7tbeQ0O?vx|VK)qM{;z< zSZSZ)FAuep4&Z?a;vIUTD-*a)_0DDTiy-b*+cbpKfp>yIltnJWJ5b&!;r{>>6DdX< znzuyhh^j$txUA|W3CKXjIhWdi)D<(M-M52YsG5I)dw>o(cB#UhR>NWx9Y~Gk$J65K zfpsWC7210+AiCJ_@ai!Dh|s;@-xR885##RopsFcZg(+_XJEDWIB7B?hz{CSUMCI2( z5Wtr4a!xCHqM3#sw$tjlE{PqCs=E=wfssM67+tw=t@e}%syN@_vVI{-8VP!{S&w8_nS&?spB)Z~=K+VgkYY<#Tf-*h!*~%cn z+hfO){g|d;Y(1CuSN36VAWIMB+qXpNh0N#{3f;kSueAVDE`xxlvK=u4xK_lRg6IXD zP|Nq*!*mE%u(4;1Vid+o+cfIGl5mK^NOfZ782l)d_Uz9rB_zj6ja4X-s0PO)#>VhWw5A?4q1Egf7SwEwpM|6-KBrp ze-ufA9zyT^s1of_1UEOYSNLM%xN|lTBkM9=eo$dV9unQ#lvMTtSXS*D=i(RuSE$Ii zCK(h$Kd_sw)A2(J09iO0TM*C;!?|LjDpYnEIgtlVu+qStyu0Gq8w}Tv{D50EQS1$BdQSisXnAw!^mOyhX%NRtPJ35L|?O zJ{V%TOu=`Y87J{Tm1DxWnIwNfY?~70!dD>OqytXID`*(aq`why3@4ErP%UAmJePUw z88I;{5xjzvNO=leRd|S_P$BAtl!i(^E3SjYExpfDTvTLHIH9gZVpf>-ujHOCs6(#C zFR)3EQVu7wS3>$qe`B(}7jbEAJJD*PEJN$5FE0;>bJzLZ9DaXds&duF@w)Lcn}24t z53TTgi>g-;f(d-m5zAWSlJGXv9x#%+?}%0l6KzMYu@&g;K9tN=TBfFe@Pxn*%SDWz zlE=Jo;PKg0J$}T0rL`ogm=1Zaq#X3{!Eq;QKQ#PL&nKmp6N*#IdGM{!u!n5Sh^BE+q#QEvsCzJV2EkdF zx1ZtrqEtisjpe;T=4p!t~I~ zY?mVHh85VsH=WrJ6f6f}JDZODv0q2rL$^Fa+?5hkh%Z5YV8wS$ zC;9xNCntZDxV*rdWNPB_K92e?`XOPDM;aiJGSu~8g$enGM&i%E{?X>SJf_OWq5Oa0 z{VuG%Okc6K)KQz6lPhiEyTuvOH`Zz7=ch^<_Lo`=QHq<87dMZW8QbAVH}>iho&u483(t|G&N_E^Oz z1^MwAhwkVh;TQ&8j^c1B{i(3%bd#w}i%kuo{34Js?c#K$^k1m-wy4s=05+G1y{KV| zWOFe%66}$g18s<@5f(#gBu4is4t?9E+Lsqhl{TF5jWv?vA>k+0Jo4^^Jm}5I&0du? zuvvd*k(rEIf}gbgqccR7DLH2cw75K0_+z8P;oYppyf}?hH&#ZOy?V&5x;u2QLD9n+ z2ZAWBW9(2Yuj(7f-M7oxj6BDtBJ<{|ow2c9lu6TY^sMX5{;*SY1=1-VVPT)CjCgVH zsf~>>QW|8o@Z8ARtR=;c?vxO#-!kyhzJeDIrsoqL8!id z=%jIO^&HPr=1N1hu;%UN$wP>Eg?sZzjbQeF!z?*Neb9K_o_1VbD~wof;dOoSc+eX; zWUFDx23?gUm?qwFi%QXgoMobj!Lsx8`I2P&WW=@0>q|R@AaO$$@1b)N64cV1LLno+ zgXQN3rGh1f6-jTcsN~2uYgR88w7t-WPLY-?)LChXsbNP%<>y<-1p0?QP&m_SMm98m ztBWH-W>RJC6UHDily~-esqRNx@ryi3aZ_iRrm@`joj^eEu4o5p#4b zmqmqe>*pEj(8E1n+FbCy8JM0jnRaQZiE$L-9uY6THqpLDdpN8?R4RH4a&m1EmkIcU zQGBzE?<*c5s%VU+N~`r*hUcC)8F&zXga?F0V$y6f%uYzr<(QQx2KRzrsC+QkK}})A zjP)|HIl5)BlXlb_1B+h#{qa;^h-ygCWu+=IVkki{OCn>4^T3l;CJ9dMLN z!BVwz?pWQ%{uAW4_Pr`#&G}pr@+kPT=|96XmmC@;4r*u%gn{?HS$zM^%Oqp_JePR``pQtuAebA@;gM4wz1rVmdp#2d$R<8qVUB{SSj@z6j)RAvUe)U91Q=FTync@zvsUnCIsVQORHS99M1~AaHf?T;EmFcn%kGYk=Cdw- zF@K8WQ!RXtptH@K#Wr4OClA+f+&osSe8VcS>aX<`OQFn70XHs9OgpkL zU$6tpCKs8L-ON2ND>zO5wTio07QmqVs%Z{m&1HvMyqC}#| z*2F;-4qBUDm}MkdaW_iuXCk+Lp3+4^C<^)?V3|pJk+~r-ElQK0sBgkL^3E+2q$g1o zNf5(zjO5!fCQpb<^=|x75zxh~{kn^lHS4=9(_gN+I7GNb7B?ex33$=G@VOt zD~da~XZc5;<;K{5l1Br&*lRJE$q%c$x(dZGtk8Mi6c*(0x0!3h@j<7!3=y`}12uzB z+)9LGgu0-C6!3_LR0Tq?0&jR1Cx#bN;eb5f220fqs7N$%yN(T$W{8~)UT=@06cjMo z=3-Yt$59(N@iKX&1HOdjeF7PSEn_1#%y(%NC3FKR1@A3?0Vrfnl-ve>#&@JYEL{K!C@keWZtL)LR^ksjnqIL3&?$Ce?i! z@#Mn~GDS&$m^JLL`@NO&RE?ZIOMhjQ%^9iE#+cmNc_eleeS`~ST>bn$%rw2grhu1K z{4onMTp&6B0P#S?g2Rb&r1y3)6Z302EzDR959L z9O9$7T9F&C@WGUb6|uMlJ*a~PTqc?ITLWQjK@JUnw|hM?bJ#2FNio4}O~7!Ye#>#> zog`4whcfbO-DeC%V&Q`~FW$FBUPipw@i*%-ixMnl#6loTX%J0`9-bJuofPSyB*?dN zaKTuh4hCn_Z~4WFl?LE({83dDs35phbK>O%JCgoLP5>^ubSIY9oYo zU$p~&xfsCS5-$%FP_;dEpeT?^TDFX`;Wl}%KNJx?LEAk7s{y$x!2nB%d!p2gIAnyt zll)O`z%XXV)#cRyN{ekR=j_7^p20G;b%+u`ro|WcbwQ~?1Z{JQEP)w$Nb)E-Qq*!f z^W}F`XLSvqV~KWzk2_+nEO#$2#yJng?Ln)5SX(I|JStg2*#@mbj~9oF#RE`XLbY#y zwG_pKA|6l-Lnu4~g~j54Aa+|z^Ij4tt-%Af-*WL3Qx&)jEfI8%u$EG812)~=Llt4z zjJYqSf3*W^6yRK!5_So?MS&@PD|$v8g>D3HdVC`ja#`k?7TzqluJJ*ZsiOqd_a(4@ z3dI;5d4Cibsbf7ymw!whLx~pIA)unfU~V5l)gS>^*m)wmpG+yxZlHcH2%w^Us0l#& z19r=`SW_S(4bO@q!wjm8o|r-Z0A28P1zSUR91=Z0W(p8zs5Y45<-(t9stTgnlx*@3 zHQ|F*BMlvC3svGMxPe-ci_-heQ^nJN1QwtO%8=>d_@XkIooXSmA$W5bhJ=cb<+JdQ zR0AkO>3MrjU!BnOh%r?E~uCZm2MOlD7g|YVVvf<%Diuk6Z1!=q2k0!N-I$wgIsX zh|rgoEc|2=yY54bXA>fDyV<;^X3jBYOgv9z8E1A8Hl~fh_GDzls=Mplaj%cCCU%;fqxU zdx#g1dGx_5Y@n>Zjh;w@y)Wdl_9e`W)4 zDV%cKH$to;;Y37uVli#hB%IyooJkc@*02uJBiD-PF2fWnY~9oB+XFMQr?^BLdGSRG zcF?hJ$WHIFyfDH;ibiexIYb*B#FAaiV$n)6C9a+rF@_PsQ`47!RlOw)gczzGTv5&? zikZ;@i1}*_g&=}?1mV!5V#)$x!Q`GFXyG%$jtq{{Rmcd|g4*8E#j3aQL8uVdFAsxysvZ>49S6 zc4VbckK%$Xa5AWWN9}Py)Emh4nPF|cBA7Ux4jE*Ng$S*I+!n+3*|$aEC_oV!9JeoT z3UcWph%J;R=eh!=m=PTH;V6{`V8e-Vm-u5lri;`AAw;{`>VQ-!k1HZUbO}@3R~n|J zlv635!gz@NPl2G&G!ZfL0*-kn2knAEjh^R)>yI?*f?og>GCZOg@NmIYCShPIPIUD^FtKPNJounS>OwZbAoW}S z02Ef>Q-@@IUYxy{sF2b26_$60l3DFR2Vkv#i6G~1;fN_*buP`vmAo*P43K59b5$NF z5n$QcJDYCtDPY>dV1jdhO;pV<57=qT&dh49Kq(`bG$a|B@*eYoi zxiiBA6t_|H9ZEgPm3{iT4l*869@k0uz z+F*zA6k3wQSKJ`@p|J%=ajd~_F3i6a5px4)kAfbvv2__eri*_U6jesYVY7~xGW<~0 zQpSY5WUCB_(F7L{II;u$Cj&JQw7Os{2c&t?Uasht3ZQqz!<1=Wp_`~p@e*a>h0qyv z5aWb4qFr<}O^-P-sG{+IoI#3z2_DEZP7yEKhN9?Vm?twnt|)mKwkSym^YIiheZ_i? z9g^eBd_U3&LiDyhl`Bsrf*7hf7dmb)7xRb{25f|X6hIhTZf}eHQ2|yC*q{LC5MY8CXjFNPDE06>*FjpZCbJzo+Kke5oQFW(+{Us-HDuVqu};brl6Q^6BLs%Z<#tD%PT178Gl<#~P??%B?Et7ig85Y4*Ox zM2q|5sOz;Cg*HbwHQ44w(BdH^JB!@8{{X`YZxT@N-I45P31h4=OU071zf_hakVjvd z)IswE=kr+M;m?*iHL=ov{T>XMEQ%i)U>pr6gB+5ZuQcOrnmQr8;?LR6J&qhX>0^nP zjmSz_*_5wuI; zjm!IlB7H{>Og%8pVZ@MX6$ex@A~9Z@yfGiSoRxZLM8}5JiCYbi5&n}BL|yjX7@b2@ z={g(e)Wuz+6k?1tW$Nk@d(j_yCSUVLAIc~4ul17ne%XJh=q-voQnGak10%XdVMIsV zu&!j@70JhqGbw0)#Vo6m*LT7&HjO{jYupu9{?Bc)o~6Rv(77&Oi&Xva?n+++2$`cR zF|an-LrRqbdB69KTtCV={3hDe)ybVTkAZ(mwgdjm?YFlhc9ig2@Z2jK=c#OBcyCt6 zD6a|RokjSdRBR^14U5nJ0BFUCshh9065EHx7c;PoiKGV}3YgeQcew3NMbq%Z zL8#%fw6f|S=@2N54p}YZgo)wl`9P?)QOjj_BI$x}+*nr0ZplXOAL5JLiygM@#yR+W zQ9`e=%P$FBzAv-G5Wi1jse4g4xsFkDHUcp)d3U#*;)9rBz_=-*4a00Ok^6@XxoBI{ z4T#w$R9Z}bAugc?2dVWJau{jMVw5(YxSO)LyT`)?xfG3~+s;33laNEda1Qrhx2mc#yzy{B!@a_p@wsyu|oR=+u!I!W$r zKdQ$ollhETyh(?R$4A2CsXe;E(fOthv9I?6WLWv`xb%Krdw zWuFy0Df~Ujt(zzP4I#5Xqa$g1NXI`_t8!HN0V`dfyLveuLqdz z=_>yKnbY!r{GX~s<{Qkh)lh1rKL+nVmQl=q&T?bG>M!u#Sx&6q^fe7@=x@Z^HplX^ zm1^XFtwM(3DXX9;E|MbjuXJq1`Mj_3sGPrq=$JUU@=!@{!VZzaNQU=m~Z+4S7c_=)Bl) z%Rw*DHvO1Hp%n*|`C^=STMi;}3@MtM4-V1-k0E8Fd2ta%RSayy;(S{IA}c|@A~NX_ ziwCYv1-Fg5E+BczMJ3B*+a(v?61RT7%6p-Ca_G&-&90MEw(|G#j57U3I8BvJrfpJx zTL{VacAOU{TvD5-xhMZ!M}v z?ETPlAu4E|*>2H4+3iS;**)~Uz@U*25f9xMIaqyP+6(d7{7$0TKc)3PrATf~j?XY{ z@PHXBZV%lYoV;eo@et6+P2vrrPvWkB6LWUYuskCioZNvkk)(?NzB%920`(7RwAZQ= zBGWCKhEWxMp|Tf>KFUC)gyx7 zP?@JZP6LD??D%5wa#=h}>>a#cEmH>qWPNcD40P~iT!md^)%f|qrQ#Od{^sj{l%q!_ zG^4oBrXS(C>B*_VAty#v=FotPrzqfAqonSm7uoz1^~8jn@Uqm@pFweSoKxXwEnTGa z#X6Ww5Io2c%b4|W*h%FI(u`zKl2w4Iozk-w+tgp@293WG{5%IU5`jiJInK|<@u-X8 z!^EBNTF|L~u^}!&QA;?SJC}NYZt)2s#a6*wrae}6lZafW2KMW(6AmHCSEoo@wLC^I zQ8!etc6l;&*>jjN%GU>q&zuFAF5V$z_KiO~A(FW6<-;5A8%I9qZ>mXAh9v{U0i3Ib z;asg9z7qccI9_jZGEkC{lQ~)oY#qdeLLKBo?}sP$T|hGx`N?@~3q3G@{X%K?B}nbK zM|ooM%P2;u-Aq$67WrIBO{q~NU6q(QhJ+AC@!}NnhC1)4u{hR{?EHkfuq+F$^pSA6 zVQbwLSMnsyh-@zRVX8dB%Y?(tw1i;q5SFo43MF8+(&|#ObkY{3+LdsfwBtdj>>q6VL;_ki&F3Xq zEd6yw&BI~uT=kBBCU4@d9C+=&^a9Dm7v zSDVv`YySW!G(QIxKBi&j5%gE|c%_|`%o2F9_Q^;pHF>9hEX&T!(HHNJPr?2k=08^_ zB>w=B%H(ozM@}xH_eQNyc5^4$sB(C74Y0^4fJsTVMnAjy$9oUqxY+!b-}5kfxnK5z zbJCG$zpup8MQ=^5rKG=fbe9$g_`#W%ERa2(DU=4&?eu(owhKg#MQk_?r=Sy++By zJ4)Hc(B@^yCna~fal0;;iu<$HG3WeBzw)XU6w`E@E{lTLnv*7I^U|{t!dDHBCEt?w z$CKy(01171PR<|AGIY04>UvIQgDbh6m4DTb2?*MMS9|@@ekX!@qFWvmd8aZK~iR?<|{0}FxWXX;U@H&&ge=z4@Nc} zQbJ!50Fen3(&KLM#YDs95Ax~|)+@Cr#O&vw6w0Qx= zQY7<#t~w4n+pc66ctuzFKs+>tj#}`&Dr$plyuoh^bc6-rCHccqnFTX!-di!PJ5$5W zqoc+mf=&A|=yRXeKM@wOy9{)OrGt6yhn8ngbV3+2vLOs>&LIq$cSIqJ)*%EJ4yh2PDtk}ayI9hH zK%QdOY7CBGe%HDX%SN(^&4pa#&q?n`+GgA^O+trSlM$JIhyF>yN}?Y2odj9CH@@M*;X3LTK}vub6*)BEAJ1!H3SKR*?Jyf7iR zum@V>B}ll9ie?+Q!t?fE#q|Ov)NSF2PQ!8=Lq7~t6pS}>ho{96N$LzI94}}(qQrsT zWSNNF5UoI)2R!ZY#0tRP&9Ed-!chxh4UNZ|@d;-IVCFPrM|gh^^ox$dC!}~GY6?Rt zR^%wJHHk?HSR-PVbn!sYYWs(OZBs6<5`c1Uu;`m_%9?P|^W5sXr#0Fq+HxE94(kjKr zVZe@U>CP6xs;Q6?;^7zoLbhE=cNoJ68MgDci(eEbYF$S|ZJ)yeN{beM@;wK`7d^KZYzL4T>+d5RGwST_Rt!Ff3Q~4rN|T z;6hO2#jnK>lA*=#I2x3H2Ql1ht?GjpTW+Pw>HMMk4{?9WCjBBdC#Pw zph@2Y7>b5z0ONMri3UT&)uFG37+f$(aMVz`Vwg9P(;`S83?^7KkwRn1M6z-X8(=u5 z+_uq(S~yEcL{WO8ZDh7>ifzr^sRqSDFsBJiRE5lJk1PzEArW4vks21D+#7k{1rf4K z0CPRzA$VYa$^hY9c`t?_Oye@ul12P5aoAl%IgpsG--pEwxmuAOOKrMc6v3LZUn0`o zZT->m!?Rk2WxI5`;e|PN9V07$gD78da{|PKKyv2;QGS?`Zsp6kv;5$pg6v`5fy>|BD5`+Qcb47ZgH9nhN%WK68p!qA&XK0>~|kcutXNjt*4ri z{{YK>5P@y{F$gJ^*YPYti;H8~Q33t&1|hz}S1&;S{TEY9!CiUo^d4hmRHZOZ~i1Vg-Bx?;5g-Gy|9qiVvPK(XOe;YImE zC$MweLx*mu5`|D3&83<}L<^%pF}b}GDtnQCnDG_7`it3ssIx7+LJ=6aW6miE4nqPM z7YJ_@Uax8n6jXLI)PtW?SXMo1*h%{_3Nk#i@q1ASC`(eypN1g>Iz&fuekeHXQo|Tm zxH3;=O6a|8DN_y|r||y(6kI7rMd4QS@3}u_3L`+M;$CZa_`E*MG7yy?H8_Ih%k1!f z!Nd{$hqCjR@h{qjCvzz1UqdoP;<#f>b{Mar6D*+oR>h8q8aO1J_(!SNd$;&)K#iE?4JOTs+Xa zU?qp)osI1g42Q+V3%fwm4U+KtB?dr$$(7>1%nZo|?>B*xj1fx0o0t$h*TwxHS$0@L zcK-nQeo+;44MdBb-qAq>WL&h#dSc~BDO^j*dU#=MgcL5LjgoOd)}YLbV0g9S@kKid z^?tz!_nIxAh7uD-!?MzDbLvQidSdrO{+14BMJfwri;p7qVOyze7k8I2;p+Q;Fjp-Q z8{(p4mrPtS9meD%f5isRu~ujROq++A@WfwX(K`p(#k0%B?E4Tl3Lx;yiZ6}`t;j~@ z*1<*npL7p$qAU=G>ryIIrj3{;_i3jW`NXIZ3?jjELH;PZk(5D8kcYkwBV?Bp68Bo8 zWxo_gBB*W022ctdsAk&{(kY353=^}adBD$Lqqu&~ORb^P;)@1~DX_V6Q3x866<|2G z$C?e%EDJbx+3AU3Vl*5_r{Uo*Y64VaPKUDX@M#N}*`6lDnO_f|wF+j~Z2Jqs(dFNF zR1gu^bRK;p127duxI7*Xn7Hf?punOCT^2GYXN|q#=CKM}NS)enqC_o!rK89hj~&Uw z;o*W#>B2UwFs(p;#p5m|LH_`lVf%u{ zjuLZ&QUQ3NhzN%M73qjTL_}H-5X2xLyTklZ1heR0R=06P30TKT=i-Q4Ii20QuZt*z z+=?98S2XL0IwwSoppAmQABqu@L%zo?ig2nzf?-yr z+Vy_aH9dg{*zWazULW#-nb5d~5b*G3g%+$Z3J&Bo7}B9K>_ItihgdQy=pOk*IE#kp zgs75pkK%$*fjKfH1SniYP->(+*%1E#t6}P(M{#Yv)0O%BPp$;kt9)S8uZc17kor{5h57K}HX0+x7Dcw(7$7AV=!8$I;4N)EV?S(Y~eUZjAo`YPLBd z{{T_S+`$)%Smt7#Fv-i);&OEz+gN#|{{ZIZpg#gkfhCq_B#6XQV0sy2LBQUUbQzQ33FBEaPYGU`e(2B5 z#p+^YQY~XTouf&4C_8hjNv8-;4)lGPp({HyJ|`55Q>h@)QQUS;2jy>WX%Rin1>aRbdL{mPl7aX7xsQ&vXW*!a=(+U&ff zw{;M2)=JDM-R>ytBR8c}9%$cD^HI(xwd(N3d}86j2=V3qC~-F;iNX`jID9b+TKt7J zw{x~GCqT6Y4WGpqU=%xLz0<@a)dW!Ky!$C-es+~ zMZ*f9d3l8c*X?yfL%8*q6RWBdQ0zElt+&`HAcJp^t`Q-Ei8HuqcdolJ2s@H7>@C6p zmsDC!fo0ILbC22hVZ>`m;&Xt!;tF4HAwXGtCGA02(3Krw`W((6sgra8Q!*Lfm`_iE_j^tI~R;+~oUZNudu! zux2FcFPvz}n-)Lpm#DzAmy`Q({{Ys1O|gRTEWR6%ZeCTs5gwak1m%2&)xx;?Kkd=^O{DPY(+N;rN#*)rXDt6Pq`u{a@T@yhUD{tGrLly}o$5i`k7VAtk4&jeoQ( zOys*F$Ps$Zc8nS^m!HUJ#Z@&f(y@WzdPT+R+?e4JUpUdni?`V3Vd6GUrZs+l6T|cS zP{wS?vFjh2D~PS>P8`-c89$V#SuuQ0&)Mz|mB4szUYjU3gCdu3ryBQ^dF;6^*E%VS zT_u4Fa>*GG2oF`oY%06)8mv*amFXphc6Qr>Q;kneH~rHDhp)aLkA}`NT~OE_i#2Md zKEg$cYHWm@yzyHhDn1s|#=iA`g=SS#p7~k&jhk1jOJVr}o1d5&RvYmj2^Y#25wL%8 z&pT@LgnJx5QWRn-dTV?n?bf*e0R15uxgMF9CoBH|v};xek0`*;vmaEIO{Z-^oDU{U zv|Gu?1w;UHhw*sLm96nN@vz^ijTcs|Jy@M5UvS)Y!n=w+Dng>sPNlVf(h1VEGKkKh zLmq9!EeL}#7#U#Mf->d2Noho_f<_BWWy$)ixJr$)glHf^)tba_Wfhs!>&zujd73o3 zJ^IlE68P6B@m#9QbR5P)%ZBjl^|`@qq0_{E%0^UAQHn&_voPKnmc!E7G$VO+7m7FI z!mw#o(AOGb;H1fuEII3coD~wc!jM@qXqGkmj&Bb#j&sd{5b}$mlqzFEsa~&1J1;Eb z&Gm^PLE(Kc1zR0Rjc$V5h9X0tko!on=@PEJMPpsDD}|)0GO`kTvS_LD;-X2^=h^v3 zp5#j-RP=cKOW{kmRh63vQE#tr@M$jv5Gw4-Q{HI5fi0M{%`t5CuMPX!8>WE=iES0$d_VnCFxh z`l`XIeTgZ0`xt^|r1Z7IH(1bs@ag9U$-bonu^CGW)h>hDUZ=G%GUlQVhf}$)^o6AT zl!(d@3U;wd5FgQhP>YN-w3pu(UHFF&^%~Aq9d=u=NfphuLwKJL8h4~6m?d3{rp`S{ zs*sal$~b$|5groGFBd^g)L1HAxxjFR&dLrA%@A~#${f$9=mw=Ka-q7c6uXkH%{D@W z>AA!=`{7xA{DO$tFC&SjCFC2EYjDi1*Km!coD5ADq$^l|xnR1JHRfIk%O*G!2a3|q zxU+|1mS7z;HK*bEhH*QKF569*fbka)ag-|vMx zSZsZYok6x_OtOBP^e1Jjo{6-|`Qvt4>%Vj-sbJ=O|Y?e^`b2uNE}b3DD!NV+37&z2ZBbBw}G z9CFZ{eE`qqxhkxEI|ud)Q^`NRTCw#@WxE2V?9T*$$+3oHi-NR}Ch~znvE?#l- zes%`+S(~YkOM+J#6Ep5l(`imio1LH>6(J#U3-ii3&W#E_>n5o5pY+UU(br}_S4zDl zP{p&17U7PzAWYm*_mzl?7tK|(2c}FO@7f&x&nFZ zRk8m7@(5Y&tZf$q&Tea%9}q~W@eoW?-)Mn7tdwBW@>*`7>xxPIAL+aj^R4!oiOD&E zBog9LA@_Tub}z|wbTK_IsnmM_`a`N%a%H!qYXMMYvP*^6IOD5_@?Ag8>H)B5?|qDa z{VNjK`wR^xw%n_3O#(I(4&+X-Dc6_1eeqeiSUF$W5whs%EIozU$Jl9^>>G(C>W>gy zAVLk8qWL6wFUx)cKBm4OHTFF#}Ump@kR{&AieodkY*gTeeA8pYXw=kjTBfAs(BcrzXgM5Lw?H zw?tcqU}RgTg?R4qdodOCQx-WE#26cVTv4e(A!ma5xr9w^D`1AB3?6590K*VvI7rxL zu(nNyE>>z&OEC6b-WV@TgA(M{gnGciDvG%|bV^SaWDq4$+tp3VSh& z^_n94lC+(qqor)?d%ui$qh2mbxKjm|N^zQtH?rV31;xV;uKYn|3*D|!WD(z{aR^ej zwX&A6q>)U^4(6cPuMm&xQ`uP0R!6d#xfne~IHS_b(D(lUraKJHQ3_mt8Lmo!9Jv<@ zmx^@z;jGtRR}sO<$7@&Qe2Ut(%qqJn#*@@KysNUavM#77nA?BEp6KiFIbOd<$e5kH zU&yPu>hxZHQPO4D)da3__~P{iPjJ#`yehF~VokR`T`>(Y+#{C>b`;v(%d5i$U@kaouU?ppHX~=ZJY|_(62xp;JR@{S zk_4AzN#RxTLlhMqiyRikOBWrF48HBm*Q=M`1!By$#mD|1W)7v=p%iWTK!p*YLfUYr zPl_PEz$OV>*ZpM#r7WzCCIwtOkV4qHfhsn5dQm!jQDTusa|M@whAJ4UkZ3<_06^Ga?{) zq!3dY9Jbx?vZp40f{SH&L?@}U4UZm!b+E*!ESeulilXGBr^5p^6}coO#D)Yh!)=i- z!xd5lw%)E70F`zu6$Ev@=oKmbh0C;pcP}`$6pyjXP4VWix$HHAh>7X@Ff5?QVap<1 z*Y;vF78$YcUl)cm6v{XjNmt>Dp2H}?L{NFfl~7Lz^B94DH37Slu6^GZR9yQ6t+Bh( z>Pw%s1|m~nNxj?I_@L|rMu50NeBjh-y9f>+h@jzvRS+fR4X_nGcTQGC!vm-?PG;TS zA%U3DVHtB+8I6Owjs|CpygtkVs7>y9X07pfqO}FWi5JjMhuK&2+R5IvFhVKW*Y%pI$Dn!+5UO%5%x%#)1d~ zdd1X%Nt6`LAQ*;h74b!oP~B!oUl-jA2~1yTdm>PO!0lld^p_k#KwNU&JTPiOyOSJ# z)EbvzB=ymHf-oZ?II@72n(%kTAlc^BbzYb_jRc@38@G$8V&WK;8oWErdSS{4v295P zDtnc<5ef-~^oFDV05HR1EKr9nEmVFVwF4?ZWXO}B>xL|9s)6kGox(Q51+klVTvu^w zJuz^9z;41_8@X`7MC`ciAe-+(w%BQA1(k5eE`3mO*cCIo_roGINsxM>ueXOU3{&+DSI|cL;1z0GxskM7`#dn^8poW;bwVzf zl@hMdChu4%U0Ov{yI4!sh-=XRqK5DAp1h%?0OIdBm;Pvk8_cpI1dvexu+_mE?nokk z+6c=%>z#eYvkzt#Wb{f3j9*}G9P}qb_^Fbfq&KPLc z#5O*CUMPaAP@*>;XgYgcF#r%!cW69|#SjYw88Q&bJW&lO9hPoAIm9kv!=_t*x5dQ) zVNBW`vdQL4bQDH~vyPjU-VR9_+H?lsTDNdz;uyG#1z{z^T18NyC|3q)!vM-GN&c|1 z>Gwjy>kuu7nJ-L0gu0?yp!viho!yqgT|uy+Zsg4&RRXp=+c89VFyNz{z3?>v4`;OV zLV*E{D3GpbHpD2#?|G`-5L(=S!AkDWEcl`YQ9{MQK7Ydyy+EQB9$X~_5LhA}nwJT)QZiBM>uE zDmp+|V!nooBsT;Owmuy4vZbd_4~wQ44aEp8nW*XP#4TB5SafU7AWC}dx$}q+rpoS~ zn1I+=1>dy`vxg2~xQ^w2@k0UJ7`EMe{{Y1h4&s|D&*A?7IITdp_VFFdxyCS;L1CL6 z;g&$VDcMM;0Gw3+8ap!3JPz4yZJ(z?ByF2<-(E;41|{7_LvgaoljwH%8&d)2LtzsswX{^Ijx~6uQ7cD}yePAH{SF3b?lj zlydOIQ08=I?0|DCh`dnX1&Q&%FvVd6@Y$Lcmt7PMfw z4wq{TA%SeTkvO1JxG@5AvfR909~4sn(9k%Sa@|*yN{tR#f7&R9May(iftUuu`#_iF zZ`y>=T)l<1`ekbITLVcLp#||HwhDw}B0?vcSU>PUF4%bvJX!{Lnz zL9E5uYYb8VaIQT%dZ368cnQLV#9y-zTQD*!PyGx)qo^41a)?tQlH%vn5HkWK<*Jbk z&+a&Ei1bb`3_;P{D{bAU_+d=IFkoy)ki}S23EcO8rJ4LuI}{zZ?L5@3AK{8;9|}H} zE!yOs%w}+EIcJZM=@59a9W(a4FjWV*r#!l#l|f%&2X6}ay_ka++yubgc3)~VAsRF{ zbAU2fVFZ_6YtA76xb26U^uee#07xVFVALbq%Roxv{{Wf@fm@in!w#tfgNUwVy-;eT z9?QLdz$yM1FseChb9fwNR$iN{*hAk6*03a@pJZgVz(k;3>4vx ziN*00D4ifqVOtMs78t^$%J!DbKFLN*s0QS+Z02!f>%uF;cTL_=+WdRy9Idl_Z2qm- zaa}#d;`O2gKH^-z;*RpEm#Cb_RD+?`=B3YnhjMB>QA4Xy)*pBRf-mm)>2poKBt1Ym zj&X;Un;4l(hB%i8u>n3GtC$pYZt`7YXAboj%$>1d*}v%&sfN<;SCM^~->ESvwRTTX zDLM8@OT}z4yg=L?Gf8jzO|rVwn(-SVj<@%J@kc&=nB0rCIWl^W^=31uOEt;jzTbR* zY>c~8BNCL^If*qKxKr7Q*e1xsw;5geI9)}>3`rAWPE@Uy5?GSMQ`}o~M!Kgcpz({7 znmPqypHgUzx1?qoe(s)=9RC1xKy$5>`U{OJ4Y`VNjwN7iX00k2{xn7Z0E#$VE$Q(Z zISJCNY){rp5XN7S!&z-sMI4fGOQdsuAI&3LM^SFgQ)KM+u9%l&wArqrMn>FM%@aa#Be1^Qz)U;U)nrC7(PSXXZLt=2k<+cUqi*!Y(6D4wHgiGKK730Ua@E?SwV z7m6)LCN7iCT1S^qkL<;O%6Y$8rM@o=6-9>|ME)OUDzy$6aW3Vj!aXJXqKPVh6_qX= zq5wN@mPkE3MG%)1)84SN_M#EJ!{5Q5iVhSB_6*r?d4J0RaWbQ($aih37M{!+&dCZm z8H-0Q;)^4MWzE^#z8GLogMn~J?m{7ms$XFH>x0?+P--V587ZN&SYm-0MK*+A68``U z6n6okq&p?YlEuYH($J?%dLdwcxeOCZELl@zN!wSlk$6%$T>eg!?EW8%iz9lb;-u{x z6zyvejHVl<%~Tw7g|i&RroL;*BggV`Z1u07OViYJ=cCV|Y{Z=Wy>l8&9ZIXAG{^T+ z-wG%7U12`6d`$lUr)-Vt^1svl$4^mk+fo1^0xY!H@fLz2k!g(Ce~OfUeX4puN#PW7 zpG0sTo#{7pAsFc4hz~x;T zu2%++O(%R^BL>f}5RGd&P&w(6ao!^+=RRVp4(pFE3}t57V)tAeFhnA%itNOm?FUlo z_F$1ZfU=O-ijmpqJFkX2{C{NzCtf2w3gNcJ+rY@3K`YCpBNr8mFHxrM-0cO% zgeE;CiTzD2@{+L3gI0$qMqne!@tCyu8CY5h36>p1lzLq%x`Wn zBDT=?`{CG;i)SX~@gZSMD>w|Y{L+!F{76C|zso3pHufY&itPri+ohfsgdP$*h)bw{ z$`y+)*twdCoT0ubE&$8i=NAE7P_~95`ho|&i=&rl$?Tj%VoAvznRb?!W*;{M&+ zyBxvJ8F2uQ2((GfK2S>KPwpN4bhU|=R9Ob+9BnflNQF^5mIJu8oed>fdaU1Q9sHTL zY6UJNww7J%=bSNBm=u`jl5nn+?TlGK@(a$u(_Qf~y*sSUe@MA1?j_*cn9 zpB*ref1Vks)G0;6SpEU)f=|^}o(dTI%k!sxKM2`&79xH^M6%r)(2RO$GmyzOI za%hJ`Y-{N2z!)cUH&$ibO*SNiMK~+ECHn}mw;$p@>}U0^bj}XK)a*WT?NI6TYB-ic ze*-Tn*jkD2%Q)yS4+4Bl*5Aat{{U#JTb;?&A0~)XgiC~%uaRt~V#>ieAnoYhNV`0BGm#3JgD zP%#G-nGSjYOd@8VHM%fn;PS9_V(L)in6~OUZ)jibXV8BZjT>ngW^$T>XdynHzD@rC ziMNL_Uzb00S0D0P>28x(OZ6Qs=!^7%RdC&UT6&jUn71?$5p#*C^jv?*T`8BSe+NeL zL$bCj=}S4aEvwWSskrGaQcYdnyO|d8Bg_8)5^=F*#e82Aqxh%v->BYlbJNpIi_@e+ z4UK{#Jt&d#>GtwHW!6U(3WS@7n$9~emL;G~@RFy$-x8%MPpg#6m6q)xmpiTt_(jx= z*JsFEZu zP`5zEONzYkfb}>?FqlgP5^c;sC-r?Pmc5in)Qh7~_83Ap5m$nbCPbyh;e=$#qT{)) zY86AMPB>&&T^y{luzP_Of99~Z_6b;0FMJJy?m4GbCTZt$4ijbU_+i+&ET14}q?>1H zlYkbu%T7k6=MtkjmOG!G01CH{+0Bk*`Z@>JtEYue=?(i6w^XB@6|b{ z-_8?}n+9KB0fU#u^%JAW_{)eWlN5TZ(z7hPFEcJ`4z%FfO%ihzk5$Fx;>(NGLzjix ze2Dz*QBUEGaVCm~m+6Q(3ii)xu$oQq%~>L%X!3s}D6 zN1L~tMI<06J(2dImLsl)ZZ~bnoFYHrg4~OrVUzh5p42X5e?VJgeBka++Jh>a8}9f! z^Y)?u(V;`ppAFFnWQyO1mxk>k1>6}9^Hkxw89+leJHXNgplU)8j(Ma?g{T)U-yXcZ zpPV?R2s!t}1SGhir&YH^BCt^+9U=v*1a95teOE*#syb!6*X+WlutT=Ix2n0R+FaWSoE#{@qtBNXT zK|%_+oP1N-D6WG0_=RWqx?~DAw4)p_+s@SZbwZyaC}e| zE?_SD(9KItASodTDa0SLQ7ItY2~LRGMZ*VBggG_GlEuOhK+Rpucwm_Z1KuYRuiAo+ z!dZ4Ke;yzV#I%O8ldzrPU6tBJR-~|mD^B$rzSk5+2vv&;m=|R;yYIJ7mfoh&uRU0tf1!06OdjW6=xrpE><6`5^MZ)kiLNVr zM4G`u8W2G|BzdWQs6R|SK!TXw0OUXO6jcW*4IEA3X~XtlwE!>@Z_D_i#j|Kovu{*F zBMaQ_ZB^yf15oN9h;Ot_jEY=34x~c0(ZeMB~C4~##Q3^f0v%?CR z(FBi1ekf&&usMg2L{r13;ftoqvkjh@xIa6fnFu26zZVP%2F{Tdsb$(-;)01Tf3^VN z^sT)xMFU*&&0wrUSuk8=mY-LOK2?J^xwDs{rDll4k zulmXy0*W)smy6km98HF=OXi*;UkpMFC&$mIVXM>&5$`TXmEBO-fQDtldHA8R8Yy~# z+j3{coLE&b;M{)y0O5#Gw*6lZiXkG*8J2gPqULrS`wy6>+|FGo6az3pf25~0@cz_7 zE<+(AORc|(Dq~~${Baufwzl^n7gu+VPs!sb53Q1LgGtJF|Pp;STlBu=nI6Jk1W z{{Y?;{fG#yy%W3a#Sl;koGgNdT$AMx7BW_HPYCru)DT47!dVPTgrZx!?}afK$hX|S z_@Y=|Am&Y&k3qB-bGo9;h&ci#DMN zbwmkYMoL`?GC}>Qf3CqR85uPjavvy{BPPQ3UXm!VRT#lF-kmW9!k8vdzIMe&VA%6) zC7IiI=WJ7oZjgq}yx>C}OWWUkEx>-m*4a_k=*|MZgEl$z{g_}AH5@W3Ke%8gxmFEs zX^{^JdoZDhq6$}!E|_Amqjej(DoT0#T`+3EUt$Jc>z9mTf6B0hm9+9sE+{1i;QK0) zbi*t+QfIN-YOVhO^op>jK`PeqeUyrssIdgiy!gH-mnm3A?~f&bmJ7JO-a)AU0L>I1 zVI(42{?uB|i?F6dL^SG)X4u+}M$xrAL-9bBJ;je+YY<&*D3_AOM_?IrWuI1Jy)j{W zq1Eo>;`oXoe;Xj;T&`y%g*dL`sdXaNMgVL@hXci;@hHN}>=m~s1BlB;SQUi>J>mZ zR-S3Y{{UEIcPI&T+MoNPnMzd#@pmta!xcfAP!3v4e{Y89WOe{aTDq^Z7Cj?Jh;1DF zF?AWJ1a}Z;6jekBk8u5{B?v=}+t2Yq5J_6roy7p85pG`(i|&IVjp`3LSTO;2_NIje zP=$7juFL%-11X%ta{_##Y6nzU%%Fx*OoXVQc`wTE3=Ih|DCRgit`h!GH6n;|VK}ql ziBN_ve>g;BS87n!QV1(@6t_sSw*VX~ZqLP(LaYOPIcE?!P)|ICueHM$VlctyH;0ru z7@?%Wdt6*l$eR|UXNXD7A*BFu!sL+!JLvH6>Fs+l3Q%qd5#*j2gF7A;vJ1Qa0EQtZ zOpmPLSb|0!Ba%JcaD#;)kMwBpti#i15QzL9kYM ze}gU}gP&lB4WNmN-SEVRApTsC!%~7)7&~l}yTcKff|z^DpHwJT;8{4Td3bz3D5`}D zAl&}|+w8@WqeeK1GEOL4FcScmqhJ0Q8w5F*vSFK6`~9d2mDqP8=VPj}>VJkdaGOjH zUvqKc`Ll1cz6EupC;2b_*z}7#Gs2rke{2thVyiDc-w01iwC@+1fam;B3+?0^vd@VT zf%v<0s*TAs#iTa%WTLPcqiRfT)ND%YDOAF=8Nl&Mi$+E5j-SY2I2m-+AIVLrC9+o# zD{e}c4)_Re@RFiuY;~e`a}9@rW;-1Fo+WL>DA8SkTAfGe{{USH({Y2dDd{o-e;9y*A_J3;as1>{GguFbL{{VD56>4Y9w98@pbp}e%K_?H{0a7cz=xvm? z$%vBv#Mbcu3Q1AtOX4>7WBcHLQHnl6QvNGH=uJrMX1XlpJoPS~ANsRMMGKxr{8mS& z4=?zCsR@dnqTsOIyIPf;ng0NFe=aIfNi8OPsp^v1tAk70dYyai1kQjr)S*_3^*?{pRSQ%_RLQUF725 z0u*J-8e>){*-qLeKYuLu?M9Uy6(oHZYC}-L)kG`o-X{N2Ev&eLo2#V#Rp?M1OdBM;oIkW!v8Pda@``d)Q{i--aoL zyafSXX%RuFMn`$2y2QdoHxg{Rv-GLY zn|k!3I+?je!|!h81g&-P>L}lj5toz6TuW8zH5D@vNz0i%GcY#~wbV-*XmQonTFk^| zAllQ@k9Jyc+q@(Ze>jva`MqA2BF#nKvnJmBw~pI!ees@MSnYAup?Z#b^t#&D)>U7q zB}lh2<#YCzQm1rucsO^|PEKufni1LU^Vug%b?mTbUt!$4K;5cEaYEQGi4Ic;;o5@= zaO+SLQ%J~Ukf@sj9fnRnaguK|gt@Dvy6kUfPU1?dbJJ~@f5^66B18Jsj(0PcMd>hd za<|0a5^apQXf}wAzjS%0eX-A|EK7;y@Vy>lZeWC3Y**pB10Fw7sx=dAbCxy6&tod{ z(-Q^2Me2Y{gi<|E8;H1+>~ZnYNclF(aK)yZHy$n#5#l0PYrQ&{YkiHmvY$tkd6As0 zeW7CBAHFzTf6vSOaTzl5ZPKRXk&$XihT3F6L9w)L6cLUNT&y*}7ZLqX>ghW>2 zPkwR2%VV()tI}Jkc)DhTM3F<43CV$VH>0SC1UWNNdWx8_wCJ@dO64@BR$f)Lw2N>B zZtLM#f724IqH0pBQ>7yJ=q#AIFiU%;Tq2#{C_vq*TP%4{k*dsQMqA;qi+Od>AF6T6 zizR}WhALCcjLA$i%%tIZpc3IDPdJO}&xuY2nrYDM&ll+BI9vuy5^l*p=p>2@1CORE zk4p$s=Ngk}M(Kkd8@<}YA1lK2i?R^da8jWlfAuy-aK+aK-RO{=Wqel1SFG|D)G@`Qp>>6fWmmZf*=rzPbrQ(R#)Ci3cdp+6d zf8bdsnqz<3<_hOXx{5NU7RGJ$HeH;NIS`kAq*AP@0+U3?5srFs`9skR@}nslgyS>r zFz>EbM&ThFZBLrPM@Nx5O7M` z1f4-{LnP+v6ij`u6@n7J&kV;=|df4Oik zQOLNX89#_CbCr6;(LFUZO6#w>N<>P&ao6MU@nFU2Da_<@TalO5H3lG?#G81;E>duW zyQM2GNG{Hws}b|bd!r_2Zxsy{VBH>%zKL`Vld~&RsSnmw?F^kZ(8Z(oXZ+(w8;QRm zPn-Tm^p~l2$;*tc5+0)WM@}*5e?;{Y^x3Oq?Gp7@TqEqnBE1Rf2@KOVIm9}AFm?hM zu#;yiCMpAKMdB_kAu`6El1~cE8^GES@ey1qTsHYd3fvY_aG7T{a@TIVi#wr`j1qhK z6ExbC$pd096|QiQ`=D69{v?so+Ov+!9^w)456>=8wq1i&7l`Fqui*KLe^sJFcbQ_ED-PV|f%jxy!`)kmd%$IpLD-=f}cW-}EHp=>e7eyx!;nw`QZ8|L&9~3iO4^?wyJwjbj+%MDv$4R}g%a+mEMJtZpe=DQV;BJ%8@|UQP zZ!kQP9%Sq-vf|F}D1{CMcZZ$OYQ&-9F8-1pQG3x<3^NGf=DgzXvN7kJ-f)i$PypGi z1dRJ2>0E?K_M>(c*mElcjdpFSe9Ld!>fB4U>CO&-V$P&XmBvcty4a?~{{RubvPlhE zu6Bjp2jVMWR|`Rtf04@9i4!nn;Aj3QWz5U(Wxe?@7;A)YV8106#MoF}y@ zybp?;mz*8sgT-llKNre92LZ8U#B-lBLmPRyc}d1e_FHJ0njRt^mr@mz2D*O6ujxKe z{WIT3N#RP9+eyMwH%LroRY`W6>0fOV-Tt{o9w#BV+5Z4*9BzLZpE7182wcrL zx%gw-;dPZVeLB z=yp3|a)5Hhr@x#i8imwt%k0DnXfEw^{itrFTdIU?u00^iNFA~}vDN{cXj`p6fAoV^ zA(6DLf>Id)w@(xiUB!`Gax7PJ*VsXD^6?BxgBAeye-90}3;=MzEw~FJ@k0pnvAG4` z{4oICi232SUkpPj7d4r9xNf`cL>m#X20i>y0Dj_|JANoT0${kgaNWfN6QbDgwn|>; zWx!#;wnwUaKe{gDQEaz%i}s+Ii`)vAcX!2ZgAl6*B~B&wp@nWw3uHxddoc=$*u2X- zyWxWae>cO$)pRPhDtiu^0UcAPNTy8|IuYZ`!wT#{%Voe`FT>pyqzv7zTmC4FLE|H? zULR+}0zu1sI$N!X4|{)kMe-9$c+vKyR;RBJJz^y{oT zJAoMXF>>B(!d@Gp=mg@>2Or}vXG>wqfG{F(xRrkt2tfNq$7(@TrclE14y(%U5`h|l zfBQ$D5_qB(=ncWP?k^Nxz9cdgwq1#SddH)q`ye=)G3 zMF|M$Z^Thm4OGDyOc;XkC+x-46>S4i{wQ9cY^s8BShE(AprUa5B@lr`O3c^oL9uI5LfLon zx26{}p^D07e^N0Ntt=TX70n@Y*k^EL=#9(afGJmB5xd23WP@+~Q1tFboe3MQhA^g; zlRM50U#TFeHYVmsQA(1*I7;N_mY10<1V&NDIhvr>H0a1*g8*xc=_rV#& zmsu}-LJ1+aPNE!`Z4W`P|%J0P& zP*i9|rQR4KOoAdN^n*fKe`FCoyX6f6RYZ1-&9z?)T7@0MGWDi8L54bI&Nx{I@_ag7 z3oaOaF52%hE~qN6#Jdu*oIh|>Fwx#05A%Ys==Lz%b{>PQO9aj*jpOXZBOvja9pimELw$LrR-{%W(B}6$bXlwrfx&WcQw`&5x*i%jSue#_GM2BUu zBg)gxDVUKbo|i#9f4o-2j==yXJeT5&77eC~@=pH%=@1u{6w`e^$ikX7OAcFINf*Nl znAue_vglmO=mB{lhVM2&x!V+~XVVQF9p8ul0CY+Z1-#QF@bLYpIL1`=7EUF(uQ7r^ z`h@yFVT;ZAz|4X%n5XYGSXF>` z(AcRv&kP;Le=F=8l>2&1${}nwo}{CiDE|N`D3Vl#nxpRYwhkDIG;)q3_Mw&z7-;n- z;L;6Pk?tiaDw-|%-3%nD@2kjqMk`QVGNe?@N(`bb}5{{WFME$0gxYI&*FvxB;sVFE0d#SFO&_A9w` zj6vxl9F!t+H1P1nGlMg*PDtDz7O__XqkYLT7OzZ2ZH3Y|bW^+hFv@`}V{>|BoKPf} zF}e4Cf9QclMF^Gu010tKE#%97sWV@*6}S?uu)^kbCR?y^T3oMj+d@#C|FR@}?UJ-)=!3E9?)+8Zn#%@7) zd`Yda!o>^0W?sxLmLNtR>GrORsSNM>k$Q_if5fqDIVQUP1g(0zG1Y zdqdL@8w+f_$2fpI3cAqC!w?9DGRX1VpW*X}60V0*{8q0A*@aJFWx{m0IduO3hAP0! zG>a0n@?UHB#dQXuQ-v~13MQm#gdEGmbS-*9GcGD=n)rB&*@9-UH4H9!bq~YCe+sB| z3Qh?#)tbSo1reZ6XT#B)JQ+|3;PQir(RDED>M8AbV!;4Ka1y8Ch!YLm8&6xa9HFp; zsNKOXrTv&bz)xUJxIo{!4#iYBJVKc%U3OPRvl1Y1-lv~Pj7V^Y((dPKDS1W2=wRaQ z;^p52#Z2fkE-3S|6vA~TZt5?Te-WiuW(mR&CF6()w#L)qg=zt$ne1p+c|Zd}GO4>i zJ}4&kV(J~oJ!5wJNpv#<^$KM+<_eq{sylakC_P;<>2N*Qq+7|f>aLvj&nPnq#0-nF zq+Tn8^H1DR%&pWegA5lj+3_T*dq#oz=^3BgY)?{~GYiVh9vG=ePB*)lf1}Tk*j^Mp zf1GB({{YQ2J2R+kGGj@^EWW%XMubt_!zVW?>5YXiGLksBhlo+`5x34ZD5vgpR>ft} z*_jt5+;nCHxOrQwb2GC;8ZolU)Md2x>6m{WR2rJYZB?h)HL%|+MWkOStW}ljVr2BK zR$CD)?FSLqEz;<8+E@_Ue!WI z9$_3v)v+7TWA#t0utcvGtme^t(g&EmW4O#O97^2DW#Sm5_fd=HRs9>PLr2!9eJkR0xW9^iO>*bjulIj>^oIlBd2BmPWu{pM#75B(9+>lwHa&_W zr-?m1G!$xVf5h8_0?fFnnGtlPTB#N;-6t!0LwZ;{v%{!?z}roa4gER5 zW%pBm10s62X6Y{&n`u%s+y_#uGFLMu1R`D%T)U$pf8%Q&vmYg0Y^M>|@ng7xp!CzX z?`925uIkP`1|*^x8gJABUSw0@@pnN7>6t2m5M{>$G94BH{a{RV5oS;puwd4CZxli| zjEd0`Ls>8)w=f<0`R9Fy+#6rEdv! zEP7AJaXmh!I{a)pDPmi-6t{R}NIw_d9VvSC4`$KOdkxteSbCb>MtFCV&tz7R+$|@P zN0|JdKW$VnX5?Jj40H(iV}kx&0#tG=jf0nle-y#8haENXK@dAdlvKvXP`PZznvOj5 z&APTIBk;!FDr=}LShif;nC@8VOoTP9RK}l8Cs+Mv3wEf(T|W0}c#TJhis4c z)R2}4Gh>qL7g7cl$zVuXaE&CKWyzKBiirGC(BdH?ELL%JozpH7_`ila*uA_!b=jx3 zf0&6$nHiRw=_^X%;UtP>xb8dYg3WC%ZoP+_3Ve%CRGS14@QzW(?qX%BGcMIh z&C4tiH#frjf1cbhGqZ$2y%U%#M78Nmp)RU9CZ&p(Tu#c% zYFoo(ZnLCXha;>Jts|;ToRQUBRhnX5TqQCAE+AF+Km%W~JN=KR&M@K4I8QR+S#7rt zYsj>Vuh<8VsYJ^ui75+w-?!>WENDT=8y%a+VMf9i<{ zijfF={m>JWVU`+|NE?}#lpz^_Y?Qa6Ttk<-1(!q`RBOjiV3iy7FA|Hle|zQk!COR# zsYS(&%vNZbZs!?0l&=C((>)O~g*D`my7Mi2F{>QZu{Qk0r!Fbg)KJ_{b?PJ53ng-z zlT;qojt6LM)E{c(;#fFQeNaNZe_IDB_wOBX;uUa))e>vI)QHce2}m#PRLu0NF3dM` zbDT{M?$T*9%Oy_b;ec${QsSgM;ypvhlH}WG*@$mHWRv9ySyW+>mt@OxjLsW#0z}B_ z(7XHXhizm5@Xh^UN^+eyFzaSn?u-R_d*WG%T@W(#+4@L5x@WXWw(5_ve_3$r?}bxd zK_*?CDd~6U!#`5n^S%J}hu7>UdhstfE|({T z=;JFVF4U_XTcGLx0K5t!f0bq?<>!sb(YJ?%e+)U4o6$EXFd_q&72Q`sBUrBF^)_$_ z8$p3)BVsLdH#dQGz$tQYds=@eR-g(m$hOmJOz+zTZ`J%`)ds7uy(*KmaRN>YL@xdC zU4V4iWS6DOPd3^)aauf9@fL9|!x6Mg$%s+FN?P)9vfv^V;rpR2e-`o$Z2}u1$i5=D z#^{Uf;uZDeAZCV6d`+hg!pKjC;@6xpTVjvb2ec?VyphZx~H9K{!WNb?f&J)+IQXK|-If6;kBLNpEadQIQ6Dv{1h zkhnp6W5hAk;J(ghDLPAE34N&Osmozv0we%?(Qp<;37DymG&C^SrU_mwh{_i3Tu;~wlUvOX ziWMp{TyEiT7Jky;ahIm@UE;PU12M^97siO?+ z*>O;&nvvMFh%ofhr?Rk&!AEmZ8Qgis8kvPO_3#p-e+7wM06>D_7bsgsg0QhaID-@@ zmYMlP2+Tql7c*TTVnb3F9Z`O?WTqHy(;o=aj8Sg}WuuMNG zE@g%*e-4q!x;-o^_&)e&;<-(Yy$rwQC!gjgpxrCda`{Q1pO4x5qth()3P#$5Y&T)2 z62Wg{0uYnAs+Zb{y%5igRXG$Cbrq-_2=6$cI~To}5h6Ut{#a!8Aa=&U_K>}(TM$@X z%eo~&sHI-@Ygtbf3*i;sHm9ZUsUMQ-N@HfY&Ps0pS9%Zy6ftb)U1D5UFk$xDU6CQ;N ze9X6C#V$?f!x1hxC9n>Rawrl)R zH6p_k-OJ0Up>rA_gL>7<-}#|$1TxIWa*yJP3PHn9p)S0jbp$iJP8_&@#S?HHY#_LK zMOb2{ynsw`7HJHqY%-ka(4mN?dlBy!uE~G+qf#!4t%lDD72oJ#%H*inoc4=~e;TA< z0`l`qczvik3F?s2^Pd+KA=g6{f(*^BW!zCw28)DpYT(z3*pv_;rY-_YdSKE4U=B=L z1D8J(IYI-zOtOR3h1_t<+3Dhjst_``gb^tu-j4}>)B`CQf(Bfdss%!@yS3`!fR!Zm z1aFI#{{V-E7RXXC=F}@=JHTOEfAd~L;)SvoQOjv)3MWml1qu@0YLE`0SkDYA(i!Rs zZNycInAsV&dDjHFaKIHYp$xrj6-B7M<%gF{5~hO_FzxKzuyq3@Fv0h?cIHmw zuonnRpS}%3sLkg$XPU!6s3MxTr|m#i#F;WK4|lSI#o~g62Kzik!J5TXe_@n!gHD)b zN}j@!VGB#VFcPIB7;QN7C;)<7x#qv=V5&nT7N%*{`%opYocC^BZoViLC8~IRXD(57 z3MXP58@|uO7OGX`8F2C|u838SBZRA@8iPH6l*h7HT?MHIJB{WF?GuJATSYRBT;MTP z;o*iWY`sWUUPBDvfke=;e{+nuejkPnMuitGJklzo0+*}-Qh^pOU(B?I31rda zTSGDBYzq|(B%Ds~{usLqijC$9u`90Xe}J$IHkjbH2qs~p+&cK7sxWTjxt?%Q8X%Bzc@go)8FD=3Ni*zCB{%|%aByh_l?>?dVM6m?DE4*^l&M6~czj2Jz zsc!~&u zUTaLd{{V_0H3m#tWt3NOO=AMd30fm?kMTmmpJAG2DVU0(yC}EFf8n+VavLIv z+!uNTQA;`}@Pqh}LuolEr@;?k)DMF`5%E3Y&n5cqWYV@tV_)Z91 zf4U5wmr*kIV&R3|L~gG!L>r6ZiX5Kc+?6KJUw=$i;5MuXAyIQu{{Y<;5(75GhC~|k z_@Iks6?R5BK`9l5lh<3r6w#p7&ob%?=7awLXckeh!-055Me8UXNDmA=;$9NBrs%l#EtJBK9);5u%vqM) z{g{LlGaJ0~;fvIeP|PLy-TP2lf}^l9GB71hD5{lH+rljrZTO&qTw4$&cB%VO2YO&! z;njGqh=5FNMZ_hScZMy2S<&syt!EcwPjdnydac%sRf0Wt21ypq6+n>Z_F#p~e+eT? zI(Wau3kVrGYdU}NK~%oLi;Cs%_@XE*!U(h^A-3o`*o?vqtvrQzz8IA(3yuEL={4E$rHhbXRa}fkcGUS0YqoKE+Fy{@9_ThR0mjir8JqF5&0me;AVt6(lQV zc8TJGs3Aq34IyeAPRxpTaB)L{?s$WFrPjr%Knc5-?2H)};GA#Th+7D|H6&gX9-bbk zgi)-+nI4?s@z^n|!@SqU6<{TRj~1)L1W-ZGzwpB%EJ4FhdH&25OR)M_`;+k77NoHC zw}3!$@W2HTqYb^sZ%xq)e;hXU^Wi?2gbdq!c_cyv9W6z_r}x6hWLptJ;rL)3hzvy= z=lFh6K)Q`)rU*dM3gQ^NObdIPJ7lw+x`~qeqtbC#YGr?JlXPI$f~m9GlLf@&tix`$ zI`UM>V_vorx_FT7%p9EHyV62PUz87~p}c)f!y_=@;(4Jbx6M}Rf2~f5`$i}zzc}Zs zYbl{DGv4N6e$Nzk@c10e(Z3?~JyQPw>ul$V!EC>FU(Owi+#ImAvC%sR#M1JzZQdkL zjzT<8u0|b|hJM7qr6qD_4#|yLNwMt{lDCtjJX!LE;#D+1?K%COs#fY0aUz;@cZ0+u z!)L=9It8|DusfvHe;%q+NFH$BQsUw(pxLS-!6>3PkOv75R1;XNQ?C+gO$c5QU$Y9R z&co@JNPi4xV^l{?j)3|#aW7-r1!9V2Yc**FD}AFM`bRtRe)vD6N-ne4{{Wqij@d4B zdfjrAzx>{5Onyrfkr%|d3dY*X%2}~gz4CCJJNR#7f13Wv?V_JI(ggnKzQLwn z&I=@86JisYbjz3OZau2fhNVZGY?9=1U03qY`bR5=Cj5)G={^sAFc=i*+exR?1-qLq z<3wJ`M?qI=b8|;g#ZwK5Ij0O-E`q*=#nv9(N=Xmd+WW zwPM2Q76hYje@0Ms6F$KQA=P@I1URB0Z1cPQEL=v#hHtMlD54G-_ra*Z&DtxP!AEk` zaW456__&~60iMH&aw2e5f6_n}6iEp9y{_nB6WlM4G4T8`0AhOr;99QHF&w=jizai^ z^F?TY4^DmRy~v(%O_fo4gkR#GN7Fw-AEVBdu$cyHf7p;(s$!{u3oaBU*iT_RlRrIH zI6S^(+o#0zUyb}#KbxP+`;ODX7-w<<^Ml3V_+yCqc6wTtv9eK;GazlI#^W;K5lA|b zBYr0y`eFKBE-smvpken3xF{?xcIlyS0(Avri-A>8*+Q%;Wr1T z*+}W?f1M|s=0C+j)GJoq0&<Dwz{M(dE2ET|}Pf&iS?A zZQ_1Cv-MW0GW5x=wSqQvO~aCIT62eaT2~fH%~lJI*2k#D$4po%r!hQhF1eqEJ4NAO zF3+90rSKOtF^O5HUOQF=fi2rH*xn=B#Y=HXf0ZGZ61CZ`ui|~tLbZL#$~gJCFO$f6akc%c7raq|B{5UYtJjG)!Zu_&MhC(# zKM&LgOWB<&n`w-=xMSizB85jCZPY6l<5oJGsMmg%7`jIS?C-LGht@R$v4I{E6w6i8 ze>LFaEU~Tl%M0Ri0Z#UJiBqe@Ued>Z-~uUd*e)6jS+rJF@d6n!S%SEDbMF1|lkyp< zh+if2hf+(kUXp-6|4{?rgvV$rku zuVLOb;UYqgmj!mFtVmqd&D?B%U}h>)fAMW>r>3_eUaWtdb9p(p(kz81a*Saqfu6Tm zxQ&%AE?%TLaSRohzfhK?sMHO%$EdQ2O@(s%FCvusXtslGqa92sr6ESdH1(M`58C35 z3dKxp)*DQxsZyo|zt#^=&KfEwOj0l?y(?97e()5<$nE38LLKAs_s1_Z9o{qTe@lBi zK#-NiP0BYk4;f|#L{|}K;m$evs|<8eHRM{pzGkB>FhAYfX6T26liJP##Wjbm)TmV2 zdu&L}I^oN-CixaZEgJIx%T+{!($uA09-OK(^;M6ETqJ}hJXeZqZ_g-%^#ku>(iN6PR7%Bkcs< zf(L4l=NNHiXkqnP9@ZX2Q*i^=XRHR#Fnt4fy%eu=;z923a z%bm)!f~eB zY?MD%yePgHOJQdmtwO&mRC($5rP`Abd|y%Cy7IQrx87b*g>J5p*{n}9v;=ONMD#= z)Za};Wa>HnQUaQnR}OyUNVVi*Blb214fM{!)7!C}8L3yC@muQ4?}}wt@nA6Fw^6rn zF3>1AyLM7Dov1fgTXGGn{bKzVpAnGDYLbpl2%C;KXx;GKe=(WyGzUG%x12WKyh_`L zir*Ak`vAfjx?l(th<%tUmmcJJ)E%QvjMD>b%RMgZaKygFuSsyg^@$p@#ZX4tZf;|W zZ~$!=PnG$3!TLI0pAk#tW@BN}>)no6M}529=f+(*N6x>9{{R_A^;GtMisme>EJ#Sb zFIL=?yx>hZe_8N`LM8H#9ZjA331bz1V{5`~48v;Y>v*lzbXcp<7GJS#o5ak`QkyN{ zla1s}7gehbmtZPv&4pGDrt^*+`Q>Q|dB*+|Y{Sh{XNZw&Y;`Je9YSu6y=~Gjs0IR>-39dDD#Sg&LLm4q3vrEJu5QJIhQwNf8gF<5_>VbgO6oP>!hQ#I)cd} zaGlZEhy<`~*1X*&eaB|_hQ;CbmLC!oNA)~dZ#~+U;%ti2x56+l?jnP+o^D~0He4<* zFXVwcEbIe)t)W`b&2w_7WP#6lQbCsDmbRHgfLgUx@u1sFzX+LvmYMrppoIP(Y8 z$*9ICjhVh?1?29EWe8Z!TD?%2jSL9f)vnGnBX0qPT4rQ@E4nk=fvLy5WGi{{irghuqPZ6)A(XGU4Iq@$1J%n;fcMGy@5De)y@T5Mt6~f z$zHiHFX7OF3DMXGd>K+No?Q0$Fjc ztr|%gGT;>Wpjd;Zd?c}2k}t4k{FkTifnwHl6-$z&Dx;ayP`V53;`nZfsTqw=6Muut{4f*&j2R+ok$uWCoIhw{yAaE7cOgJb z=z&{!iq*S@03{UTo?K8>7}kNK&MB^ZP)PzAhEF}bJz>!Vj>BhM$TWr`jaSgpVZ{*B ziW>?9zR35URB)7pZ7!c`G^Id^0OQuUN+bNr4xtGdguf*AZi-+Xg^bdUiNZQTFMqf) z!P|1{RX@^2&v5r)PCTRiK2dcP@1PSC3a6lek5Afypca3q0eg7(qHv(YVm2Cf9X9cI zLuLn#C1y8{TD?%V0oP-MaS3xz5kS(y*B*=!jhU|v&@8(Rm{V?VOmgtVVG`qp$cfLg zP_|tf5wtEZ-{d5wiPg1hJPYkJ}Av98+#bvke*2cr~#*DH<@a@QDq4kg4~80Nt%Pw2lK6U{krcR%3KFm~L4v9b6eMnG!G9H9c!I)* z)#{v38rV3LTWBTEh9xu`C|29va~N4+IKTu@d!njT_7F{zhnbHI0;p(?#t^MPhr)ON!_^l(DdD#Ds;@)fZ9Fngm87cCX_3#Hg#t zjpuiC7NijS!4S=Ohr<(6R^TffT;hlNfkn%ME4ZSpEuUwbwX@5r3x9G5R$RFCwkTp` z(dV8=4=)!~KqoHG9KiCoh9M)|Lzfqa@eh;)M#Gd~PIdnPt|&49eC-_ltnDvqE<1+v zpbe$X=kURag%aTFpGg-`z$%V)(T9m|FXH058ne=7U?f63puQm#23#z@E2HJlCuvth zrg`L?MH%%3W!aPBLVsAQfv6iyGrK(Qiqw>&cUuwy@RVIbXwcc_NQ{2Uj9VzZY;7{x zXOe!^v0+qb-e~0DJEO&NlLT9mA1e$mtoYqHl9%$SgTk&Eb(c1 z2;~)EW;PI|$$twwboPcArJfehk2s7$4@!}yNFjIK0jL&6&QrKDLDnjzD-jcNb}4X{ zS4HjuVX1n(JWvh;9uqrB zD5{zaj~6~Dq6Zf3ZtIIbW&w~@tPMKi)djz?o4b}5Qh!hGGUHpohA0C^0SN8>FPsqu zPD_pYbqp=N!n}fTuF~!ji-anocbo;{D7FUTRIW_dh6rk_NK2Gl6-YRpgw3F2-N&Q= zjX+lAcY)&)x&_MBOtUFz>COT@!X39Oz$(9Fz7>L=KQy`hf40d#D!>?2<8i8_Ed%9v0Bu8Ypx?;5f z3Ry89Z{dprQa5?6-N?QYwip=M>0b06s8};27o~0I#SBK!Bz)wp+S_;eL3)D*i6V|& zklIj1v}nN7ejcxC7*LU<=7o^(#n^@%rl)sQ1AjQ!je#jU7d0LzmM)?`tKMDb62(8b zCn;9);$31yiW)ghw&eDQSiQjk41!;35A4M|6}U0jNs!Z&B(QEt`3FDc+QnEQE;HxF1Ouoi<*n5rfO=rum)-_z z_0I72W+ixPAj2Op{#5(Zhu@E+<#<7COzzY<=nc844ToM@{;tG zw_9{9VnOG+^0@l0h+3iR*WD14J21Wf0DM5F+!B?);)c`;3f^xy4+(zGaJlvyxqmRP zdc2pb+4n@Pi>N;yYFr}+*ez&E=goijKv;l_nR$#Fu>d%Qo@vCfbuJVUxT1OZiYOTD zC#1zzAt$g2jvZH>&{iyN_35v8!FM3sM~trVe>f%vWJr~_W?$(cQ#&GvA{JZBe$-G$ zk=u3dh6{zKLbOQ`uV=FkBvG9+8nd zfg%SU!u;-f8x}=&Fofq03}0Y2k!{ zK-PgFc|ll|FiqUKFF1OP08Lt>;_=0 z)gt?V)38nr+N4EVlBb0&U4J>lQfg3HQ{AR(0?b?}_EfIr;`}gHE~BCHyX`<&xt#}P zy?i-*QDu)L<*jEw#T8->TP~GR_+pqPU75G|x?y{zLVbw{WCds#(gQ-5r2#$K<^NjfG?R3si~1b;{S#7x8YjB1qF zj6Otk?71*xq&AS3h+^)&Wi;mL$y&Q+H;8INq+{p$3YCW*}1k$P!`^1-|sRjOWzYL29Rx? zy;6%2Y;sNE;_T#15r2OF0QjSxlU$~aG^xuJ%4*0(+9HcR0$-WdvT z+$+@%nAjB@%P(c0a7(C)=(}tsO0hH|d@8*#hG6Box=F@cN94qViAZBJ`x;UUf1>s( z{=#}iVY%TeMaTOw{e#hA8DrGN=@~Mo^H(p_?b@S1+5Nms4}T!3F@M@v=W%A-TADc# zOy#1o-ughhqVahQpQ)ubu*uTuQnR7LOqib!!lJRG9CdV+oqA39^yp#?JtH)nK`_#O zBqWYXC2}$k!uzWqBfgqVnWLXlJW1@v zhF~HpIgyc=s!j_GGH*;hkcbGW5iBMBh;&B5eFxsDZfdx@)wEnqNdaAV+y4M4@W;iogy+}yb zIDe1Q*J$gHHnlj=5q4@xEV~}bd6Gv%i;kjmLpiOb(Ob1hd*fenqp`ZZ66hvpR;inI zfjW&faQ7ywm){&O%VpO|laj{!ZNyL%^7v!S)X7le!_Fl`90xcCT?Z^=eOyH(uyGV1 z6-X|a8iAx{4n|~5fB2!CeBPOG<%VM#nSV%bhpf4jZOzF^@f_JULAfwOgr&tC>)2B^ zhHgxeAHx?DqdPku-&#>>U4)5T=9k2Sy}KLjj}fqG}%eGEr^kOQ6YltYK&pPVurC)e25fM?}ue&bzQ{pKfMC_cSn0`)NXItMg z_GC(2HvQ1f-`bMa91P=gjf-sW_kYehAOHe_A}ijx>lf2LBA?iKD=Si^_U2!jbft)G zv_{bt<*%F-+6beX42yDfqL-VPX~Ue)bS?h?bf}}nuY3w1;I&F+Ik($a#oiHKD{c~B zf6fb}7{*p{jXu3KC)#A>!?8?%<-vmD%g@~z^4sDu7>UBTPG=IxPBJ9#Z+~yvD|br( zs?*{}eRz|24-ib?UF9~Q;WqW@y944Uba$h%B)G#@XAcd%YT!Mn8;sffHvR?|_eBXfBgn2c?`ucnO=)f!;T5s;SE_#SM&sL}+f!CPHGYQZGvO;2g$l)+m!2 zXsPD}h_mD=t@bMwD)lpEeX+-90%A$9a?vdJm-l7&!qXb3Pmt!c`F|Xtx35SCsPswd zg1G3H zA|=D0$~9r+GI)`_&Xd@GrSMm!rRlZuX_=AWY{#X~oNx7)FMsV|6?gKnx@IbiJu`f` zfp0i~gEA8NL~J@S<#q;DV{X)az_@%isF1>IzYvoD03oFHYFJ)y1cH{vqm%6lw4?cy!YY?YbA&SA&NNW4YW75>X<@*{GTn3CBG za^Z5fFiH2rSMhBCPc=(+8K%$Sc5;&6vW4vRM%+AKOh!&xxjip2U6|q*CX3n_-$pRq zN0P1L9;EF>Z|ud@iFG8TsKLAw9VC>N zB$TKfsOIdvB788Z#K7qY4W?}VqlqWwXDRH9&W{1{aOLL?@`Ypy7)X zydsKX0ZD>v;7DP|h8J(B#ce6HHb03zw4ju!m#&p8zV7I!HsPJAan9w|5X?HJvD$_e zIZEM7x6v>+eesTBq-_y7k@7MQEA*}FCpB!mWELuxzN-CnI8BO z#)WfdnsAWBAu~&!Ntt2KnDCTj&5X@4vC%tn=^5$)oow%ciP-_4O2F?-FoVUIY-+*0 zY=21}pNcf#gXo%Y?zJ5ifzMmc}!p7b040BLwV%zr?W0;|%;^uPhM zpMP(f{Qm&XF+(>WD&=1v_ne-868`|rUnM?6PD}1glZq3e^rHy}5m_6bi6#;Tsho_@d?kXZr<0wD^2p)KsDf^|TW5 zPsIb%1c%vz8?}HAr+`z7ho{957WN1n3xAYFQe1T|LfC85)dImQu(ARq%ccmq>|xGa zTr2HFBC+8#zesAZRx#j*C5e&K-Tbr45 z!sVI)GBSiCtQE!?n3vrKolrnRf*E2Yi-#!3XH2bI{{Uvu7&2dCH+G*)F<2^!7Jt<3 z%l!;SU_!Dw>(ANt;*=iYMVpU5@k1(9Tk0rf_i32)>(v$yQIT&p__~Ckm3CbM4Yx(C zFj3Miw(~uFJuv8<3$So+&x$ByNMT~r_d#?L!gqIDmD!97!CR2h;y35*MKC}3N7DI- z53*2z)O{`L?O0$z?9B8n3KYxrA%B!y!!+hF0_IpKl3$k}PgEL(5@&ubj-%p%rpw#~ zWe|@}E+`6J}SSW4ZVks(NASHHviFiOSGaZS0wm$FeK zxE*2pLI$cgA5XOej3g7*=2rWmmK>nBB-x6spNbtF%jtn(?)~uEfXW@2j`S!RsWE=x zz|3%aB2YCBSXhb+1PZUtR*K$dhUM%#ai3ZtQn z6M{UV;xtB$h%OyEy{NHDy9DC-^ufZG*z`Af&H)^_t%B5nRvWxmXn(L}&L#vUaA$W^ zT7KbC8WIR1c_Dwx4pD&!g|pYi1wG1+!Z(6GD{g_QXOO&y{{VDKmb0MEuS$26Q>hwu zIB2(L#R`-oMu!E&-jJe!ObI5aT`^S&UqUV3YorXufg8D<<)2Jliqs8WaPKVQ>Qb(O z6ve~-h8%-0Al!40NPjUZ!bRNAPM(-5guqD!&U`*Ap^DojOzwznB#PXGB;PiVU;VLa zK}}6=%#)9*FHl=Phdc4JH0gnus6E8BIbn`P#RaG$vp~0Ix1S6xmZAFF8aU=p+u`CyPphQN^}%Pgn1`S2}qR`OU+Us_KksXw6!^u zP3#H+n7Fl06#lSEC4ja(A7%)CAuc#P%a>KKF$2Jn31~bpMn{pm$7(9W5K%#H?dGxa z$M-z|t425y#qihKQyd+E2{{U!(Ol)lT3_xlH-UTI2C}6!v#ERqT;pvDO zf==Y({{T1$QVD8S*RuGojR>IFa>(1sxO7H?k8(mxlQFl4;ftXq2W)42T}xHbAtpKL z)Aqcgf@K{un|>}Eu8XM$jm&iC7Niyv0wradEUEzVT zDmR!*4y2ZrfMc;`>nu!-mA1T{>bENJ-0$-z5G;pYuf7&j4Blqo&3VOY2?|F}GrTfHf`tZq5OdAi_FQ~fz`_V@ zgFDOFim=5|Z5HvI)UIFnV2h67gs!(fp9~8FR0($;oG_80S#s`Nzr&mjMeAV&&gA@2 zaT+QV$~Z;E{{Y1Tz|3e^x2~`(r81t~^yd~z0)Ksx5c@nn)IcRj=Z5VQkwt0&RT;Pl z$>kC|44Bv<;SByLnt^(OV&3ya8=;dM8jYas%iH4O@f0e=cL>C&x1w^k2o_z1-S_)B zz`R&jP)6HQKAx`>4FilnxHCDHs9jxN$i+efQOHZ&e(14^n+`H6hD&r+!KR1uEvsjj zwSV6VVyCex+k6lr!JqK2cz6wF&NPR5t?#aO*ahot+EZQ_fL#MFuGH*;7k7uXBZ zd$#jVpJphj?ln1azo)Bh8($x(kN*i$$$8up~9*Oa*l)C#bs#mfSEMGdl1=k{SUJ1A)MzZoj?{{Re78j%4jcDD5ReW-zg zdPKX&_@Go42Hc*5{7^uK89wzJllY))5)NCJ-dlH6Rf$o{G9teXy)jn%62q3|On)`_ zZH5&c#S?4A3oV>$F)yiRBb+f-W3rJZv25Bj z136M{HHfXSY$vhXuT9^<1OsIoPD+c0S9N<(xSEk?WOVestDye?-W$0uS|gmfExX^- z1gbC-jqqy|a4f@Pb;)!#BBT;tNq-C&EgKPqvjU>+TR(;cS7flUbxPFkf}l+enzYCLX%d7{z`R^2m)i>t!pZa_w(g&X30$D9y}V~X^_*0&N?@G0$s*|?TvKjw z=A2PG5poz3@+d2m-9~@q{g|D__7u~~DUx+)SA3$THa;>QR}i$e>tJdw@_#FNZzw38 zjY-*oa@4g)Pi8EV!Uu%P(l}Vt##~wWpp=F!Cp5SCqA>woZlCC2i>QmP;^6-Pyh;|p zW;OVKiXbC$xPKH|_8^kaclKf_0XTN4W>*nq6hI_a9!n5|cQbF|7-hNZe=fxI5 zB&pPw-4KQf;SBAFLl&rpUM)X3fNoC_BYqQz4fYH2{{XB&T{dLLzJDP-Hw!MKW=q2m zzOdWDCCN%4L78sjCHA3HH7ZbW$mD%L5Zf2MmZ}`RTr6O=$cx3*1rl07p}V_;hlV3I ziDAsHU>QVcStxMDxh{W(6#a$>dyyiM@bJZ`HUTg;hJ8Qchu9!bId+^EMX3NU`NI^o z8kZ6hAn`+)Ky?CH?tk=(j=+L(t#$DJ%re1P5Q2`W!d}m{76_)uw~;zXNK1)08IKPS zx+zFGWJ)Y09!e#Hl6#<7rX7o&-Vy47RSF|PciYY+;eejwkxvuIn)skQ5!3;>+==^9 zQ#&sh1Tq87I--klz`RHTY1Pvg6QdUD0#|5^?B>5N?C%S25`PGngrlj$j^g4@fT+7o<2^jHOgrX|HaujzbHu{vqPO71{JtnqJ=Et_9IY)iX;1G-K zJ8$b-JyBy96PM~YNzvtT8v^}?`y-bM#VTp19JHzLH!#^3h8+0rVozr}(*W#lua0Ok zwK_c&`KG6W_J5zoj1_86=C`$#QjcILZ2?)W>-0Lne*kO zX#Hl}rA+v+=bwp5*-juCJV`w!=Tl~_2g0bawX$qDmO4q9KkX*OD$?u3vi73^+cw8h=&pZmY$}x?t1?Rzhk-vXd7S zwD_Yl7M3PxZ(S{tbqp&k4n_MC`#hYMrtKnJ=KWhD20~uLiE4-M7z%-zH{^G2Jgo%# zcsCL?(KOIpUug&aXy)+Jt&wPO zkwVO}HwJzEMGfpjC9dz;E~o|p`w9~2MYpF*i^-b03&`~L-6%PhAdlP zgRh7DFk6zDM{M1mT~I7o;gRng;=y6dL%d#?U`y<7Uqt@^L%d1pMTy&DL79m`+~_piP< zkAIP$$n`uf1}-x6`G0wI!}PVU)&;&Y0V|3!zEg6p&9_OXjEu0f189t8S=jp2q54*O zLP`2J?23A_@c~p`6=F@V5jeUgUwj;Wh9c{z`HapYspCq-)a>)y>H34J{Ntimaj$YO zl5&mwbzdW&pEVlxFZe$QOjQh9WwI8u5`QwzHB0E==0?ueU-dTD6T=etW>`hBxckVu zp-kH};Nh-5qC#?ToO00OA%vYILkypK#5BVio6^f{1BGzGQRQYgGn2#XNK1%Y{{XB% zjf<$6U(-{LrY5hQxj2wZhC5c$8PnNE*iQ=7ake35LYppg6H^6kqC6s=YvhreE`Mry z12bc^HUsF*OW90=Gm}Cy)u#ms$H;hE_)#N=o08cZ_L{I_(Y8`)Rds|z8cdVXcao}m zeZaXwr@10Dc$^XsiYSUl;>x$w_+p_lrT+kzmvH@_=!@nQPLG|nI>FQ`vr|Up%P{E~ z7tM2y#u(`nxE<4}uU4j6Vqb~rf`32}9}=t^=Am;nI^SiuG%#Kgm62c8wkdgZ+`Z)< zGo8uxGW49+n-;MP^p0x+6b8*AD9a#nE1-es2qSbsimDV_QB3+_V{9*ajex57q5~`+ zq~U>#=cc&rNQ$DFz>B@{bu!Byy?xMRvEXrAScMrUeE3odAcn!&D7eiYVSiTpF5$X2 zVevF!l1{NM60GI})Lc;7nhj@;mg$nqi`tC}Eq`$nL7<~G(Ry-4smW^=(Jql(v?ce( zOupv6Kd{3G;K>XtTb^~oCyro~bRt&nm!Bx+I?;}_MT=c47t2@byNAfcB+A0QyGWDW z_rN2pxXDcc$8Jm2dl)@1)_?j}GiaI{j8Qw6-v^d{q(ovF!Fv${i!CruGj*b7NF$ng z!dlKU)d~}e!ho{|?ONe1?uw6jlsP|QUZH)khiE=I^peV6G5S2kZiI47}D8X<9I zSY=LEHvz-nh!BqszS4Z5FD0i?_3DS$Q{ur)ZzTlA?j_=0__Ym|rGLU!X`DfYdlf!& zvy7Q;hzG0o`?B(lFU3KChT$g|bj~+=qLgn#N|b7OY3lR;Q~H&A#U8 z!fY+J-jw$&F6`l1ps&^{&wp*%WoD+CY(*U#IEnQ3Rus-?Rr?oug-Pm^nKUgYdINS55uc_ak(8mgpxrD55r&Es_4+08hHMq_XeOWpH} z)`?=k&L(P?UUA@UqBkYnV1(OSvM(3T8;d0c`e!z6qMR*G4Y z03#AC?k{RHyMG#R;w$e=UF!^8&-kM}MusrCGbeSl__;x`D`HkgL0@=Ogv7P+TLLN7 zz9ZJ@NY7K&p>*0vP;#3sQGz*7IdjNL;p)2I$^v4c(2t%j&}2^*y)hZq3#l08LFreU zMla#wD7*ZWRSuK%W!vMkvTV^RFFsC*TEUkPmy$s?R3R6 zun7cRR%%>c9Eim|q2ef;W(uTxk8#{C7DdusFf3j4Y#alPmZ?k`WwUuEHt_h8DUdH( z$5DD4<7sN`7O6;?Wv3=&$sJ&esGDAYlE)*T#aZe)9A|2qm$Ne5$dWSEy*)9;^wil3 zPt!>rw0{~Nt|1#z4iH;UQ7PHbnd2U!ORgUDilHskWL{~zK^)=n6kenjeTS7aZQPWC z(Jme?7`l?y9=B8r#8iG56`@r#G)%ec3C-uiUKqAbk{+X3%s(epbBVAO{umETVz`-6 zoWkRLjK$24Q%>;=NkY;|3^TRLRwJJ&H{JGGq&Yli64VDGn34Y^d2%Kd}N~ImdLE~?V6P0WhCaJ5Q zl}Upv#ujuHZbwW?lRAt;7G5^GgtYbA_@HkkVa;D4+K^_QFu9TTzlh#uC5xz9Qd(q5 z;)P`wkd$rgwTy4r&#H=7i225SxI&4wDSu*hdM>u_4si9t)6_w}^Vf9=7sJH@sKgy% z@@kuiS)I3AxVUY(BS#t88lkbU@g*i#5m6>3W}EF2vyCViTX2#pM^`d!MbBg5m(pqI ze&?fp+vgvVXa4}H>fxN!x!Y)puQ7-IE16H^KlvOEUO!E*@ne_CyNr=UN8*oAtbfAj z2H#n4@j)-R44WJ>%J8p>FR^fy6))mX7sUfEMhe?w`y&=-WY9qcBgs5531cLLDVlhq z_aW^>U3Q58!&ECNZ#WX;ux8v5<;=f^35r$Fpm7tb2nk~(w;o>?i>4MaSVBYY`ll#W^j(+{wUOkUTv03%LWSJi z8NdS67j40saeu=FVwa>qGm`z7C1OnI#^*H3*m8^12}Z}4v;0va2TbfkEZlHH9%>VF zY37_?6dII`u!7kTW}G}w%8X+>yAL&s0Za=#e%A~&2`^BBJ;?whALhnz+ZI|*^V4IoOfgL>X;5V(q-8Li!@wp$gbB=#4+ zCjh)LTBy2c!*3P$D8#VPuRZZ`M5%VE9W>z)=X4UOLq{B6W*Dj=x`8+gnJSIR2BoYt z1MK%6uu8){D<15reoOYE>>s6|4l&|U^M=^ZF`#a<$NvB{MD8_JK!1~29mFBU4^lCk zt?9b|0O5vM%<-cH@@KCX`NXJekn$Gr$BNP)hUjt=cJUNMxsFBC)ef;A_L&3Src3QC z2p*$A;j~T;7=XpBI%I@xR2@sh?840o-9wf}{bHDsp_&_#GrJ5((B@pNJnbj~#|)5| zXX1oB0-;7?(8*y=gMYnHPI<3PRY67vn5ybf6dcqrBgd>2MY|}WbAu~%5{SqeNRlE$ z&!!@-i7FsY-l#oD22VbI)J;gSw834{T~IX{rje?yP-+?pySTc`h8syM)G>DO^0w8w z3L|1CvEkzQxT1?PUKC#l!LY;z<087i#ORHTKnV}E3{@Acg?|UF1#h`JVnTya zyMuMh(okXc5M3I@RG}m#evri=E0m7aEq$AKVB!cIgoOHdVzni10k}6Eyg&7WBz7}q z?KJ#1L@X16gnznS!w`a-5^TvhIG|=jrp%Ei`tpj@i_`;oQu~m)qSz>4Z**j_T9JtR z5fm+f5o?k|3Oq%XsTCcFC|t`(vIK4X!!qiDi3T2_cbpm*ABqUkFJsNThj$Tum>P)) zvjcv-{g@!EDVK+tL;`yR-U)xJVzmU^1R~(gc%o_oM}G&r6by*CqN-DZm@JE5W(iaq z0dTirltHmO0!MIX!_yD|I{V<)sDQ}0E$aI)YEmi$pbu)Wy| zXT|$a45~_)d7Sl8iBN!(b~B&tcwikxPhq2;BpdFFh|wpo+2+JtB_9+Tk~n(&5D zN~MS^Z-324r-~YgRQ532+oKiY;pw&*s7UNRr#->N!|_9C6^WTv!Oz9wgoZtAcZa8k zhuPL42Mx1pmZ$p47+r!7m9SKDjEV0#+iy%Wu=P{L(WihK!i5pCq5gXEJjh(n}37d61D>O zOnpJP**2Pw;elf<7-Zvlo@g*6WExKKXArTeH{Ov7W(_W?N9}#6fkTH9PZzodY*1kv zxy5csWxfNq!xb}P4uDj-TNfS6>?yzB(|;6D@?46|c%b(Mq>mfRETL~CVa&MNVx3mT zW(Io_@?smiZMeOt5q-x3DSnX&3K5aso};8f)7T@@sryh-Auu7A&;BTu2urfI0VnNCZ^3^Ye!oANq)L*j*&HYr{{9xsTXtPMpsCR{3zAb<89 zH%~z4#l`zDN)ox}rEbt+u{#dc1mvBe@j%$WP;BJA*Kq#;?TH@6OAXFPjF*e{VVwhp!3^dkM~gn9fMIOF21k$lP(;3jy(S}aFSQHoP3##2zlR5J(Ez%@+BcbAE?QQ9c z-Hhx3>VEMFdUb=fNQ^fr6&tgQR}0hFpR4rCyV;6|BcOnnx5NJc3>YdU#%>6Tpnx`X z;o^gzVhyIHR9xcefv`x#RO2cKn)~>m&~uC&XT3*ADw*y!AR@UJhVB@ZAAbxWSzrB=!g{75Oc_Tu@8{Fs8_aeWHRG zI&FLZ0Efa*w=NV}Tg&Ba1&P>Dx4hHhNV^QEqA$O|~d zNjv;8YCBLRO4GP*;r5_Htw#55!!K*v@T^^num>HqJ<#avT+V>3#ja+S{{V^s(5i@a z!gB8o-p{%!wFD43(uh$^Q!tlEhUNS}4pAyqfVlO#FH~G8K%8@WAAf7`+XGSox66te zfU%2FZbA00ik{@Kk{q?^5sQg0a0%fD4?AIsQiHhzOKtm6Qo#stm%E6HC>en+2^ad} zf~ge4fJJI}h5|bTdBNk;rYstnRMb%(+FgygPtkAK4u76c+q^>IWJWLLdMr}nalF;vM*i%3X?_F@F4TV#ugVghG( zmaEF^Viz64G$rB~gw&4Y+gtAc09wMbuw#b9gGf?>#zaZM$$5|RiDD>bgc~s)m}gjO zExU_V9~4ulI)Lr-))X~Vac=GjS7lbfiQ&H}F2yJ(zRko?<$on4ZcLIUYXn@z!DOEQ z0Q$rVpxkW~GVkez3wRq4-K<{Zpbl`Ee-t%H0oN2ze)u7R<;yV5I=!E2AUBcJsN1PU zOz5i;5~X#hrNQlcqF6Oy;E3`bA^n@+Wrk~E+bg+#)CGc|t>@zK`&^=@xN^n^i>enh zu@d$WCF*n@sDGe<7ZQ&Zu?mlI)QJ;}l7H}&K}?13(c@&;Gi%E6#Y zqDtMjfgmcXa^to%@$lWM1K;9py}E3&fv~-|;;bKp>K|B8s51gGQvyg_3W8`kr#n=# zk447CMXMR}$EL?a`ej*;_Gz;mXwh7?iXh9(ThcBzf`69BJBiVrr&~AkFj~fr!ZpVz z@^yq_+uo3N--$&tM2^GGyiPriy7U?JkjE8ILVOzGS35dOy8SpuL2y;Gvs}NdUl$VP z@lfU26Q`uV)g<7nxrhBN24k)rlfh*@VA_Nl@W zIfvJI@qaIzBP1Jf`>#>f8yCgW^{=ieNZ`07D&YH9aAu$3jM)=H(pKFEF>OGSTNq@V zD|Z#YIMALV^3T+grPc&nZL;apiXIYV(2PW1r^~vZtLcKr?oLO!DNie<@=w`;WgtYOI+l>YgoC=BbTm=7GYt%}akDX8-^0;8b{!%>Gq{D~caOC=s! zp-)n{9&sHHktV;sGV|A^TlrzDMGM7p(@Wqr7khw%ltG*zWW*msz8o<Mw0tl* z7z?7UyEvZo?nUzF9Ij74UL!E@GxGd;SNs0}^gfqh{0~9c?iFT>hveFkm6Zzy!M5Z` z;=8n-zlXzIOX*_i8c$S-)5$rJMohVf(?-VGZX4`!d-r5LHvmCvTc7KsnsJQVs(K@2g_(T)RI=B*uBQ)%R!+2_U6jfP~ zr?OKMQw3lT{7deF$n;T1Zu&XwCQoCyY=CbBR9UGLgm^(Phjn#Vnp0Y>;yQi=ejJR9i!z5VTD(1Gm5)6t1{Vo>3@lG0TJpV zi4Ra8*x;gU_uZKr9q8l)B0O2ga}%s$7uer^je8e5OO4j5%-Z=6sA#+>zJB=N{!8); zJvfY+a+}4LdUB68By`BFf{74;vtMQg!E^=-zR<+5LxB`nA%etiT%lwL%YDnm1yHIf z!rg&Dmtq!S#Umw8Y6(np8-Ehpgh!He!KebaoOveyz!vh1r+# zh9YbvMt>7*(dpq$kZph-5g691qiQ0cinG?sI0Msfv(p4wtua7@GeoQ4hcQ24Ppa)Y zYLhbCVNaKsWJ=Sy#RoF>zHpqdrmQpZA4t^X9GdNp6YaZtIsjPU(NnU$$#q2tNP8TP_TU54b|$ecHLNRPkojR^Yj67`bHQe~Q}JHF7w zyu`zzd!pgr+p2rDhbOt6)T~I;>XQ?~Q_`-;w#>q}XM~Wset&S4POhb>S6J~Sv6d5d zdO%wNc9T3MYedkYT%UeTPmNUYd5&1Ondm7sQG4fR`MNvU+Bl zI^7+Yrd~0aCvRXkey+}FS^gMjrr=BLhAe$hexoqilU=2;xzY@qxj(EFW@sd0*$+^} zfr{m(gqv%#xqp`S(X>f>iUpZml+hN(Fq^|}GTVdty#CHv>G#6@T?bv5+ZHsIZi6pV zmz*hbWs8o{?@#lK?h+#$kB%|Z&C;rsSKa}2?8rn$(amG2!Nqq~V;)S6N<>8;OyeaLRPnjm>^GN~-yhkH77;ziJ*8Vb(0{nKf&qd{yhY5qiUnIh*F)x9 zw}cC;i|s=_Kop+BZVL?WkDtZUuQ&#Mj_=|sa+Xw@LuC@i3a(e-4SF|~4TkSx1 z3S(prI>!>5K4esHn4NH~mX{IYFUz_X^HP+qGl6!CwH9~tRRY7b;?IWd*<0+YCb5O{ zwD^XN^?w?@=&M*DBbLR}P7`BrkvO*J^3DZ$Jec_fc9J!meY72ezAe?RtWvH)A}%`t zJeRr{yxyNl{9n+e9{=1bXu|0G#qT0n|};S)Gq%3%ni=kS8=B}*DkZ-7f9Y> zw5~o@xuji;_7GFV>o%!qQBBlSdy`lr5PefsY?~`Z&Jy^MVx`n(C|L40U!z{p3kPkc zn({n*siN#g9uhp8Pq#s|e%!;>4qPZPE*C)ewLIf3 zkbg~DH!bWJQ74*3S%!Z|?o>`;ctp$W^u?mI!l3$XUk8w}}!G=PN!cFs5e}u*o?>rgj3pf*YI2T)5xVICdPgErDhiy^aOT zf&e|hVRF#U6*02P-Y%(3GV_f#@qdeMWpIy4M@Nl1T8)U*q;4seHVC=ABQifwQ{Bn z6ZDFDGRXQtY+9*Hb&&0{t*q38z9CW8-_A1TiZ#BbeAw3_)sKdF!w$^hnt$BHjKe#b ziC0OZ5*uw)(PP(exjw!t92ENel!p=H>f#yk5v{}O9DjEktUk3}98XWx}}hFa5B&jhAp`aa^nXQ5XZp;PYQ}3^5sm z?>$KW0Lv6C_Uh-Z@c3YqC0&ITwKBHo2<#9>{{UH^bTkzg*+jLwhs6S=*2Wc;zSV}< zrd=L#P)SLDW*V#s=VfgxRCO37ZIDjugD8qpBT3;M=P}`isWk>jcYnW%4kTNq%=UfR z<EqzKhngo%x=;baz$!*pjn57YvLHKC^kGY;^JhZcf;a~ z3BAW`%Wn*GxV^7x6FnmJugnQvEITe-0;}QvkyQnPHhnO*LZ`Uthr5;$tpbubY_00+7#C5B z!i19O37r-x!GGY-q^^V&9e|&>ZeQ7og&V)zDS{DaU)lG?ijKlu>&Rh>qux|Q&RcMa zK-67;OcmCz`qB;nC_&f06u6z3-&)Zm8mKbEVn1kL>RQ@F5ZoE3O|S~1Dh128p4W@) z!=gpxfnw%&32`h5RWdFj253hRl|8%?`%!A83325R;^O`onvs8zDq(^_T&@2Av|T}1 zSv|bnhMvq@$$7vMk}ONPVg*CpTCW%RLU&TxloOAL_;_xI z3II=91gbjS5QB0d?}MpK!fry=EVBeRYvF>ZDhZdmvmUP$UZA#6t}5r3PucLqj>RhI zO1dqcy??a@3Ne3MK+@gjL+>uADuN+%8VcIYvChHa>563_kz0&>UKoX}Z1atX zhDiar9ThvJ9?_5vesIE9HbAq$g{e$+WY+=>@BhnhfY3yBt_ zMxGcL?gOU9ky_Y~b-EWfV@L~u@!-De-K1h934smPG)&UDO!M660W?zq!D5X&ENbn z6^K;ZXFF4e_HB=wKe^}?M-4VSS&Si+ssZ8S#LC0T&Zz7CE91s4N!C-X4_P`Q-%%^K%k%tAjTQBG9^fb*h7ibqXSZa zIQ^U?R+JH(6VE99n1mC(yIunDz&w_tS*JH|XYGG~3Pgq$z{SOI_340M(0eZ83H|X* z;k=Sm*Esd?!9>_WD=y`s{7^I8xM9mPWLkJ(svmI8mwnnrGw2#f<+Hdn_eBwm@057> zvV)1(C%2aIF9?^j0;B@;#lK{tt~0p8dF|cF@kKL`jGCG1Pi8CzjS-VmW>0uO>jhCj zo0xw`H2trN1Patc&kplWA-ndWgU@dg{;!9o1%aqW-?e{+1S2jzd5R=lD7H||k}2eo zL<$5%eOg6A1z}=DMoPYSMK&*}62aSg?tUl=F)L#ZId_YMpkg*gjg8&{aP%v_)KCng zcbmJUVPLw9GFyc%;)pI_%O!5B#SkIL++KejVh|v?Yo5~eMQR8cvbf;@u;5(d*Y;p+ zMGDWX0#zOuE7X<@*;-4309`gwU2Ns&_I zm7?M4`!J}DlH-vDadjRTwhdg)?{}x-f+Wd}*r;XgL?lRuCGOQOhzl^)=JL^eP;r0Q zi|#j=V&KcFER4i^jN791c%fxggtsr=onF*Hw;_g9gf)f(HIS{gWvC2NsUa8SV5G^z zwLCmgV?p+QKA*J^gIS4CZ1jP!)a(KFNL0LEYpNMy-fS|PX22}^h6qUA=0?o@p@onR z?(2L!F-*YB2{~(ZPM8hswonfiK$hRd?tV4UWN^a^w_W+K9`uLIoEPVyj?k z5GF1U_+X_-{em{)5bMLs@``_`wT+4r5#bW*f{0>vJ#G3YPlxQmFkpzT`yy9BjgICV zaa{X0#m8{Z4_M{z>Vn(|dkbuck0`N0F1ftR_F+@(F(ggdv{?@i?7?nBg)&d5u4E%< z!EOgb0ymU&NL#=b12FC9n!u_AfEC5FFX4xT{lQ;vhT?^lVGb9I#SDKGstH~m6}lHd z^#GFT)a0l@LR1*@zJT02Wbf5H|z`vhi}ZDx?H{U;J6YOl*M2ONACGL7tO4yLrT5 zjN5LVP({L+w>yywi>iMDq-fBQ646dZdNI2A%%)A2&|om#<=8;#s|FF(Ex7`ki=jgs;n z7&w4i9){Bc)%JR!ib#zO-fX$3!4$@2+acxnVPKBiY(&dH4~h;WMbviE?-tbJLG$p% zhB|HT;^`9b!r6aDAa&x^I={*mI;XC=oR@~^rV&2oTX2Ol_J#&yL{+hyZV!mR4Y5JA zEDh!g>v*CG4@+=|MTkd|(S+yzuAFW3ai;q(tP(t}6-{GKDA*P(7&@?X)oPVec6+ov zQg?b^)AvVnij)I4xxKL`q|~IxqK+TK4l?OGL!ESzYr}tGautDoH}id42ah(EtfrZ# z?ZRH0Rc!ZzmB=>ua$h)eX0nO&SL$#5hHc605$N$=la?=2ORFT{lU@XHIeGIKcL$g0 zf%<)|h2U>b>YYNWDBE-M7Yc{1Wk@nxk~cr))pELz(RAcoP-!}1=4?EoIxiU~ zO6eBfAS1CSD#;7ksK#)`R7AHVolv(S*B*d)fN_6b$uVUF%Z;3#0QrW$zBxSZmq#>k z*H%+<>^^X&FiUKgT$`%VMRL5!gXl?PDP7}b@eB%ksQM9mC&W!RjHP7At#@=y7sYDn z`-&$o9`G=)Q~9bs^r*wZh_*RgmQlGywRC?d;mq`%oCwe2etLa9@Tp}m`l z7tMb;$6E{SPA*hrZlgvv;x8ezz8;u(4>k}&x9j4AQEb{g-Nbp_5s0QpNyXR20IU$J z6}?A?+Ja&P5+VW5tELEAsNsQ9F<<@B2`D=YO*G=RAQcZTH#piL`@Z;usXK#kwEi@& z%hM^6Z_iK88VgJ`HxV!Sd*B`PFE=4m`j3C8UV=YG9Rp=_s(vooD78?p5TmV1mlIOn z{{YnMD&{Pfla?7ub1h1+*xDc*A}JiV$f$a(c(u}{ zI-yi`!?{X1`EoRm^p9bAa;;aT+D@CUC|MnJZ(2O6NXfPd?89O@R=O z*11({{{TNU3o9x~hgH{MI(in~}L7T+YX$@a3d1qbA`v1d#{;9Ng$b(*YBsQZZlJ^<1g@G^{urCNiaM zy;iB>yO>kD%e&5qFYVf1-xWwa5Ws5aA;dBH*= z>f9%?k;BiQ)P9q03{~o5ZX1^K9;jKOV`fCWw2P@KWP0IjMF@^4^DCfTwo0rSM+YrB zVO1}~DV$(`#;2 zMnnp2!oKq2w>UZH<*jTBzRRSuTf0Pfe}>rS%h(%Objy3aRs?dWR>-%J0Acbtj&kP< zDknk1hmgYt%!C5q$_Ak;3v8{?BI0%xf;LEppNcHI0IRvVlOH#p!A5Qz0dl$EKfapeW)C7w3!6&@k@;dK~}$>K*)adRZhoAoQCZ3JPd ztq5JUe7y7L7~Xrymr0!5oU6<{)Wp2@WG=Ac5aynKOYV#ktpk7B&|0cg)-eST zSXSMaaEOGai{Hy6^GMUBl`~B2*X+oQ$7+(4d+Q16SdfHq z7^H88e5d?Gq`C-sRj zi~j%-omWI-Qx26G<|YVxRJk~AOP>gnd1y)Iu_8Kd!hhiz9$+T z=%KbtmbvE+PD-zkG$Lw`HCmZ@mdU2)E>dat#R}Z@x7`_?w%sMxreyJB^qc`bD_NXr z>6Y5HqtHat+`Y*LmakZ5q1tvR!R_{W*}AmAvoO`7)T`UqUteVrJEJ?{Vcm*VV;of} zvJK5p<>n=18x?;Lmp0dU`uBXHzLN`lwDKx%V_bcVo);dMIS?GI5_6~FSZrfw7y5#) zwk%bPFiy?qrY92e}1QGc_b|@<%O-DxsUG z-h4&H)G-QdYbC=E7V?JL5Zb+0L@g6Tf@1DP^7vwyrhNl;dR~nuF*7jc?U`1T%!|dB za(v(|prf$gV)zTURGh_?`y8C8df6c&{{YFhI;ZZAZzCzA8Z4GNJoQ^|*oW%+1CsRG zUilh+tu%i(2`|8%@OU`6a`hSWa#tHwSNzTUfIf+QNwf*c8C)&4Dh$L&+66vBUcDx2B5iTW&mwriSI^2h z_~!jBu-zH_PHfs19h>xb+UqltE^g@cCsTUR{;YpdS5e73le#u>?F~{Rv~;n$%BBv> zHtSXpkfB^9aFE93K`zQmNK_@}DB`5y_JS@U@RUi7l)mQ3*gav!JjsY z;c}Z6%WTByh!XtbFo#_jldKbR!m2$#7Ze&Yk!U%l*PZPmqr!y;(*ssiw!MxPgHoo= zGUb1+OUyF;-w2rJ`=c9swM|(18pqO!JZ&a48Crw$3^FrJHtodnTdE9k@+VbJtr^z! zDcJMVdjM?COpdKwr#4NB@Pxu#Uz{hUe3)wB+0Q#h`gL}Dgl7n7#oC-4$Az^afc;)5 z9-K8bwNvTPo31f1JsKU$X|Ems@oyUXCgKr%QkA zf9aT}`e|XIqfAQuPgs}YHVC*~`7eAYoyx(Hf0P<<)_RoczeIcr?LQ8=h$dUL+JW$0 z)sznt9TevBXJ+e{a_5N`msD$3F*u2WNrMnxn7N&o5&>K}1r)n@9?5k=+aApq5ml(R zRHq0_sCs%zGtM@Fc|1xWxvonZRN#Nh?T46dUwv3_G2tE}i^<>@9XFJ_lbgWK*ypnz zju3ZApuJWxw}d^Sh1S3`Xt&a6?NV8=2k}9w=tHpZ`<4g|=t`%YLT_aQXiaYG#7H_2 z#&%P89IuNjFnJ6{VUqPD>k@X|!^AMI;`s}599&gAmI2bIG;-Uvq#~poH06KV!iX8S zA~)_1!G>WINJ_GWsUUH1?O+eam2CC#N41IBkih1Lo!Uc6+zJ-U+UfrQ3@oaPyQx+oIt+;ycMrm_b+ETkM7zWI z;ce0l4+xJh3{K-a4mx#2i)INzU3Rfn4#qw3b`sIE*9)A!%nGm#>?D7j5j?H%!7&U7 zt>Tajc6=Fd@-)bPrR8u2mxx@#sLh@e^ObtfMC`QKH zdGQxi5P{QTLKd$KK$Kf-Z#C)V2U?WE#l6m>%!}l zeE}F;RUtumYXBUbXtTEeSZ9>sJJ}8a_M7Z|vhy4sJp*w%_$VqRv+VI0@K}6>8 zwf4VsMvVh98Itqx!evERSqP2a3@)Riuo4@&ejk(_OK)K$=6a{%fn_OJ6OMU)%q*}I z6G?e05kx7NY~;@K*@CGz0h@Z@6;jZ+Mg)`CP`SL-3x**O_U_(LaXTX@Hysr}iYy8M zam?KR0G0_x!UKQM!!+uHRwEhT>6dgKwn$L}MI+J(FVWyccHBkszHA_3!3!9V;OZYBW{cKqNN5x zeUA}bciRzDMF`mRe`NQ9~fqwUT|dzCd6U{%cH_VTub7J2%~>-1oR`~i)#gWJ;dG1b=P}P3^goy z;`4zn`WsznLGA=R>_h;oh;`09yg7Jal(tQYd9o{V;fWG0kWTO7`%!y<*VrR&UBtab z{4rGuB*76`rwm@8Bh0w29=xuJs0D)!qiRJ=kqnV*l0;B~Z ztxKV%OVbb~3^M)R)IeE}IL)hSdr&nadyQU|QasiO1sO~;XcG9mUkoW!qBde))6Up$ zVx~3^c0w7%RTro*QvLTs27-CN#Jf=(LUu${ zgF@EmF^t3A+7MhnW*}-~cfd%4)fd>6J-f6rL4t|cBXVyp9u6q7r?AdwQ4c7D)C^uC zUk;EgAaB~lQ&<-FAUTP_Eq$Nbfv~g9%nOT`R}3kOVf4L^DBL_l6d5BY!lRLE3@U$P zWTdJh{hPc|eMMAvyWNGU_I}J1b_hJU?$0$HBBDcBSsk|E5$ZQVuzs3~bD&W-@pK-FIImeO9vg_6cfDy9}U8jl;qNlMFI9a$$6vd4kd)DTh zFbXY{OOuOcP|AX0EP-AHEoVNDsxV+7sfFxIaCv}+rJD;5Cf8< z#lr#*VUv<%N0R>l4~8L9!#Rl+-f7e9!K)G!ZtSm+@a1n0oE3qd!&e#ZwgH69>_d(3 zE#ZZ*&B!;UlETZK&?4qE53={H5{sxnk>ezav2>8F%82O{HWYI%$OwOB7gCwFIBk^P zYs4c4QwQCV2eTKsB|y0>zqJ%lB9lh-UM^7ujh~aZTVaC+&P&VcSONxghgMKWxOsWWZaaKTtxAr~;-@1M2yqfB-gxf?vq+OIOs0Ie*I zUg5Y?irBdBR+a{{NW6bJL6nlCR~fK!PxP>HI~ia^Bx#aa!v$hkLzxI^#SBIzjEQeK z!B1i^p?6DDgrIdHqz1a(V&V&7V(}J*)zxo@D3vby2t+C#_>~P`Vw)ia>Hh${5pFHF zJzktK1kg*Jazrer@92VksZNO>oU z0^qWqM=T9_#ms05*iP_AhVCw?x|$(Go1dHz3?q0W#Sjs>Og$<%^@AO{vT#73xyI+U1}^4eS~y|YG3=J3lc?o=hJju zL$!=}1$s&)Ko);aC4`IBbV`*Sg^2G1hr|0&Wmp@HTwf6Qd@%wNH#ejU5F3r>_MnJm zTj0rhe}*Ab*zrq_aR|z1b7|@^2_1opcKBii+#_s@dc+DRU`d;`UKoG^CT!QmNO(wic(l3-rD_8Sk#qY|MAQosxHKZn;tXDsExn+k^YGmkq+X-KN=1_N z#ndLKcFTj>ND(T+kvdTzYFM&vG9Y zUOszANPd>f))m{7x($e z>#)i!pG7`|yd#FaBDSZ9Fs?AEHA|4GPzmr$V@N2lUF6;$NqU*?-qy(TQO*@yRo zb-w86EbXWap(}*uf_jot>(4fgQC zG8erY&*<#M?9(>h(cXp@Rj@PcWL!}@R?DEFNgm-F&28fU0G26>7j51f zV1OQ8_K8^opfDp3ci)gVHE4)KeRh@+G_1x-@n(4u#8) zPM8LA4;yELFBA|}F}oe-5XFB?Y!Vx2*i3Pj51yxot2w5W1ihUiMC^2Ab5?pe`YX$9 zt>UOucT?3e%s|L^SWk3meoHu5F?^Wad)4LmV>!CUcb@1A7hsLdM1~_7aKw+=fu6)u zAmEpDUGzzfo+rj!2$Zcsg-hs`DOi%cwW-X6t!2KL=5llI#OLPAv6z2bbaQ3wWF1Sk zD!L2_YCCU++@cy$Lbl+8VU$Up^P>OrM?p}A(%>bF9Jget`rup;%ue9A5$ zt2C8NP|9`(v+A{AnX>A*LxR*S>_`%ELtf|=HJ1*ExJw6Rf!j*=YxFdKQDikfY97J`{PxVL?Ki9 zt=Y+G>8gE=)!8H>XxX@(1n2vqQ953br?AS8w8^^Vc%-LgT5QWABWP`GQZr6ve~nEM zi>IWNm;3!%D+V9)GM*$$u_z zhJTWbh|wK|<==mtke8TybvX6bnGXqy_vPgSHlC`NU6%JJlTtcVFy~@v3afF1ZNl|JnwAk7P=%4`o%dCH0SK=qs*NG)PvU>g?XL7Yh zsbJnjB-3i+rRSQ%{Wg{90%V>40Aq4hj3hLQxj$G9M2z@Ik$gHsBahS64Zg&6H_;~y z(QR2eY^^P!;T{wc6#1+Y@i*cWztlBb1>l??ExfB$C2eopcrF;)oHipX6-PGRviDAM zQkvwfSyap3ap*m_qn4i=C|yhVQuVQR$K(!yLXULV60))@-2R(7FF zNsKVuqhtm3lxED+XPI6^R37L^oX`x_4nJ9bLDo% zzXDE^wOlQ*ISMSbLUMLaUE&uHbuW14Hs6+A@!I3^7ZRML!@Rv^M_~S+lG_LL@Wq=@ zYDRx{a)*hSd5zF!CdO(C4kB{ThhMa@I3Jnx*_SVi8#3TiqrEFVCNQt3r=q1p9#Z9J z<#8?-pAu?fXm?MNC8}bcXsH9v#fje7m_{smW1GDw@Y+|l6)RhGOrDrwsiup>NU5%W zIOjO^^D(ljq13i@MR7*sa!hj{dTAT+d#`^`oY+ax{TxSz-xmx*B51knixzeo!6A{` z29id#i}gbtgo!-b@d;!?L(>u%w_B0pjf6vttwR2Bd(adlWHF$V&7u{gFkYZcx+G_- zo71sNkHS9qOUT?&qet!2ZrfNKS2aPJ#novU2|R&`cAQZW1wmBTEn7FR>t{Ikpd~;~hZ2dr%_ANIxa*NKD#l^$IOABs00J+JkAEV7m zWJ=cvxUS$B4?&3eiH3a|W{Zu%oy@=V?Sf7wh0bjb2houFZJj04gum?)c*;~}VYO$` z-ey&t!bRM@qKW!+g1vTQU*av8&(eR)h1oL{@mtjjn$&D$0ki#~PHZ~(PtT`OK;hDW+mJdA#^x|ljjutdU+hDO?d++H*OM;+w6bwLYYC- zgP5ESMA-3u7zQpNVLvL90sJt!mpt6*70@;J@pG#RDOUKmwO zV}{;bp;D#9=x_&oE_;mBjv6H>T}!DGhzN>gQr?kR(~Q%J2xo0xZ8!)m)&q+(n>BJ_jbr8epj0dSC~c*1`hYSfEv zWhbSC$B0XcI9ZcjAxvnY`bTW0Pi-3_s!%5z7dOojG)3WDp>p-=6!?+xdQ5y;+Lq2{ z_G^x6R9V)@W+Y!xw7N+Q>7mw>a%92Jj?G;K_P?Jouo+ zgjVj+ICy=SSS%-af_Z-=EEQlcaHeU_CfZ7t4BLCd%!c3M7$sd2J;w>d{t|)i8m@$w zpX|g4&kyXBRK~%vg%aeE7o?24F}b+&Si69+p-I4bEK?vo&jNXrE_($&;aeo{;aC7w z4>&v-#0u6t*o(R?@d)CFQ~W(pdVv91Y+1E@R>g#)sd5xtmx_N0NWq8SZg$!>j9Vd4 z!tJ#6gHR?x2;I)%hol4r_Q;n%5a-1WP&Xn4o&rUn$Qyioz_G9*HJj|>b!C501W=AXq9BsM%O+n!@}u{)O?j5yek zX#}cOflM{WlEVpMyTBls4@mz2D4m6Rjv+?p2g(UUx7Z_Qhoad61#A%+P3DEe37HL4 z@%55*53}=%j^h9l`jOA71&AiqY&GIY0uARTEew(&2={-UUjG0O3_w&syKgQih3W&d zF~%v~3oI(mvlf?fftE9F$Y$_M7?I5{vH@%Nd^m!?1xw-8BLfb?L4iz z9ECz?Qnlv{B@DJA7ZO7YZ(u^r!PEZ$G!lz*4UZmc)fy3sh|p%lOHUS&a^=KW$y&E6 z+B{KA;-P;GiZ3PNi;r=X(7AG5$ggxsQbg=9k57xk097Pj?1xqSFs7CxE`zq;d{Yw` zOU!Na_+Upk;kPp9!*30+6FV0Oc!+vSr>X`gWOUJn%S@Nr@WI#$)X5E}@kFrXRg)b* z{7^;JOoM7(E28>F7$$RFIrV+eHXzmZ^ZO)FQQUunOH+q2;)|#(y9P}0=A1Ubi}b;x zVlcwv5$kgS%d5o|s4jxIb1y5Pbt4544WDO(qUtXCJ>b-$Oa;3Ey6s})v6+O6#5S*s z!xmb$j1gRUEHZ_63A{q$FBC15qt_A{DqF=Bs33wP$06Ia#c&0_+VJ5Kn_WlHL@vyWh3V$ZURpf8B;nV zj#RlQ#8n5VZsQ}vnDs@}l?Fp`mR&#A8xR;58oTVp$6)JGQtf~3h|FEdSw0(4dbHLm z!CaSvW?s7W^7_f+9nuEE8J4UZV2^+bjZ7;=BxAR(Xa zMek*^FjfZCwR!&l3=<;*+zh~n5Se zRVN5fgZ(5yH6zH=nvSq4feq#!-G*piY9&R8l&A}eS(n*@3M7BqEKHE1qBJ(P91PO$ zGEs_F*bN#A6G}^xbiok7&JcgymxqP{fv8sJh>s5HkyNg>b^k+yZIEBlf&d!1@C+;ECp?_F|?s2=%h* zTYut!1w$zJrI|vm94O_Szz6+Z39vhXWyN=tQC1e&TdZA*XBNx6f8u}P;((bexg#p) zoC!!ilXEfC{{V^?+^13etJi2>Y7en0K9`)d$1m#uM^M8&L}HvVaXKQlj}zB8Dz@BE zV5EbXnkN$_>4+Uz5t%%BtX%g7pj>Q<^ZjSx5}r-X>)bww;J^lJ9}277%QpxzqbFKvu(f zwgj$-Gf+oON{KFiw#)$ceTR@kB6)Z&?8d!PwyhZMTnb_Hoh|cqc2k^jHU-b%Z zPV1sru9XN(_be60bc)>Psb3JaK|`6i7f(B3g8f1^YE*C2?R+t6<-83JRD|>J!S7*iMsG`6*)M(H$_hG4 z7|b)=Q7kNG@Z5i7snzVk3;y+K5UrOxOHos55u;#_-nx!uDStFXZDxX1i3 z%7alv<@zU7RCfpwpLN_&WC1ZSSrRY$NE(9>44x4`bWKx|^5gu0BFicP<6-ms~oCyS*g??8>ZU|r&?+-}u{7_vaJZ>F@m+bgTBryyN+lP%@YCdogNPW5X}OnGX>`lW+{B!^Ht%pj>RMv|>~mfhk?u`zsXXN{H;4 zrhPup#d?8Opxl<-pW%i??6y%pKe{Yfl3c)`YA}CLrNOVWbP%^N-kN+cHU?l(iWF3_ zYNCgT`l9Suy)w_@;rYa31h;$D?&q*{Aqi$taoiRVNs`2##IStCCuT`-w2v+=DK3*7L zDuKkVh%q9efw2?JOO6&2u%on(KWZZ=Rx-gY{_pDo!Ssr|k3SS7t!zg5MDw>ufp#$1 zsjg~T7EuakJT|oUY5xGj5JNK$Nurq1M}>djQMxK;Lr~Cof|67!{M4cKGHO4*1zN+B z1JP;$Ch-atyTMT}W*nnxH<+scyjj2p52L02j~%%l2im19?FTnx?6{5A+=c{}=ALo8 zhV752%h(Tn5!3>Du{;*2i-_S0XP~Z7&rzj(MP_YvG4x~T*V)|G1N45x)fXEr?7cXcGA!=Q zlV9;d@jlGBX8t8B4$(qY;(R~8>GrM8Jsu2NIj+4%!`+0|9+xufY;C*5QL$7l?M%?- znx>fvU2zmJ2%3-EngN?0AnM_WRONp{!pOMr4(XYPzQbx?$}>Bx0}b^ag)V95n?aX$ z>v~)pV!dP?ir-S#u~DAc{?({;cWq^PsTgo|ajW$kpV^g*ZBf1SnL;sn4XLI!u}xNHW#1mA%?pRbsG@(r@W!6Z zUnAX3_d==#PJ<-+^p&DtB1q%%an;#>ynhofO^=g*O*Y8Zp6(!Zw)7K8fGdBC_@ciW zwCQ2rk$052@}gn8PVTP{Om%E=A;gIB<#!F=hAe%|lXq+X02DB)f^coNAg1XIEt1;3 zJU$qyQFac{-D=za02Ekc#ur4qQvr zH-Mkb>T6g04vN@3tn|JUmzt>+)03XGhr-e?BuMgH&Q_H5TvWeNvKxP@DB71t66aY* zEY%2-0azi+Q^a429H6ndwgV$=G@Mbo;{BLnDW1mCWxrf0@gxy(_C9cJgAxA#v`iDCb%gLiJA@$OvJw9CK4}eURx^0epqsfg&GZgov zk0)C;?)IT9nk7QZwxg;*7Nh3HcUh!ELQR88m7)ItEL2X)WgdSrDP~C)8mQzzNHyw+ zR3M}~Pe_pCDkw_saT$vovO-0qLX=x=@x&~0VuZ;yd`M7e+96Y|N|iP1RCr6Iq1dY? zJh7rYp54ZcNTg6sOeOgtMAU)YwQT%^nQ?qXNg3UGjhtrDvn0yaMHj*`y(>}e)H-qD zY%*B3B8(=b_l$p$nL{@usb8x~+s0$X?7?mdsZ7OLI^IY|Aw{eR%rXMm zWdWi1?m+r86-r zcc7ov2KowAE(NP8Q+sraBrMJYvg3ad3T2Y(=M2Q(Qc8c7OcHW-hK$HbgsY`zF%eAk7?S&Lk+Fnlg!aiNFynNWB<1ER zjf(b(jornWFLqG$Twfu1Q$*EbM-?+ysU{u6va?bI>KhKsJ-Q-G1v#fJMhVyUh)jef zKhet^;^}`eZxG^!spEClNeVb^C$f$%pSmeH*<`28#h(fK3E^6`N?p0h8LUw}C1@2z z#e9AF2PB3^0h~i-`!Q#zO5n|mnMpoWYCmJ(v7rICFA8NFG5$M4G1FsS=~uT4o)g{FUZd_l!=5<{bmpvvh@(S@oFGl}Ws z=z%l88^pRvGlNW8F}v+f4fQiS9!j5-EA=1E^pug6 zv8l$jZJzCqX-4C(3&f2*?=RME+jHA8mcUKAgS3lOJbHnUdW0cHC!w&rJ0 zG}-OoY2om}#!*A&W(c^y4-_2r6)~{RH@xcUh^SRXNlA-`nh)(k1a94*%KcwKlOkkX z0%7N7m076= z(u)tXjj_M4OywLx_nJo>@mTk&TcnPqUx4XSC_cMlGS#rzo_XRWC)I8K-Hr zW>Dp)ffR3O^F``SJK;;nS#Rc=CjvQk$9UR@J?Sj=HKFXzn}+bL(@soE(8a`dMp=L3 z0myPhM6gcfL_|c3e7GUHf9ew8(CcX)lNEieVYyr|P0Eby)2P5TCn}5ugu3Q}o?dYy zC)G>Icn|43^K@N=$l>nW8EPqXs8WP`D?)Af_2N%w-vNJp0;{r0fiEW5Hd6BCD>5T%zOv@YeACJXB&kwa zEPm#Tou7sXwHT=G&!`LG@WIUJs}Cs^9@ReDAlgI1L+wSBTb8iT*BjRd^*P<*<^KR! zP4qTAh^$GoIi0CucPi${kjw~x@Y`x8i5wnR_NQx%#hVWLJ+dGutc}+AhpK-w(0djs zJ%`Imotu#OxO>ts(qRzA-vw-1Rz>TPkm4T<5^>2s<*Ch<$?h##ar;rBu&AZ15Y-Kt z%L(Qp84t=cvJHnw7|m?vT*EER-qcs761Uk(mMqxSIk{)1CMCwf6)v8b$D0zOjK%KV zC(h4E0+%hK)EJcgTO^`3#%_N)G`CTq=8jWnRmZ6FjkpADn`DHFJR~u(TAfNn%+ca^ zzn~w}agKc$r{w84mFbL8hAyz%e6D?l{mb8%=N()>k-tuUukjwui$C_gC)i&`e@$!) z>0yQ$sNpKZPdxCVoRg%2>mKDr*FSVQ@UO?IOcTar_?uE{p=ss2Cdhw!grgaayuhr| z*jE=+LaKl!CO3#frLhW*#${)YS48RY3?-Jvg?$?bXET!XRM!Qv4fWhXcN7C+(u>I@ zk`N&Gc9}#VlH%nQ%0U+p#g>IL6e2x{UN2`M7|O$}Mw&pDG@EYyEz%V}#jlwaIW4)< zx)`i%N7)lMO&1Q~;fa4SVQ7zUPUR64&1Xz`diy z8?5o?2E8I}_6mO`Xmr5XV2f2X{3UEis48TOgEHb6rfrwlQ*GyNT_plUU56`o*(X#3 zFcTFLP1<^u&@2Lq7W>zR3sM^~=G$I;1Od{Amw#t$WENF3W z&&3diMZM;w5L3veWW>BMdw|sOGQHkubXY=~Z4t7++JKJa1`UX&Ny85i^1xnd_VZ5{ z@`6&8hOU1%Y2|K%1ktiw!40g!rZzcTZ>as4VuBD|Rd0sdx_&5AsBzfAJXexHZULU) zcbm`O13kiy#t4?kT+YH+*xF!-k3RSd0^?WvqFAbfQ7#y^E;||Kd^ZpHqExVw?<)zN zumMzZFK`##5S1|EpCB<iiQP`5nFc; z*@~$Y5jnimrY@lYCin|JcR^}MHzxgNonH(!M6fb$;P6`ks2l;o-aIK2P%Om3W_&gb zUtp#MHas2(t-vbAOjDZ0Op{9>NV7b>uM~d}x(RcKkp3uu_7do*regzPjCreX$$ULf zLQD}_e+*MY5L<347@`%tqEBKTn8_RA>gpJ(l&m$<@>2bnBD;gQNIw(=_XU(0J;#zk zk|RRfbdd;n3IlR5K@W1p*oT`e_3?E<0>(+qg5L}bNJa!jIl*sbZVwZD5mDGFQ9XZJ z+XrHgAo1jb62ub%%s~!t-JcXiQW!4|aR8p;NUmJdoI-#&S6Y{Npfw`_rw{zY6H-At z&kuA>NDSs~5p$^eV8wSl7iXQ(T7in824?Whe}rPlzQIB4gG=rBpsG_&g}1|`2B5f| zgz4exx(9MF$qlD%u{R_JJZYM9{*ixnB@b^4oL=C7W*p$TJW*r<T9#BI~~VZFw}o+f|p;qcuO zAi0|gmzA)`+5Q+cDiD&DZqT_$tNFmHhpLhiZK(j~1k3d$1EXZ$J{T~9J9370#111s z+|JD4SO*$B@(61Q){Nshx2`_MUat<3T9Gjo89Bl^FNPOt zkn&r8y#CZwIR|pCTjGdStV@4W=8MG;mZ>#QoDy#|;fGjXa%xr14E#|h2C9!)5^;0! zK!$^m^?E4Wh1CZgipVABEj=LAUf6Fm_HGt_5r$N_u*zx6u3y6fNS%=LS7css2{WQK z>8;L}Yu+&l6S6XSo2;cPVGQvMi~7T)7- z(QVK|Dj~NnJk+2iOQeo6_@g@!KvX9LO;g3BTCU1u*ok+oT>k*pa4qaMF@1+p?x83A z&Y$z&^WeZJupe!nTkEPru`g%*+iWbA><}KA=;R=YbudEb)eNbAE=h#5JTlYJqxJKgAS9ossn^AsswXdyojJiF<&Nd|gnsE~8?UL^A&XT0+>Tk!^s6 zmtAj7u|?DBp_^nwlrSZKRupWJXq;B%`9uyN#^q8A;ElR2JzQ48M367JdriN(q- zMUK#k#T@ub42q_ABbCs5ft64$3O9A!MFH4=o@}^5JU_(^sZSdLH7}SF{{X9?bpqNp zGI70rAF~z~q;D|JT3f>q7-59dezP)&8#u;ozC4rsF#u@T#r6b$LoeckiP+t2Y~-(( z;fseQG%$Iabnx{-SXjxhdfT%~!N)tMGiWpsnjx%n*wHMfx1Hd{#*~8SB zBpdbc#4e^AY)bAJCbR^5cW#I=ELPy~`4?0lWsT%dza;RQsGiUK>2p5ogfzgRIs z?hyT!9;`H89lAhCJ3vbPD~P>fBZ11jS|4hVCqot32(4j-YpcP%|5T zrpn_KPw>PbS6mRcK4k$aK!WY-oJ$Z}ZHVjQiXf*7z!gq$bs(18&+NgFa?3Yc5QC=W zZVZ>4EVxrB*>IBhVTCubglC5@Y68WqY;5Gc!Iw{pEkhdmmzsU5YA|;q(TlF3s99t(dzq9H7QtGUC-KrEKrv7hxnowP=Vq5Q445Mh%bOP(RjKjncW zum_p6G=>HTcg1(%h7}XAWCBN=CSVp5cNpYQaHXP#9w7I{*dan!a^YAKDw(^x+K52H z^?|4L{NP-I2yR=He7a#Hk}3#_?N_I!AP!`l?(oDUDdx!p;t|KxxsEo`cvGwF#1xEb ze@MxH6{F+HhlFizXi&s`jM5^hu}yZxX?NPgW4J?ks+So=j^r=ij`+N&dqbtVghDOO zG-5;4JYx>WB67jm(_V}lQQGb?s2w&=YqLD+m^wtQH0yls%{|ejvgpkc-xF!<_s}+v zvD&Px_9&_aYY>i811i@OY$rW==BXMnVaok~OeZI@{{S_=$ofU-XN2TxTLG$5!Lbpl zNj&M?HrlF$sRjCfkvO>*;&as}(bm0InQ0!@Bt!%(NMHE~DLt6s(-bCg1|WEU zvb2mR*|M17LBH?C%aN4d0i13;;5 zI=y&St1@lLn2WOPJZ-T!9?9i@&*dDC%l<`^6>MwZ@Yg0&wD9{qj#;+UxtLToooFh3Sh<36Cg&rEhYWYgd7 zW!yi>BIb583~#9UXHLe9(&uqiW$q`Zs?U)4i1o|<7$jYpISp330oeV2YI_o6X^EFe z$)O9aE|J%|HSJYSRy?fve`&W_D^3;hHrWSKWQbXKV%S*X+e=6y2Wi1AI${u8j}3+P zU>GGMS#ysx@WT|+qlVlS=hX{g$fiW@xh;UGT7Zp_7K$(-shdb(CNvQri7TH}NNM`g z=@<-;r8DJfmAY)ZxPk0{lkqQxIl5;#%B_)$l|Ggf_Cy;UZt9K6he~wO78E8pkq@cG z#pc;0XXK|zmn#*P$Vdh`OVVc^GG3zCrcjdK0MhJ;rcnxO4qF?W(gjDaux6g%%e-KV zh87(u3ogRg8&leb1UYQ7`=SsimDxj`Icp*)Jxdj0)kvzfud+zI^bvy!r6&@|c6owD@byEmz|3j)HBQs0&}CeHXkKiZ zut)~o6T=H++SGM4LR0Zob-?;s>k-wjmJ&C0DO%)d=P^oxD>lzA7e4s8iinhd({z-7Obw2;w5_L7=aIIXo@7wi zbcXF50aZ`oj(xSsS2W9iE2!uEd4RvOPdO_v*xw$V8*5{eO?ez~wQMSQcWUnhxSj4A zJHkER=wp?e*^PW^$B}f$?Y~&rE5bro47!MkMy!77FC#3Rr&5?HIVT8sVW}o}N|5ta zVIU!YBD9OyjeFUe;wW{Drw9{?V$ub?mIh0JiARR-XTBP(p)$jkW-bBGr|*Zu20dlX z*@-8#mshBv^J`(pRO#nlZ}JRMBCDOAiMRTm(u*Wvk&!vx#w5aZ|W{ zT2usg&hH3T_KCE+H16jMZ*b$VY$q_*ACf756TQhI1rg*zH+L#|MPys_i)qnQ#oG&Y znmoYahUBIeaQEVez)UZ)3*{U<{3nA>9%jBKubkMX(w=*=_EwosXr>T^NA8U4i}sqQ zo~mqpm+VGIV$#o6E42oks*tVkyTUAI+@5*ITaCuigD!tjihV(uov&lTsyx{>W=3RLm~qht`b4wg zPiHRZs*Q=Ud)bu?DjVmGv2mdkwAZTW1xA2aaf+#}Nw_A=yF!F3aSnHV>D>oX?65$+ zBe~TJ;vP`D15-5F(2<;F;&4Za2!h*xw1Tn{5G^*a%=K<~CGWZHgb0g-QZu;NO}EtP zYy*SNPY=kUh}=vK#rH=oypFOil)6#y4Bb%0C8f#Bz8Ixymbo@vX#S5idB74Wc7+St z&gctl7G^E@e_&xIVvkGm=ETfIY z&90U-D_xCXRIzb_X~w`;+P8{-I)Sf9#mNV$XLnF|Okq&rtCwAh`aoh`h~jo@7D&*?KeGJ3++DjHu<>%xkrRSw41q@B65X6)2ifm7R2Ql!nc?K!_ zjsQxqX6F&j79hdIob%F83$pGTZjVWiR6$#U`Gj_o)kwxxe#T~eC*degTvqW3mc^{N zbwZ5NqUI_idET(S$w#-I=(_}kBCNa0i_7sWW|bPF7YnIU0gC`#1rnqiyyc;lupy|~ z_FHq9-TA<6gNYc-Qrj(mQVPr#^Z;eipvYoN0?n{5GF;LDl&4FL=A0)2)#{2~l9v{S zZ#I#A%>y@tNeMhKN>xTR32_k1+K1wR4Lgb|PR>(YF4 z+YZiVc2|$;6j`?SXC~^1{7IgR9>eh*&tDS#k2RV-JhduC*P2Id1=K^?my5%6R;0FI zE{O}mpSA4g0-#)5WyjUS76#aGA$FZM@Ws~NMI?@yZrB%H58MlLi$sOYY$wVLTPwvP zH%JvSOxmKU;)+#&*-YXEzC4qhT}!coZs6CH0iA_6mkdBh0@)6!)dIsJYAX-H zh%j3+{CQi{H+(rmVTIgw+c#=M#S$2+SSIg}K?tJ4Zp4r5#41!+2Tpx3wvCM33vHW^ zhr<-HqQeZj58>&FVjgl`Z}`7vEj`NA2`N%g*QOfLqQDb2>jNEtb{ms@>khR+LA=oM z>BDpsdx0o_H*=O=%o3?;gLgbgol!$-BJMoXlt7FRK`QXu)A2&x2w?b`Bv{QVR-g;T zZ!RuY#HiVdf^oqwv_HxMf~T-6bJaRvsufRKgQt0TU?w(EhX%vVIa?CMGM62sh1C+n z6@>{6;*0p9Ok23MTaM)6@kPgCl^RR~V%U2~Tv2L&rkesU)>-0)D*;fZp?7Z2F0Zu&P*Hm0)S+5}>?yM1 z&0>^)Vxf{POFk$ysa=9LOTw3RM05hy3fgq$S`IkX{$&EGQFX758n}B=Exr{>0 zWx=0#^u!_T)K^uA%0VZ@xkM^E87D4WmhWg{6;W-G4cYNTWdz9y4==SBO_$UpREbA_ zE*L6kYdMs^i!Pj@RRqqef}`Tj2`h(vBrW+6L)xJQ`q#cm3o;O|Z34y34^gAyaT z6NczAFR&8p*NCDF5H466K~zf!y3qEQNDwAAD(h1;h+VX0vPDe(AKe8|;f0R}A=JN= z31LO-&gCEZpsEUn9EdW330P4H4~vF>2Ez|sy9|~HSceMIxjzg7sA|B-l!GDV;`za9 z3Ah1a5PUzi2U4skL(kcZh|w%0Q%us!z9;}C1#XJ_VC-k0Nf|R8K2hNhYD&w>sSj1$ zD+_W3ZbeI9^=vGG`w9qU-wmk&xh1^2G2tjvDVk7?o~PZSa~P>mdQ^&@lfR08(<@2< z2Vy36PTQA{iXcNS&u*}A#1b-ST)+5Wp~yRxlejJZ7$sroqv?5F*a{c8qHY4H=2g4f z%{h2srPOh`BJkbI^N0{P9NvAHg-qyaFy*Ui@h`REh*xox)7!(zAZjV&LsCm#w~p`e zLgEG3Y)D(rX7AyG8m>2S?ca)jAd!^B;_)c)Taf;6r+`=x%x?^m-xN}=f*wn}VtE(( z$}YAcB_v!OUuFOs$tNW8U&K)YC8R1y0g{)AA%rEQs)}6y0A?d8cBuEZ_xF@WQd+>A zdpA0?hs5kPpfY;k&&3uozi8TEuJH9lGt4m+JR;wU;)n#P2{_>m%Dhm2RwSt%H1hWh zOA-`l^O@W}A%)bTl;@IoV1t<17d?|@?C`_@bxC;nDGy3r${@08UUKVJAi?8qTm_ME z#2~Sn;D|vY;*sYAQW)NRpo;7v!sdg+5LHBroEIz$1QEBb4xTtuAPh8{LccqrqG~U& zHeXBf_>$OM&4)veSa^7UbK)p~1{snd&!}J}N}15%r-)Nd8+H7h%Fces{{SeVEjk|d?-G^X)D1zfR_X=a z?}OBZ4lA`<5p^y7#EkbJ>mh7P87h%=AwepETLN-;MDn|)3Se7*MvN5qdU1R(CDd5X z5^9_)5Cu?;&38c5bVkEU)GW5|`Jt5pq5ib#Sc+AC!CR<`hDtj-=)lnV_WvMIdKd@y^ETONXFNeTg{KwcvokvgImrIJ2qZsO`9i(x&y zNXMJEMT#7%3}y9y$p`ItU>F*nW?d6c;)5-tULo6;%rjB6Vi9aDwrxUA;)pcQWfajk zliG+23GBM$mw(L=ig}rIyDp`ONrAxdrb)WV47`cNjgUol&VxH?fp#oaQ6L3#)qLlqswyf-hnVyr$Uq8odu5N(GR!FQXo&)VUN zRVlw{P2moIp3GSlPb|hT zhl$l46T|%C7ahSP-O&aq=Nw+{o|uILCF9aFknokEmxow@)Cm`SJyewwFyOdlkrYHw zhW8M-DzI!rDpsCJ&IE_Io9nXtKV~5e6P#rb8wuWjT7Kvz1yo(dpYcG2V3!iwoKV1p z4tzhfQ3xDBbJMEm2~q}IOP5zv4xm$5R_{L#{os^nMq9eoPYf9c()>-1W5okfl}9Gh zdtE=p0y-mNEz86I0JaHA5TTLkIt*OK$O*=X+ayk+itItNjjDj6^+d4%I>6nWOAt$P z{A5ahKV*Iw5NZwPZRR{(Kb#$G5Z%W3$A&Cj!8p!lS#^IFP%2dR21CF~utkF)@*{{{ zPb+qy<>K~Y1E?@OF#@Qe;pY&b$l?%n9(Ke9Lhi(a{wRg2iWj)^U-0+DB=#J(;U_<5 zssg20D`j@R5*RY|1C~p@A{Dxsb1p6&I(oW)VgjsSZsdi-;fOXE!84PCpNGUT2-CA5 z1f0LpLBkYhdEA~DvI23w3s7N-!7FS@3KoU-e)uYvF|n-4ZjVp&FdzUxyPCmRxM5AZ z$8U59R;X))o6;dx5Uzg6!7xXPYSYO}r`d(f7kvsjn}A2E*hm+UV>8A%@QFcK8=h@{ z>#fQJWzQ3quU4H=0cRZ1-d83>TfDfsq6>1d=_P`vs??@xHkkytsGKFUYcHg0#QQWu zbjgByg|jchu;G%!*6@7%`_?ro=^1+>q)<1KDQv;pHLhWYS*t%ynr=~(61j`n*P0Tc+Z#%@{M`f1l(#JV4{rrvf=qB?n(dMY`m8_QbRK2efBxiK#=??`} zx>%=UZH39Ms83*eX>O%|`&C1C z(r7k`M&-qp7paVkiv)PF2{pC~T{dKfQkKQT(ndFsK_(TSA&}br!+GttRH`i4)7zH4 zfEz@AC~iD>qv8|t8h%D-xTLuy*M_%=52*^EhgRC-SLPy1J zMGA)(mU>Ra*p46^ap;OH-MpB8!?Q}&#nqB7Y5R&W%CvG8e!(SuD*a9KgK7g z>cX#8Hbl)po46&(S|#xhQbs4_VpgMnyy;@We0EmxKR!vNsslDpxKGaYLO8K@9KePS&0D`Mg{6-e6V)1_!GWTm_@ z@Q)N|OdvGq=&wbbez=nlHs0>g>KnW;%O{AKdw7>mNX@TOHY2dyNnfoIjJq`Ftsv?Z zu%MQqqid-6+q2ws{yCub$30k0I^(UicR-Sop#DV~RTd91$>g(t<*J<>u>39c78~EX zKE7zKFO&~|6Gsgh7R+CSx_F?dJaV3`%COjR7#3on(`*qJ2#EDWZ(^?{ zHmN>%g0X0E4coDQA9QmvyJTvq%*g(ube4J@G$_&{06O@iRurMT4kbk;>Eph5SJ~o* zk=vvWGn)MYur87)$H|=-^1)ZQTT;xpe9Adp{6vPAm_q3*k}1?UVf#7;a3gz$l`PP+ z>WferLkOoOfMD0y$hIlUB}N8pNMMK^zAkYH${a*jltK!B-QTK2Ay>zhFI?LQT|7k& z!kaSk*sbZ#9AN@ez}warR2->w4Sm+Cb$DRXDpN5lNygPGO`_Dr!R((9!_~t~%>|a5 zw#TLIZgO1Q0r3PkpwgMmg-NS3i6k3&k_Q04Y61TMXyko`sW~GfCgDCn-J`?pMJnuZ zsd0ADslz{ixNmyngjW#*YG2AVV?r)$L~Q04n8Y&IBVgDN@o#uw+}w$NqN4W4@ZJ)f zar&Kcsd;AwZap(xQ1d~*9cN_Rq21)5A%mFvYGcM3+Hd*a&oV>$ z1nnSyunyQITHYHh&KBs0!{LnS+}MrMeMZ7l#UY95@3dd5m~4c-+5Z5PIL`k7xQW_! z#aYH%RIN5r?vsp*#sngyStlG|<4Z<en6J7I6ztONb~en$D4?4(x{Sn#R4P&aI9v$R= zyC5K=#gN?^;xtnxjwZ&HretdqQmEis*`1_0T1lKHJBW7K05t$6i5IHq*sNm6dRD91 zYE6+4*ijXVNc$HFI)FT7X$eCk+?~VyXwwKvWz!D|jA6Wd#XQF@D@h=IWsf387B>bN z^rXYiNJ*sWZq8H?ED^X#Kcr|*PAM3F+;)P(&!i?1sJ`=qQtW%Z($%ad=!p9HLBE%=~6OF|?;<5A->yCT`*rEQ?55;ZnT@E0rop;wq@k)n^WWq+^w! zP9ZctS|k-plxPbu*5+F_n*jmKr^24-I=P}0mvX5$Gb}1~4$Ilx-pY!&W3LYiA9vpu z)?S#AsZ<)RP7|hEz%LOO5kQ1oc4G5mO_=QWHpr57c%tSs2Xh#K#h5&sD}cDJi|Z(; z;tAoG-Jaq1t4D6E*VYxY6rOn416cQrh63RKP?r)4*QqD56(qJOQxVE1NpukFv z>|kDYP2n~P*4-W}t+v&PV&#mC(WBRukYE<7dy4Zr!RaDG;fM<^hwZa}aAt+W{&4{v ziIFjHO{M!$1BlUpc&y#8C|j1QYL81FLfjwejf9+(tIM@3h$TfzLZl*_!Cz%neE$G^ zb2)quQ~9gpQy(7vCp`;s2cyO?n4;II?y}{|GV?+=H8Kg}Mebj8V8@^S6Lhhw54oai zm_}lUyayZJ2l!)F^fH=%D-#r!rVsRgR`-db=`WNqaxqNtu(x z>hD5dr7IfxE!HJia&g+V2SIjK@=~=v1@8GrR}Vj3IZa-=<>_zrKEQe{`gve~OAI#L z)e?D$S^I5%P13N1eoL%ts=rd0b8y)@yp7i{HFrKJ&hTWJ(Dq$_?lD*@ffA#oaApJ9 zSh+%PMTZFO8e)WdiTkD``b5X6ulhhfJNXvVvtY?hH$5ybFyOe;og_R_gDw_DPKb;n^E~i*gFkKejB2K7|pVdiF;P?!(+J( zHH|4{Os&iT4^dn{DDH517ha<)#TbEm-5pPlK^Y4^$8phNJWevtx%w30SYllwtKS&; z{AJII-yl2=UiwxxPuTH?*RrgDi50G>k5Y3&{E9fAhPEt!wD_E_$#MFuaXY+5N`)TN zRXLz$TpKchh++Y4c=Lt>QQKwSo){W|EF3Q3U*Yh@OjK1vGvP(>=M>SiOm+_292ucN z*h?8UJH8&65RmhOXbxN>1fj4YY#cJX!w0D|OCCnZK)85Fk{2sRcGX86f`52B4{(4w;uhw3mcow%9BxTuHlOs-*?Aue-V~qNVg9 z2R&27Tu@4(rd^6Q#dTls!Ke)zGTUf6L8!M)3*FgjC7<<%L>72iiBf9bLD}+yg#)l% z)!g|+lqae=m2SlML}<{(al8Mi51;E;cS`}V;qb!P(-X7aJzxd0irjH>;(=kx zh6S5-O!DdTL?DZWu+2WqHB^A#a^xgV z7%~E0Uz9Ng#OzqSO1ckBK)o?S_Dhl%OdUXY4KFdgw?3UZVoHcXz`CM(_;{fMCKO&o zI^6`o)F9?T5}<`-i40XD+o-jj1s?Uchv9_JaP9RJLvD&cW(^DOC`Y+D#3LZ35g^Zh z{R|yQkD~-CL7#BI4?K(BkpZxh8?;y$f=rFXRJ2~Gy}>L2#!08M87qtRjq!AUjXUiyKF`lBu!XV-MP!rsBY9QR* z#ne-~v>;XB!nn4T%tLG3{wJYuYs2di{qOIZ1FK`av z!429Z8x|ujF)r(1OO_Obix3#>D&8F8;Y&OuL^;7$q!!y~&~bgMqO2D{i-kNsC<*LN zflb`0mxu0v6{#>vqPh5r3c-TAjFRY z1xk)X-daVM+*%K)B=(?KJwTH&<;VX3XuUuJ9wKx1#MBkk0Jt7J2z{vefDrgd^u9dWdfOKGY_{-8MwjBv#Ew{tZaJ{U1Q zV`;Be8gW1?>>H7D4tM)dVXF+>y3Eobr(wA^MQW@KI#iaXTE#y}Mm` zK{HsB8y(5itvv3uV5hl%eTmswkI}B<&MAG!EL>IA9HXcZp0o)@2&O@#06fp{F z5fjdEYtAEpu0qebar`i{nTYqCx7j3!1%@xX)s#VsGMaksn1bv@Rvt~xY0RPk$a<4F zg63Bb#SkKPUG3$lB@n3WK5697*|s584vzO*5Fui!q)4#>=sZe)S!>e}5Kk4_sKf(O z1zaC%;kV}yF+z6f-PQ3#2B2prEew(r$|=xiD>Nmlbigu-9*p9Sz9?AAxZ+-AK=W7@ z6}TQVFpdSO0yf(gFIZwp?A${D>;nWT?v%RK_G3M4O4uuN!{X}F162W+W2Wc4`k;Xf z+}9py_+V;NPKQi?&0EjK0z?cm7VF$21go&+EG@~qlQA#&pwu9Jqs*=GKjQID{3s z5duyQ66%N*Og6}*H&RG}Tbp#?FT(*+FfPwwilj<{%wU`zn7RcJ1?RttEPX&0R_6>T zZu7&-=1~QIfhmp*l)7RJpaOe%WtS8}N&;|)h?WUgV#d!oapt#-HDg88WjuFl#3Dt~ zFf$q`j+t>k_+mtCgL$IKpKIES^u{WYlMY;&2N1&*bS!6onnZF9=9{}%sO}34ofI3h z;)4}og}I(y)My-rF0eCBm_#sd7Bb!3mM-!VNs{7!)7pYSB|uCPh;Z_TDwe1uT+9pF z@WTRxGrT8lMJ|$vKTu>`QRF)NQEUYatDDdq`mOsq!Hft5C#QF{3m`X6#I$Vhh(RLf z2AxF^UbFy+m*R@t3<6Hu^NOJX8-zTH2o*3>T4mkrz_3S(W4wmwNd!22dKR9~wFeA9 zMKD5t87vyM$PVuR01O>ScwD(MUZ|$_AeRc`(0XE(h+}wH>O3XY^NSa8MKQoh)9pkf zm>YGvAeD11xyQr%TE)tZh7D;rnXmChsbSj@>ftPMbh z0`;TL2rwO-d89%L-IbA38wEr--QNj+SOVrYGkLWawc&;U1;M;E9-kCK2q}I(E~py^ z5ui>r@cpPF#1{{QA_|_yH)Q6%%s}d3#ZyFx2%#GQQ9jW`4r5`P__|^M8M{YR8&~Xq z#3@*C$gi`-bV8;>h>e*gzw<-};R)^xlkq^PUBn>?@*?(nd*F*j3MIj>i!PXji*MTY zM%zju47TBNST!np2=^yeg$)W+QE_n*T)y~N76jp<+^gb)p1};?>&bobQ_@o?)xzmf z_IhIX1dhO5m@?u{68K`v6f+j}71uz2M{sOsv<;bjQAONIObDKQ9p8p3rJ3Fnn!p!P zJJNHP`dBz(*$g9@22cf589lsQuHuMQ349N9#w3+!uPx!A+YDbKpWRKRwK@#^n|4S@ zg-T4DKsE?&xmMicH!~(A^64GMiYXzo-G@kG95LG_aGaEk-n=B*-TUK>mzVW_W4pt` zxfiKP{e<9JRHZg(mY*sw4AY~2?}$+sF8EWv&A7O#tTObdlNo)Oxi2Iky_%S8Z4tC{ zjXXvZ%*U)d zy!6e2H3?CC47zho7B7k)B{x%>X_%P=%GZF0;VZ0;j7piMG&;GLx?CKHd|q*Iq_LWG z=VzI?1m>7Yz#F}kv44DJ@Nsx`j{N2Jv`{{V^@igWYY z-|jidz#H;HhK470MCM#E0b@J6Pu=l*;FK2k4$ERz@ct+eAqku+05jr6;o^f)iHN(? z035iBpN2M~^wo!(d(o$6!;Nt}b59M@(mgyqXB>~p^8GcbxAFXce^V*fwDv+qO}2xD z1Y^Xz@5uEPsO`6WOX7v<3L922M5P8;oO!?)D8;dcsm9xLc6wx)lIgnGIE6tk$RqHq zoZU&{nwoHkoZ{k#Lv4)r+Vvfk=x;QmCLmrO7*o>2aLdZ|7r#!8lFRM0(O(r(9eLJF zq@CT^-S=h?F1P7_8#eJiU3xF<^syy2MWtk!mZypXqiwdR>6hmWOuaoPnQlGu)aj{N zQI&1BfNen(5r-t54#T*3A@{d)h(Xl6$5M2_Sc9I>c4n}poP0W4lQ&)P#)dS^RFb5&OiNt7Pz++3(Fu$y8_CNT zgc%!%qn*s1^r)6*vCI2>+f40S5|uiaI8w6mX15sll|Q6(e;dTo^%|9WdX1gv(TXQA zW&{^!%Xu6f|JTb}$a?)CQ_@WiG3-3aOgtaqm+`3_b zi*Gyhh!uw*kpT?bo6R6k7ntHdoqB4Ljh4mV5Nuj~x@II-S(jy*P*=x(NF@5A z=pp8-bO#w38NQ}$u5%V3^nJwG!?Yt5&}?RZGaH>G5ggJr`uL$t+RHvBx7q&y(OZac z-YRabIf*H(lW*@qiFQ|oJ(XPJ13S-(nsBaF@;e6)(O#m_l?fDPd>gGBGi$@%N{O&#+T26+;+FTsiPCTf&Db ztQQ=1G!E7Nm@ENJlbB|c^^Hm56%ZnAKw|AuE+tQq%GmSiHJGbB;>gn48;gXDmgs8c zVGh`hJ&e6Mv7R8fTbvnCE}hWYS$yJuS)@wxG-}*Qwj4u=5S8kL)0YqX$RuVnW#nwE z$j(eoJVazyUDU9{88F=9w)U~Az|?qB{{UJ`kPDW)JVWtCu~n$bTD?Dq-X@&9L)j== zeYD+a+?sybSKA!+cz zTm#Ekcy&h%V!cL>kaY^qD4S$7n0NM;C#up$U)mYGby5J*asL3?SSZTS0P4kq_F)s8Cj@$L;uyd7g9BA0DgKi6 z`7qXM>vFVhNGRvG)Fnb|4Ej=k)+Or=zHbm!5R zg`e7Iq=bpM-O@O2RmsOo!+Bf)IsVeXO#FgW>OPv3iV%;SV4kj{T+WYwOO8)*2o=8! zR8GqcIL`1hw%>}_lN%t;Vn-w#x?y{Qtzv%$PXltqBQ7MZ@bPC24V6g(;|$PvVhITA z*9ED=_F@b&!t|o`Kvb&=YFykrlb&%zv7o+@Dpe;RY)*1kqR32CY2dEQyQc6}HMcn9 zen%3T_~?A1#JD7C3=u1TKCp*~J(Y$eZB0h4#@i`5Tb*o3dELXs13to86xX#G18nS= zZ0;TrFA&CsqyaIpi_)hcqCvz2TsFY>EVXQ(ZB@OL;z@d)5}`Fuo|h!F+Hptj_ro#e zWbia%$*+m^KhQ7fpr>M{i^ycdgrNLqc-r#3otoJYr|s}DkZgH9WxAF4p3RxXwOsF9SuqDu?o z$($oIdquopy4moT%ij^I6x}JJIq&uaaox{Zi9f?0-OpV8q@elNi2y(z@!)?A8f}0TJ1x)qg7>vcL zg=M|n+4RI1Ib?Vp%%6VDxC?!&%3~p6J-aiyDbxa7ThF$S`kxkSUvgux^x{O5? zyCT=|LBfFWp)I#n8@-q@eF4LWHRaV6F|a5&Wl4E|z`PGdsQz7>H26?q3^-dX*nZRt zF#6KV}J3mK)3g4=)T|LIK(M z3x{5Rp@}d+WZk+i_+UsjnD-6WL?GGaQRnQ%>PQ4f_+b+DDtigx>R79m42tev)F`-6 zyU}jk>?@+GTCT;mOO{AnJ|Al+TL%-PE)CZu+Vw!o4TumfCra?3!AxuwP5B9AghM+{ z;$9zS22^0{dEQ-C!eL8=5?L1#d!doowp{~%-0wApkzYYKc8A%6#N|sK5G69qV?<8M z7QA2cLD+!qy5wFGh@r%2Zt)A5oya~7c-!L z^87mf;f04E)D&Fa9&3LVU0qp33$URTa593i8AV%a@o@N|Tacm*uZ2W()(SfZV>;QL zyfI7#L|t$eP<3%k$_uFV;ZfoCp?Z*~+#B6uix&_YjHYrB;pmIh33(ei!WsKe1zN!J zL|q`JKnCF#A>#IiCqP$|7g{|!mT@9~0cqLh+}wbw@``04=NwBjK5<1)VY`c&>ldjM zz?^wY?35OuYN(QLy&}V?A}r5dQ2^8mBDyP3S&=J4OSrkj)DT^$y03}_SS4Mav`dib z21Z>8*8Kb>5D4sKM%@+)!_pMlpi)1?cF-*jNvF8TR z%{pLaL92)^E`BJAK?k2NW-dDgUU-WB{&Hvz+gakO()_i(}3R$p)~ zH+9Qx#TKAK32=D*p^IiE{-SKm`n2&31p9=p!pM^Npw9_hdd2jN3dt#Sln$Vb!G*+K z5V>VxfYK&DybJOpyNo%@BZp-W#fP|7})l_I;@7>q1_+?fiEz9I2I1f#iWd#y!* zs6w1>)5(39Au}UsiGB>%n2oTRf-?O`dC6YUs`z3=3#kKEk}fVEvQR32rK;G8>}A#a zP)xfcHG;DuM0qEQ6%nupEy~&giPdy7ax%;vw)anG;)5Fw5R61J_F~a=MguhEp`XH$ zECohWMf^N4OVUqFj+^TxQPWX@VwnbIs$`so6*9tvyj$Su7+D5yGNKt2H*!IY%($Mu z&$ZP8?hE%AIV?_bBm*#iG&0<&3F!+i!|7>#a<{53`XdinmR5DU@cSte)+m17N*Y`C zpw)wj(4iz%^N3mP@d@ZxLu0rqO;PFw+kId0K}TRa5{!{KY4pQGyouLd*@JnsE=c6)H#WMoteNe%J886;Y$k5b5)Oh)~7nt<$@wiXbdT z)g2oO?+*+>E;6OMgk(1tvk)k9ew^Vp8QpgjK*>htZUKl?b_&@k0V3IG{wRc=!P|1g z*Qy{@2yP30#6naP&yOgnje@E*CoNpl*@{rAVaE%SK#^xdY3B;T6}BxvVl}yoPb**; zR7S@^J+7r}2xGZ_IrLo+uDTU5uw6%O%qoYpMZ@sI=dcUXEUH=X^p{WN0?Dv#W64nR zfb_}=-9SY2>VcFXX6gl&YvL$f2soFh5Zf105-iSKcEB(b+<45#2k^lQR|?(4xpc%H zzUPVLJC@KD>4*ync&_F;z}QFv8-5k>MfVx4ib;YHC5Xy@N(dQ^=L&nGrVC`)=w99h zzZX;lu_2Z`%;ZFQuZk+d*|*{mT(~Vkwg@F#4&}jHJp4La5S6hhD=?xRK3*8O?hQ#v zIi3vCDO9CV<8y#6d&UZ2l7iiwEc7U?Ng^0alb2kJ!^#&ip!)%GXj4v@iUWI%p0!`o zis*_LrWL+_Blt(!iUua3+fEG1AldFKd48TKHH``ty+XIe@ciORjYip#$gTKb12ewq zMTY7~B)Jq1V&#FDip7KJ>|{bir?UpCG-)(e=)8&zJBF)ald~3vpNbA+Ku`mdL{_R7 zoDCQzW(HGSdPT%o7Py3XoJb{I7f??*mEO?w-4xD$UqK%*@QIS$;)bX(DlnRNj?w-Y zxa>z=jLGFB>gtOZy$zirYza~ZB!e<@#qC65);0m+Qnu)o57HRUB44H4P(ac~P6`h= zIGvQh46HIy;)`E!wx^qLgY5WVB|%dS5Rv>N4=(a#i&9nMi;mz&U}W{ys_?;@5_VvJ z_QV-~J%wCD$hfVGsW$>QoOv%eFqBa}!IDDAz{$bo{um`FO3Vm`YCKUYKuG4k%r2l$ za}RpJ{{UR#w;@MiySZhUmcP;|SdrKk`>Q_;6-BxUDT{d)zSKoRlt{g37F|(v2*Ef@ zufqij6@-a-7xRfM2$U{8I!DD6Fu;)abd(E!0>`|1aX_mHNHUq0z5P)5osMBbS8Baq z))yUsl@`M*V6Tb`q+CXaP3xyp>lA4Y*=KSmwZjlqY&aIlbwc6NCiGIKf+&d?qQeIU z8GBDP@k9bUhZe+jg->9^k!5e$gHR*6mdJ-lA*y(If98Q=p^Ul3%ajOx#4oJy@k0QA zXwZiS-4vn}4#l%bSk(qXsGd;3r9?P6hDp^B0lST(Q`oNVeV4CA1@Wd#b;5;m^BD29~?PavkhiXn3wFxlLH z3|z*FR@hsFkaO`tLj+@Zx7xiiV#HmL=CD=C#T6?3Yi>Hb%CqR-|PM}o!{HwxWt8{;y5>HVMq;bTWniFh?BFO&$IO6_s zdX(A5V`I{40^ESyT_C0QqmHP0j5DP*#>VD_$~@zjXyohUpSDr1VS-oL3+YWTZEW^U zC7UOv9-nALL~bbG#7*Te^Iuqh4*jcO@T2-Y;n_8B^p`2k6Qn#^*_?LqVd^-L+frL% zRe+<>8J8}WML0%14+Yv3rj-mN#bCew1k6)rYo@Ou{JolhUK{45h3|PCNvy1dX0tXd$vg$Ce&ry?Tt1Q z&J=<1aEYBNXVdRXjMz>j#~PAwh%9oSH>hpHf{Z?|R+4r= zl{_$?RZU8pHQdErWoy#vokVN)VVE*(&!5|uq+?nroTo&3B-S@GjkcxYwilKI#|KtC$keGbQm4cdOgPbeS)n8;mt&$V zeB{G;c2j3_m5OY;JGEwg;yxQlR`OV98a5@c^%CP&9}hdDTt>{ze~6J65Q8-57gIAR zyGNd}Q#&9+A{7<30>LQjc)Oh7N|@0fRC?!!E*=ab2J?JM0qwO=v#y?9+0L| z<0w|iIVXk#dJUCCRH^|_lW@FH&HiNjYoRq(%_zVwusacCm>LkA-TJ{>UxA3dcDY)XD-b z*yF}?s7^fd?p$O_L`C9Waq96Vl+?#tdOG2V z0+YkV{{W~+KNK1;Is8fsFDahBDAv74A}3*pxH4EW$U4)OnPxCb5{JUGhT-MKXp}n= zY+_+&@h9A_OUm&VR64ryIy`U3eT2D>crtH=A&PWU60gLKn#EHOo@(b*9L9!kL1??$ zq~MQSQB38se_Bmj43JBNVaTGGaNL=^W0Pi3L{deXOT+_^!7gn9Mv%oxA_Bd^5+PCQ z>51#9U#Dp`)*|$5k;H*8Gxeioxs1exhER(yn#BEkd{i&fGZ0uq(ANTN&Skl(WYt7w zn=7}jy>;JrD9;U+Lrg6nBW23eBnb?!az=oH{pGkJq@wLKuieg=|@Jos|Kb#j+3VvtM)i{f6*uNVYcG|UG zbN>LM%o#;=37$)Riu2Uky+p-NdPUvNDQY6-f3TGqu3}JY>;>F+E3$cqzgR{;6kJY$ zuEL(nRlcqv{wOe7uqXYUBRx(30NO87RC|@Gf7Tl+4NIobbEY(>VR;in8i11_a(@h6 zg%N*RJln!UaN1E;Aty177NsmLOfu8N-${6)*ovbxnU!@vQ`G|INC2r3FWnF; z1DdN&QmUPnVqBSdvb2DP(iMWJB>TlU<06GB@9eAz22MEZf83w!#0`Oj(YAdj5UUSm zF$2=4p&t}nPJu0yTJ?Jn0Lx3f>umd1e<+Bvt5#L#;&&Plx_S_K=&mGx6uI%$cG9qOav(ze}^R6 z4XT_;{84r)BStDy&C}!@e9GHJe>kRn7KyWn{V}oB&NQkG8d<`+an!tr*hSVjIiJ}I=3WIjKBi1pxILv~*Mfj4_Y7Aw;de0)4E`y~ul0L;Bn$w#eQ#@LGb z>KLxjb+@4h(v^#S7;18|GjmSXF!q2_?w1ou-(vp&Na+L3XONRoTvOYAEbd_RgM>%o=YyTli9XBcu|`hdifg>c4M?pWF6 zyOb@7GhjU~WfV3$h;VM=>4;DwF6e{?O<&F+z%B@iX#&f!V^N<(PtYR}nn;Ugbf2g$u+xHG-#_N>0 zqExiJk692MSE!*{u*rbUjn?PW6IBRXZ1Lu?dV@XeWwI{rn3{oNty?bt0OEtNUZVp$ zko}?315i}^iV?eC^_*0|5!4mQbpy#ihAUDIv@BV@pZTIPg%T;hY6%x*Zbxm6zYG+q zb`5U?4xk}zw|1-Ie}<4UwkVoJ^nl!+v`so->MiUd!n~Ko;kpiBX5>!p;)bWNr)?f? z;qiVbD%cpv6obtmYE%1%Y;NoQsIXN+w%Fg(5H%b&-gf8H6*HjJeJ}ez@WLEsq;N9j z#EZjhB2=h`4%>FFUdeSqDA+*4DkrIM!e_W{x(JP`xs|`^e;}ic)%E@1^ zFBDpU!*_s5bo@}%kQ@;Ndm}2^-hii0@(na8!D@x&A1G8$T#V%(`LHVPHzvN_ zu+8YI*T@T(e^@@$6F$bviMrq=ULyEmAgHFC>55<(g^QVu~i*X8!@4M!iPa*Jbu=L6gQ;tk{2&$_r;G=(a}r80L%nRjY{haD56YM6Q+Wt)y32Eh!aZ3h>>uMhS3CocSh8PV*+*#-B!l8rgBH}+3e-!r#98I?#uwb#_9Y4ZQDo|GLy~vzltWH zf22`1uYV}9j++GbV|V%}sO%s02q5EvmS8N0tVp7iNiD~x5bF>oQC5;`_@JysQA23C zclsD1E)z?=&^02i!|=ErT0okWg$R*#doggqyllWXcgpYCi7XDJ@Q+d`@kI-%kt=mr zgL{P$w?&HF5_&}2#gs|xNU*Da!oQqXfAoOSvU*jYSFCu`){-)k_L!m%6a>bIN(`D3 z=DgzSLnABQ{;h#3XF|I$jmr>2D9&nEcXA+rj0B+>NcoC>S1J$jMG=&Rt4fP^ct`rc z)L97Yd$Y+RwIg~$NVcW>FrXB2GN|6L*zPtv2`PJzKlCtVwZY6bgK~Gk*dDRDe>c6= zI$|(tH=AbEu?P5IRf*hS27>dBL z9Pp7IYvJ&~uo2iA$_Uy>I(UWzY3^YZYTDQ&?SmavB^Zx*bBC%V5qC;m8 z-TqM6V60@kIl)BiGcEX+!*p2*e>!<9XQ#Ep4cvhYOv<@)dE3?O!xBb!3`cNgrPIS~ z8CB5IbA&t>L;?_sn|pd0c%Zuk-6Uk-+ox607^@KTLN*j0!2r5GaMr zY$I=6dXI)7VVrX7bBI(<#1+0DXZE5C)D7Hv)9Nd{F$t&<+CPQ@rHv<^wVf>ZVuU-9 zMKhWEF;m#Ac}(4CWIRdxF=JW7$xRbynD}^MQlQHYlBS}6^1x74j|}LSEa78oP>s~C z)20J*Qx4gB?q9_VVO9>yf1Npg7@~<8J}rD#LY#y4bkuohP&<<(u<}f%m0`%xiDQw1AjB0jp`6DCq5l9ZL2AHJMME|ARxZO;HYG@jbNn!DyYd!fvlY!p@j=3btM(Ij z4r|30HBn3p+m8=W=k{?LjCifQ`jXOQ>Lm)~0((35qDh23Q$P zFIBJ+>`H>We=QfP451yP#dd;SFtBQQ3y(GBq*V3@@i>B=Ie2)XaHJZM2%i@?dV=8c zB3zQp;;IReA96u%c28rcj+>N z9K}wPU3yM!T4M&#X)7%+aZApNq(#zJf2gise02C+bw;xpIh>VzRXaP;@6+0uw(W~{ ziP{^q_o;IIoUP&#R7}v5DD;cR#OrGuel}+CWvDMipGl|op;*OEOv?6lX>&r;4Irr8 z+NV@fx~EEs&6e%dbuP_zsLsl9%;WA#ob@)TOD2d%gt+xZ`<6DxDOEq1FZAmLe=q4Y z^p>UVN6`vKiqyLT^*(XFqFeGs&zIqjM=O{b!(3fQ&5>q$q|R*-mp`)@=QyOO zIeZ^g-&~~Wdqh1^Y0PL2QCzWMnjLyxX4Xn{lR*&?TZS~JKF08T&KsF#q>Z{L+`})G+1;8hrLVdt8eoqtRs(lw{7X-x; zR%Y&|nc9J; zZVpxcLGn&uuQ6M^{>)*P0~kgI<;V$hFKAlvSORoQ>kY*{IG zn`O2c;c2l!7}lP|p2pzxe^=Sx*Z8H%i>=)+CRUHcvB&(+GMK$2Ivk--$OtIvuVaET-u zF2&P+M&848Q<+BDmu1(BK@n_JiZ$5JIaUeWMTe)bY@2fxNja$&e+&E9SzA9oNe#s7 zA}wQL`ZQtym92?3;5ZHv7!dIZ3>POBlx`N;+LRgn^s_GeSrle89D%kilNB#)c$q&} zF67F-Jp4%$3iWiAlOe7~O*>_y#l;)Y$nM05sj~*gFrzhYkd{&on|&N-SQ<$KGVob! z6h%lx&pB-QXW@uwf5g=m*{ATpzjIVQf^RP5P$4Rr7Rt=xl}3MATdYqzTB?kzeF9xx z)MLv#=`rSO^p=Wzx=6{3Vm&!S(Gg8Jl7U-sTjvzRA~rszsJTAcFEWY;D4bPO-3H`) z`o2+%nUb!3QyyDhfg2sS7q?ByOqBQ%SMsQW=kTB!^T+^&WYaNXRN78-{R z63QWAN0{dV#0<%FtPuhy5@!&Eb}*xTbw(z~tV-d5P%^I6*QH6DC%0hBw{-NcIN1)T zC6TO8Qg&-Oe>rNt;4+$gL{S?unz3;hgxWqClf%f^_Q845&fp%Qja+1B^6@p};T3VM zB$#g6rzfe*^MyM~a%C6?$ZC7a2U}{{RtGi+P_zo2@;aGKy%}EzbI>M5tnF{wA5aVrpt= zi>RG(OJBYvP+W3yiIl=MIqf*E>bRidrdP_+JCWHO{PVe;_T3V|u{k5Kmt5)-B=JS= z0VZj?0~1WuyGGx|_d_By6-zZQV$I98Dzf&Me=eRVuS}{F61ySk6_+cA#cVUvDZPTd zvXYnN-v`-=>5GT`q8r)L2nwi;*7Vy9j6-L*&U0t&wwWVvYPB=JxdTmy>>y+~Wnaz$^$3!|1dn87s}bt|RLB^-ow#vPk&c$+`jiBbV-mhSr1 z68)H!A>F=)evh?Ur=uP;wdtm1rYS2p>jLt&+vqy#oRv`_tnCN&CI#nP_M#Bqe?LdA7n-ka{#I^Xxpy6>io0yXcZZiKY6gk2 zz3It8nxXngRh^rcYF+oA-rCUGIr*dmKG=?wDMPfXlZ^dq~_^wi5CeffVv;Ots&fdR=z^h-H*Gi znlo^zT0%QgjGZ>C#=Ub~x8kD#haFBiQ983*MfuAOgcaAo9J%(o-mYkK`lX=_hm&!Fn z{8*%W8v7GqwyGdQyW;yj@Ggb7nXNH~$pT;;G;ZGr!rBOHGsT!d8fyNBW8j&r3!RRZ8#dB9XI!W_4E6kUOzO2jN1e}=F&Al>dLA^>^6 zID-|2q&7d+9g|rAu;^I`dBruCi(dCDocopv3;7*z@rkFv~Xc@WX0b zDD{@DJpTZEQx_4j%OXa6JW+Kn!CMlrg*-kO0^i@nD7Hd5+wj4tr8B$Z9~X=4z`}xM zM(!xOm8deJJpHbyVxZa!wG&a|iBV3v6x(E0<;4L~lu(XZe|VznvN4v~xA4JG31Pq% zR>jmCeMbzSisj*eFVqSamD&Et!B9AHW0j?!wFasfr(C;58+<=>6d`*TFv>^* zNOxy%h9av3>MwLV%aR!iEl?{wLDR!^(JEawA9I=Hu_`ix+iySlpaS&vIF(L4Ie303 z2~bnp$bQI&e=g`y(bFRpQq(A>?Q`#d8EtruW!kVl*Ru-L2xibxX5D-+H!kK$zVw4C zBnym@hDjB;BLfCRjI)L-l3+@+)s!~n>H(8zzTf<@Rw|$#XLy;RLt=CUpxW8w+Huw%#Q1K-3{b@)7C&mKhKdp^fd@Crmh$ zkq9?je}nd+fkjjk+&U=T#T6dl&R?jM;pfG77ukb}D%J)>t7@l*h9486jF6V|KMYG) zt-+Du47hXliY|oI32%#>T|{LJ*>Ge~6ah@{EiNLtKf@IfqCABclFxh{N)|8v)B_;d zorP?Ov`Cc!UqMDZ-x&Uvd~h?nr>KkuEaf>4*z3tBN5iqOR`mh*bn{JIC`Q- zVUgSuoU=$MQF>Ddy*ykoOu_d&C2olfDPu-*?(d4RoyCap>k`3XQOJdsX-Jc9xb{2S>w?*X^F!xe@s2PxI6|wW=Fd-gnOOkkE=eY&9ERI~If5U81 z3mmgO3;1Kmx#|gNW>+D|xJMzj1cUQx>vQ2zg4Ku+TD5iQg-mFHH&=6t2BS%dL^I8L zVglnisbR2{1_6s!u?1deNC{AWD4|(YtT`@DJV-oHiJglaNy{65iYSOZZdnL8zX?Pi zs1W-*n{w%ks5+6W%@>jtnr%`)RKunM2+~6J`4^%)Ui55#yf9tvxGf|`FNixX@ z{{X`SxfAJ*MZpjh@fTEDjHu&BAoS^CsZtkMrk=gSX8*-UbgRQDtmw} zm$>s%iq^sF584zh$hx?o`wd7i?DwwE+Wr`_^`K=qwhUBuEmwX<3UcGpCZH+ocxH%~ zD4K$yDmfXe^zlIIO)#R{bcb7rei*d}v5wF@l&v4(f)9EFe=#}daSTF4?2ek4wDjSJ zVoj5JBBq}5E1|(yVD##$adU_eKG-N4-^Iffs0D=-=LS$!0;n}|c!E*UersBR@fINF4a1GFyPj71YT0R;_$>MNWE{0Aq11w z^XiBk#=*Gp7KzXNQ3cOoHz7DGLv#&7mNKp?{{XHSf2mTpqbBO8ThuWnNxg$F7sV35 zv|GeLjoPn0B3N3cjFUaxO9HGDD3fo!2Z{<<37wuHLDQt7i&0Dd9~41~Es+xE#RBX` zJn|qYsz9Jegl<2>3Pq3~1$Cp?5Vg#auk(V-FaRLrc4f!V!#0?bn-6&ok=MXj*dX=|z zL@K5&x1ZsNQG#trxPd4#8=m6nh%tqZS9{_Mu!1X$i^N6Zh#I&w^@FeaSc3bFp3IDt z3f~EQ;sy*`%G|Ecsv#TL$g{_H;_2vuQDz07e?F)p9u~}hc|yvb!b;~1eVd{ddfq;t zi>e_7TAjohTg4DfK)fT#A`TEE$Df8F42{AWL?CwDv^9tgLJvTxej9pX;x-ZGD^oQ5 zQFSYDB#Ys+phBpj9J$>Px=Ei00Pw^mK|D*m5x349tFl5UCrhPNZTm4QQ$~i*J#D!Z ze@c`#dBJq^(h4G^25x2kyP;$f*$iH!tw4tbIjm7 zPZSXa30us1VPploAq7g6`3xz zW9e?u_Nh7PWw6>yFAxn{n<3c5%c<1Me@N<2<`veg9~%x?dnH^DW;w~Yjs;6OWZv+Mf!IG>9+UWur{Nv}G z{zGKSdk!8CLlr=6BNja_$u!;hmxyB%K9t!nqf=Ghb?1nTj_$4~f5y+SCDusC7pX|1 zlqM&3MKpCxX3agWB74Q~r~d#HX~rq?=Le25?6z8< z{n61$whFO_C-U<2f1RDO+2XEYS#nM!5wNciRlIqpp8ZJ>W z%^Rq^z9%gb=*-1EDaY(gb8)0oF#}yzxqR_sqIC? z?9zcyO3ao9!=(aTt)C1~)&0W?<#(~mZqThaKa>Vn(46m}e|TDtbhl|fvgwSNQ>w!F z*uG3nGVwPKxwu%Cx|iXDh9G$t!Iy}S!YdNhDH zh;H!49!zLwe;M{&n{Lp_BNdL4o@{M6wrzwtYzER;IkfpGh@xt8!9kFOhN*bgj|~ z{{XaCt%?dRAoS4MU1eNWMqWfZxEr{F7Oca6S&BSVqv8#>Z7UEbudhyx-9pww{E`@~ zz)vxLf2B^PHH+)!MKe;XJii>l!1Xwm<&n=|I>gcO6Qy^>bTvL4UG11(XfL|e>ECPQ+7&%0=u!h;uttlWW=t_S!U8U z{{V(Ag|zI3+GZ}o+Z3fUH(n=O-xBT`0l0}1o?hs3qWpo{sjF*WNQM}qy=^xRzc4p- zOgMx2A|k=XMf$35y^-QtZrZkKBZy?1210OGdez88!J_1;oHenx#9Fx_RKj2(nhnZ6 ze;B32RhmdQu%b7qH>cU?i;O@|Vj8i9!onmTq5YT#f|ayV@fI8G=c|8;Dx;b9QE?S7 zjFksZXWas4b5m^_3;ka++FBcd!sEBjE+;^-35ILm+3KjezOka~7}1UBz|2_L@^cMU zB!t=!akAnV=y@oeR7;Yf#h8Ix#4%J9fAYj!?0~s%k#T+L0YZqXgr5OsHo#QT9?RlX zrQ}9psv*=)I)nS+F%}21pItTwg5|KEpk^wYCVg>G+yS_49MlFG%*Cujk@`=a*f$N+ zpN z%jVASwM&Wpq*aVCo60rcXEMv|v6q81yi4KfjV*-F>I*%RG&d?eYQXwVF`r&WKiN5# zLxw98(sHjMKiHJyz;HYrQ9T9*f9gx%orBbB)romTxyPoR78(YHNS!rYlkSRaEemD} z$6FWC7qr%CvhsAgJvHa1m={U%ok+ku3_p-wi}*qd{~m0S(o2YyMvM?1QF({`|_>`^5vDo+K(R*W4;FR-aY zwHKu68a)JPw0U_JM%HD)e*k_-9fXY-o@_D47!I3`Bnn!7Tu7~33`H&#ij=UY_BpF- zUXOG56v|Kc-Y5k znu{N7eW}gnY>yUY(+g|_XEY|zHZp9?xK{M%867GNe&bZvFoV?df5_Y6jR@KakgLzq zPE7A;k1r6xOV|%lR8}9wPg3W_#@setY_1X#Q6X4tMMkDpPZQ`z(f9Pn)iz_;aLzfH zcBMkO0lA{gjgIYn)8890@^{k4xpmLe;>Xv&N4#~0_P7#wo}E1_P{Y!!EbFE&CAubH zI(y-x8eZNkh9GL+VZfEztXe4^f1;3hGVw!dDqf+7h_g`$B1UEu zE$=ySt=uIOuvf8!X6wA}h~D8N3PDkGG~&7r6t|6vY(zN^?M0Tp!9;C4d@%?W2Q*zsAyF%tlOe+#VLA#PB+2(qROb*=F=>+yI5IM++#4$ zuHon67_Go500}rg%v8wHtmugbqs7w+En{Oje~EtN@nlfKo7isP+t`DD^;rs2od{?VW!NLMsJr%dS-}FJ=}Lv73g>)M6BPcs~?CnFWanYY?U~PHxOn zDdkI6V?A8@M>}}C;X9DKh;VJ?;e(l|e_ogu2GGrTxj@(mjR-T~ETBVm8n`x|yip3o zH*l`H*tzaiDk9?C`!PT~>=NgGpNHiEYJjb?1;fOncle^!wqBq~iWZ;zFfM9F5a5a) zTx0(L6h&Zi$>!x$jFZI@=6ylsY=v@zuu!L!v2ULj`NfCQBF;ScVP&sSz1Z-efB3qh z1ktg3o!S2Yt|&VQGnV^R{unLET7X15zL+w>l3scL0IguEL&_`rFt#dt3CR_8f(Cnw zMM7SkA{QMet?%}tr?5weZi?##gb46u1gZ&wZqqMjCvk=ezu|?0s{whNb%L-Gq8Te! zQTtJI*aT%V;1+#89vC$w{?P_DfBmm!F5gi5h`Qs-7pN@Bm8+NH;pv7{sbej-aqHrM z2DS{_T4bEZ_@W1`1r}@#e#}nfUtr%92Bcx;v;P2I7%~Yi6yfm11w}SJ%kG7QDV{UQ zcv2WvpfL{b{wP^f2d%qAFDQgk&rSj6G2~`XO3qA5h_060aUupp?0GMzf5pS%f@UPf z!*qsg3vY7IWp=*QM)n1OygkserG}3;>d3zhu>`{qRi-zrlJu3lKNKvFC2hucj}Cqq zOQ;H?*)}Z<*V?y+0%t>+v>aRMxO5-s7F;+f#(cV>>`lPgS^PZz05}U40oHF_d_7Qh zEhG}lkz3-5W-ZtQk~eeqe|lo3bSFz7yAN^ z0vk4l6W*<|`#;4H5)qykhl{8Az*vN=o7Y;GUH&ZK$b_K^*ebt@AXN(A>k8O`4rR{d z6kdPj5KTkdY31<5e;X6ANCJi_`%wT8`1NJO0-#TEi_UT7^B5vWair;Q8PCN6>Me+J zOV1=85`|B3;xp+*>G4|=QX!0!gg0hgMb!!!sSMkheBwB#T^YB%IrPHF8dos23IcnP zl#r9ObY36D0Z=7J>^+{I%u19Fmxc7COjzsT8&C;rB)w6 zCIL)LUY^Q8>O`{)W}M!g$^QVu3S|L-Z!gtXiUy>G1mm0V(k^3TmNYj&NA9=(0E!01 zdSV1Ee@wcffuMpC%OvDq${=@9Lboa%xo#{4d*yBPNIgODF8Qunl0p3@k0qI zuq1YeO{3z7GCK|2a^u&CViq8t5p>Y2xaDsK#?Uk>Sj`xMC?x$D3tZb2RY53q0du zC$Hj&I>MRYJvgG+Ju?Nm*7b@gaV|N5d_Tn$tRjGObmeTE1ekg=6@h1|bLOcK>EXggnq61K6!ebDo zFkZWh%c>wcg90|_zh>x#nP|6~kMTnYe`LCgprK&Ig6VHE;kPcBxQ&D-r3)yN8ZO2O zF4VY0qKjb`@nsMbBXMc}01QI42Z$G!`pzL@#86ulw0ny0d_n}`yH0TkU=&A*N6H{C zaV|a6pY1^?sGS(?O5gt9D7jJgQ{AC?+j0z5NCnrFv{Lv=El3GVy6@tGj^rCTf8}>n z8Veb?@?JS~m+eCU(;jWj<7B1N{8_@;hp(`LD=kD4f$mN0Qn|seIGu=Lw=~WKfZTCQ zRN?r&sH%{_k#LuUd|vnh3hf+u^uz|h)cRMy*@zL2o@Edkgl1ryJw3$`r&F`G2exTU zFR}R=k+u^CTe&w#zq&PEaps>3e@7(Oz{%)!dmS6H&4JBNePvUt%{uVZg85N5t`W^S zWgEE}^2dqU!HKeeHH#bGjjdMpddz~(F()|5e!r^ZjP=U?W~9NTDGYifmNJv6Q0L~m zWVr&`LOxyb(BNWgeNI<0Tg8~2^iT9LO_5_~DJ{Hn5>wLe7zyQYo?0Y&e+&z?@i^~| zlP#5tO($b);F_%T>JP7Q)CjGB zR-R&-mfXYUHwmx5${d`9&*Dc-u|(R2%jNJ5Oo1jPW*Q>kb(avXf?OHUlvr+}4Vtv1 zixzI~FKDvjid;`2-C&2Vf7aTcY=GNp%eaQrjj4x5X>u@}X|>6w_`Vo>B8s=vbxtQ< zw}#{RxJNiSO$~)<8rABHAn^v~)AmP*VXDexd`%2L*`6xh;;DO^Jj*_ws3~QTp=$j{ zLtz{kv(f`OYfXnxh>8hDH}*CsQMUFgMLCMD@LS<*xQJnEg`dP!f6Y#WpyCOtSZ-xo zYkJ_I-PBq8qh3Be2>CbEM@!=DKsbhRhg;o{k6Cb$By{lBPGh@6RZb}Sb~cF1n=Zoi zIVp!+Y3YG>V&PFM$5EpQaOLFSAIc&sHA|ywrPm78USiZuDxHCuv zVVd8Ov9Rt`+!-q*f8RHYe($nt{{U#;g><5sYtn6hMjQsXvlfiw!}>gqzkG3jHOust zs&xMV8Hd!%Z>iO6(~+4naXJK4Mm$!wdYcs*)*lLimBV%2Fy_Ou=PrtVA;7!4l?SUh z$oxP+|R`8}#OHE;|zMoIjZ>VcdAxxV;`3{k@6MrbjoBLX^*LxYogGs~8h2Bw-8BG-+{WdWzI&he-#!(W0<9Qnb)MeP}^ALWnsqh$gx7j%Y#^91hy(qL?Ge9^*O98g9b~3 zstTr*4ZgdE1T)S|i!70sObg9Mf8r}L=I$h{5idZO?v8A=*yqgMi)5>TE&?b$F|V=L z$Lyg=!FO1YrL!1q<;nnns#D1;O>ozlLUTTp&6n9te_@F+In^HBiF&aw4~j9OYpCPp z&9WPdh8$;S5?2n8=#f2(p2Z#^tV}kX_emMKF|$t%x{a0&apbpgM=qyWaM+_q5ks(e zGW&%V?$sb_60XIvWJ{93l-(hP%YSqW6l6^b2!=}TX%`W)8n4oO(n6cC{4u&08hWQN zJR#M(e@|&+p~T`{x{FV9ljXfKDU%Z}9Pg}g;<$*5i#Y4k6dgP;uus$dC8!h3jgtT> zTsD_f9+gXb78-uh=~W{YYI%=Y({Wl+G^`AEEVSifxIL9;36;04f=jlFg}@DL4?-9q=dy)k>U2D=vdqRi%E`&E(Bj^h603TFgqb` z{Kb}s*@K`p4=b`g_W-l0e-gpQAUdFtmDsZ_x9hh;W8k?i&KVqH?81j-%*wrTfxl_hSK<9-jFsjcsyOVChdWyik4rnLBk^6 zo4%N$Ot-|x)F3);{{Zg-%d(-GJdS4iOg3QkO8{Zom7ps zp=`T0{I6%I%VyC5A$Yo@XL6{pG?=H9u;!zcygV=zlRFS~iB)b`kAp6rE-00$P~;ZQ zr~ouU%f2gI$Ml(AqK;LSUbF0L;kMfGZqwz7czMhOC^*3zTE_mD4a#he$x2;7-DCWBBIBv%rQ`;@x zgW-)+*r=#>_{<)sPg{+teSW3ZsGoLeyKe$iH)$~9%tGJ zeWA()m?g!cN>^yuZLkzkpSRi^zkifVk+BmpJ3zut21PkK-`i*Wvx8RHrov4#F@-A( zeu-L_7fZtFSYN+Hf@KHOaV3Uko;;-rUT91G)4*l>p?lEZNrC-YGbf|+?vL6P7`Sl^ zg&S^|*f7o>aBj4%SjEibQF=|7Fq;1W(GL+)+nk)M&dggiJZriyWjvsN7k`hY%rldX z=fvr%Y`IkBh2{ue0UNINQaX&fIm`*hM)Fed^uUpbn#8zFG)U-!jTcat5bFwMDVD~U z+IOQXIFKHlq*yZ=kuk3q$}=`Sc#Vc+@iZ17!1Huj_a$c8CQM4w5{rr0<;m05RO`4v zClrxJ4U=H2Y}UB z#X;fff!cuaqKQ32WjBcMkiF6*Fz_r$VEhU7gLiKoY`i8z!<1cqq*pqp&}Y-D5c(;- zJ%uV22i(IIgAp<6+%LP`-y1Pz{#T@FM)f*a`yc3rXWG z+sIkayDr43jPMgX(%wBTJBLKP(ma>uIP}Y986Gt`kLgO-kYX*GOV?#(?tYJ$m7{M0 z^NfFs&e*vs$0zcfWyy-w@jh=`Z%Jq|^aeCrL^Dnm@qa@AVdS}|3|^$I=yJ&1 zVyVLskX%l~5b^t86j1^;iZ^8p*2-zo>$S~F-WXJ=aV!zEv@%Z#MNl?l&4`aasA8ZQ zdBZFqxQeeyn{5lNhI(iz?qmkijUE@{JW^M*nf zeT8Ih_(}?-sDEagwDMbGR1MTn@hKqmcw*%Pg=sOT5<#j36R}JWaPx!ekBM_C5p!Jw76UVh$N<%~lq~9ZQcGsc}LZ z+`UNc84+{U@WRzf3GU(hQBT~yprholser|aTcij!Fn@h37r8HRVB1m;&LXf@u$|wZ zhA!m^Ce(nE2)hG#dy|wMOJ~^G=6Etu{9fD?M!_4CSA3wMNF-Sl(FWK!lIkOI@ct+! zVCXb`zWz8m{eY;-VZU5iz%qlV8ca5%8RP<1!q$Z{u+CtuT$iN1n7Qs=;B2B?D-=az z6H@xzk$(w2hHp!t9z$dm3twTQjE}@W6iSS6s1LrC_G>11yR97t~}HB zp_K)P4gRdNffebvu`9W8ekdjkEw;0o^YML{5TZ;GTDdP(&{Zv1OTt6W4^lDTVg$$U zEV^LTMbBdwTcY&9)T?XA8^{8B{g`qu*fyPlB7YpT@bvtksszD~trjXLLfwo#VUG`n z8L1K^ON^BY22_WI-QJ1rg4B>x?kXO3@bvysBe``B%idf|?L?mBXwY!|XR`r9hYh$& zMR!p^SYoJfF6fFe>MG$Yk}N`n5hT;$;f1pfK{ncUk5$kyB$U{dTU#C&Hl2f0Jx8ZZ z4u7B}jTi#p(70llw-Ir2feIi4g`eLLATAB2UMPYW+sGLXT%aa26u1Y7cbR{!V$=!( zw$sbQY+FXdj7A8FyOm;87DK`}25HVLP?48XJk|laN@fh)wc6dq{b4TKEJ-HoRfABG zfx1O`A$Vbl*s1pdNyql0Flzc70OzNF_3hft%*??srVImdDd!jQC@Q?2pnv%B%n;WeZ{4qs$F?FY(#RXF1uthD|irb`K zptCD+f5ihaQm3(jZ@qbpP&$&^C4a)1M~^*CNjXSF+4u#Cq&I-X)TW9Tl7>wYC z2QP$>!8M#fDhWjebcjljwT5m2$bSqhQCAN0(GTP!f!s0z=eo zf~`tn?KQsssI@4QWrHNtZGZm&iZ$Yplsk>Bg_lUdgfg0SO6BnsLM_Od=68rnygd9c zRRS1;o*Pd-J{Ud8C5U>noOvk0gSaB?5e%SNb~Hf_e#|7Pjf`iwfRMad-4F^D_j75& zPl-Pa4N0w%wBp_nmwm87Qb}1-E`H2H;xsvIt+iC51jB*2c$13m7=MKX1Y2%V_(zM1 z35YBSbIBk;^McaDUgGv zUtWDl0?I;8P3yUigMSdH>`2Rza`s>tjKj8D^NMCHbisFbLCX+zF6G>aevD*Nq%TtC6pIn{ap^5 znByGz{{WmbxHhntUbyq|7x6_*RCw5)%S*Vwqzyr+k$Eo3I(nd$LOfGM&}KgCgrZPW zM#Z-|b-sVYtV$9SK}KtXID?dKvx25yY75f^lnax?mYq;q(Nz%K;VzI98W@Zfxz4Qm zw2GkEK+M#a{(o4M4TZT`AtF!y5{U>ZV?#%)8K1xSV51nw?gi=PAk@Cd!yzgd$?3Ra zkPDAo8bo3OaJ&2bP)fuaO*vy_knwn7$U(79@#Lk)q!9%@F`5dm5_q9>Akt;aRFMkQ z7;GpiH<+5>)51_REm#YWlJbiSx#79R%I+U#1%>KBxPMtDYtk@1!D!ew970~Nh@ff$ z`vKls8pX_^&yC_`=%xt+g;Ap_ieD4xth3v8}9fYeo+y*jvHR3iXMW@!;cOXw>s z?>Ts)pV+0ehB4Av^esGaU_68unM1#SC7+K4Q2+`@=u;vAw8R3f+{T~}ND;tUi| z5iXrE2r04_+{@vJ49St0LR{0u5V-6n`$ppNL>V!#f+R_owfiFw4d?^6SV{Xa3TFq> zS%3Z*f!Ik8di|J%SjpwX?L;K8f*X(8gHRYysW++)zZ6P@WLZZPgt&$P8iU-YckIM1 zR0GRqs}QCWb;s}ikqD=hRF6+*h7QHYV+uX1mcm3BtpQw zFeVu%iU|Oo<7XW8+4T$?k~@f5i&h6vU4;Y1esDUFjBlsR+W&2esarW;UxPO{k z`qSliC{ytlBP$2BPb2Gg8Njbqu)Qu~kn^udy)M}?!*LR1FzonMJi3mD9ruf;#I;g3 zD^sdk+m3Efy|Ufe`SOg=jXo-Avq@^fdn2Lj?-WzQrB;w9XPriE-ys(kk<{RDIWpFD z{{W$vm5poVU-LE&71>QTlZ#xL27ldo*;h%X4&LbQCmM@;99CYVRqf`26kJs&Dgqw!Pi3iDCy#NG7wtzXISm8hi28*#TA&$B%e42x zLmF}ZQz%P`WUN=7qSD2|T1bfquR&Y*qqoM*zaU!@nr9eoI)zW1Ce@kocYj_RlljMW z9c0M<{wi1Iw)G(^qcX4X@bMBs%&BOaA}2i{W?lkDTLD7x5pfJxs-eTwV9%rlMVCnh z$#QUp-YzJm%qi8R+CGe{If=3Syu|azX6TP}BWS~uHN?(6`5m8!97$SJVqGztH@Trb zjjgkKh*9ukiz{aR!6J>ERDUzJQhN_=5{V~+<=pMW^?k=gcLGXI3g@nA-5ci-gZJC0 zy47*RH2MpUJH@VGLpDZ6h~>u>cEgtCa` z`&8klMLDPKTN%l3kd(JPkYJT|LURJ)H*uA1wptU)+mG24NVyN0|)uI_m0c?Cj zbYw+3F2!-;jR>Nph7*m8fJnShR1+>zu~r&pprw%MbPwtG;!6_rS!ANfGRt<_BWZBP z#C67|ZCH|Wz1YJ-jDK?b@;9QaDJ))+5}PpD3STM3)zSStsv|&+gSU$FlVr zv(T>)Pvg47m8y+}+8rlEwec=}@w<@A7^o7s9zeIn*k!;wa%IaxzquzzBzDF~7vK|qx?ik2an zx0kbIpJoDL!;wZ}L>!73IaE_lDC)2b9aqA{9wfw9JTl?m!cl{g=6sn}I!SCZPYF3a z;&r*iQ841r>=;+blilAAHFp}L?16gMiEj3-gmMgA z$!Jqrkry=Q34b^Y<~O7~E7Uwt#6WFhJ*RkZQw1?$+5K=RxSf|A;c4yzFPtUSoOc>@ z#^r3cfF?X%DCM+ij@^eXmS1!NmJmkZiXc{Q6o{-rVpl{UQsU`|Ln0`euMAyCKYsUiX2~Y zl;s>rNmd}Zjf~u+hc0nb8ycXNOb5fL{4r!^`y5&Hg?dzDotf={>^(%PKS`yxD$xK- ziZ)K(ncS<-IFRB!U3i?eE4Qp(-Y1o+6=J01%zvxm_>oK>5~Anl2N~E0%zbv*{bpZj z8#Kf9_IQS|Ak^0vnE>aXzc^`W`-ygN)xmI%x{N87q55cH>NqpZdU~Qul7DE~xn1HH zh5(cvH%SWzh8a{YBV#$L8DEoa@xU`GgwfJ#UrUXgOk%x*)S7X!7bMNO#=j3g@j^1b z#DBku!gWj+y8_H^@btprEOV}GuR$t-VQsy3B`a90NjlMcBx9;)8o2B`AGz3d32|-g zp|;mawj)DXrrJQwJ)gA8-kA~m;yH6A z8xT{-OEefu7S40V+py_V@tHes4A9;`)_(*;m03Y{Ozucx5WiqOs?5+scB8yxi7pcA zACzjxnw~^9l`j%Ah$*|-ass=kZcBzXDR=Evj&WI*Z%-0i)Uiq%ywUk23_{sa&;IYE}YzKlb_5+Ib4XQRW;c8 z%T1$;%!`S`__`yas4vveMwBW0Q|MRPWaiU;~iu>M|-Lw~67!(mtv zz|maKFH~21QFQ`XQ*`-^vbC#yq0C}bB@pC`;u(MLiiT({Sb0UM&r{zMW#?NV@d+r9 ztU4WuGg9A3+V&@=pr_W3%?F98jEqb3f{dH;3SWN_8$!bPbJ7-qQWY1bu+JSY^2&`4JS{5>Z*xNJ8`1J-3A|1<`b4Q`X%7))?~6$L3s-10-1ZY@U6&kicpJ=!i^CdX6l1d- z==K+PZF@y>PF%YzmFgSO8?M*7Ib59To+i9Fy-y=`&wUDX{h@5z>nR*??2S~vRVz5y z6J)u3k#dafk2|f@tbdrR;&L|AzK1=wVEk0z78x4Un7P3*OLKZIr^#|u(kmY$EOauN z-I+X;79{jxdu4bqr7HCI>Ge?#CLe7;+An%6Ju>ZVI~?6kamEa*Pi|cwT*)sG_>vsH z>18b~40or{z3HVrNjc4!RjN+?T{KHEAlbOK=A6U*d*gO2*MC~EwNY}dzDLq098nE8 zhBFyA@fplGtlDG4?LaVUIH%PWw*bN4!_{PAcqf7pk5dz8umy-24lZ6Mci3_?s(jm<6n5v#OOX_QZyAT!zxh z;asD}){HMCZ>0Fgw2U6^3C8tGTdnTSP4WWawmzZwUVmS&fmreSWXRZ4{MG zvK(7W#eY0sd|4p_5ZdqAh$Y-8xBErx!6@iZg4LRMd_@(h8_*=1@BS!mF?j_x)w(A# zAHxl(l~AJ1$(qG6pk0>f3-rah3lYnl6h%mZ8y-Dkn3o)dqo?e~S&a`_*mn3#Vv8IH znsE@q*+vc-8*YTKF(I4GQ>p|ZXa2qzRQ3aId4IA{Jz}Z~1c_0nuKZC{D^M$lw2P=! zGA-u5a72{^<9=LG3LCi1#nTK{3X%k+?&ZY?VyXnC$$imf2ywqSVySf+%rl`cQuw|o z6>S2nGj*KqH5fLXmNI#}Zn$oRvJh!B!1Gb*h^j%gw`mr@)I$!A{TCRlHOMkpUcg555h9FCfNRkeoD2}9|lq4g%>wTe# zP%Tp;g~e~#iqw+A<;a_RC5fpopmyJ%vjZv=LW>uzFY#RqsZ%-~%ZU-@ziI#-G>#j# z!p;_HMLbS#HBNAG*s1Pe%eRnq6?TIeTw3(q`=NWN7Cc31{h#o~?gy&~6#OwvC4Zbs zmmXa@-2yQf48xpy_;`3?7c2<4bm~Z}3GM~gxXEJI+>)9CE?jwCaK+42G!XGxc^8T( za)Tn_&*6v;R;iJ=b@2ZH6hSQe4=={xs`(@>%7oZW-JzdP{Lq+!R1=NE;x;;p1;Du? z=q|Mr)yw^56)~Y~!oY6R!vzdTbALBld0iG;PR~Rk;cT1?la;Fwhm=rt_mo>X{*1@eE1sS&MG9JK`#U&!avJ)MALQuudkK@OMNEY(wN+P*DQvrhgl4N1uu# z15sPG5b3*|R^(+x2&&q@53>ch1E?cpsjo^AMU;%+Ag!Ty^$&^ydyU)#4wQ;2WXTVT z4{!%_EZT%TH*<pbM(c9@ zsPXK_s2|I@XWEJ&?Y9!aRey*6Goe~HLRt1_Mhe?xp{T$PRunS!`*2nn5wT@2-hyTLU}h{=U`sAd zwn^0s0hDg@HnW-!6hd!9b#B|0d_L3*1u`MWb3J;#7zLJrARcIz-+yY}68FI~fb+4d zf=&J|s6(}dcOzz*>QsRs4cZ^fplSkvag8hwQ-ErV86R{Dv)OU^1}! zQJb92dVQ!hB}Zb&h-YowKfWA>h=Bynx>|X|E;b`Wccsvrw%#aVOgWR4x0FG3BwH+# zSZehIzQ>#^z8KLS!+&CQF`OSUgD8!fZs$Mc;e&@RBV;vEyR<=rQcP%rsJ4lsadL`| z;#El3cuyoBvlmg38WQtjCMbjUh81d*qReIX6UD>##2|6X{eSpk5Px3hfPTzDaT^0NS1qJp ziXlv+7w!`*xS|yhM$Qp#Nejae2O*{lBPBw%76Rl1FLh_aKh`RH1h277yq0Tq(M%JB zCjrIaAG7j=kQkEWS@6Nj15xWFHf7TZP-O=U@c6XQZHg4?NywJ(NERxjjH{P(D6L3C zsdto@NTyJfbbrZj122VQGJ-i2-GJrDVgkYCO5HlGf@Ro2#l%A*>EVSc2$;~V^}(zZ zAW)!p<>ansDitLGABQ31;bU*f)NA%0GEWJVc0aQ)|jk7CI@3MqGN9q z5-6uZ+__uvK)eSLpyf2SWRNNYIhPzU0sjD6K}(K~!+%$tB-KjjIA9ro_TH?b%7zlN zTsrd@D1z4Fo2Vh$ix;RE6skjJUkXH^D$D`N;oNn9%@#Jc0ou6q{{Tq>!PLRdDqY2= zh6Gbaf{j2(HG`hRai03p-ixdlnu6Lk3Cg0j8L4R#PzR(0s)2piT>#2dqd+Gr!dx(M z*rjk~IDdp%7JuayJtJm9ba^iq#nlW6R15QnL6Y;*=(vrMlx;BmnyEu-6GnzlOcy(@ z=MDkf7i=Irl#1AZOPDFkgD8QhB7Z-MAVM6N^7QzpROk0ij7 zz4rNehm zR2(oB0^`=6oGLspYOFYGyt#A#01O;^5K8;Hf8v5(q5+tg7QntJvJJ0N0nft_hlbSV zKlg}SFp%oWEXpn;VNB>)+h(JuwHCyf*rvrY&fop8AO}t@5+MaZp#FKlyAeWe?&Sj=!W1_MWW8P?i>R=cp&=3t$pWfVM#bb? zy!=pA7TqEoY&(kUpjArS10mi1FXs)hRReXQpTz?miDSYm%I=|pVc4Ta98A+*V+Kh> z486ylyd~{Gvg}+=!=iMm#Z-3W^c2R+iGK(^d8Aku3_hyWUZL>B2^(RXE!c3C(F?+`1MFItsR`YlnL>PG{JJ*IG0EEF`lJ!IeqW=IQ zh+d$}eHmR4vko)gZczcT=EO$S;=Andib1I?6|w0~sEiTgxOi1A@jws~i5KCBKz~jR z*7S7n#1UHpwKKGfiXibL#2Fy!F>BNe^qFjToXXfJQuhIF%b&ITqA??}=Xe7o>FR(0 z8abY+(p*&kq8OG;3Ptp69byp8%Ghh+fnwoB2Nr1pQI|MVKk-4RwqR;pdX3Rcx+?n= z!=&FL=_`f@u-ErGA7{Tw=G8WNf@EkE`joF_riSUFHrIG5-5b+C*-RbMA42kFtj%BAN zb3PCpt{b}H(fFzb89pRdKB-F7f#=x~Y)iw{8WWDlXj$3JTV$q_W`FadI`s}i9XwFb zrbjehBF5DT6Ceh9dWzWFVPsUUEU8tGc8!;{z$*hRRL+a$%j*~7gh^AwV<-6hbm zySU12vz2Ff#IGOPfP9#fsBC+;N%J4+M5R<@eI=%zs9q;D!Vuk`2#S(_SVH8vmzclL zIjPVhOqbf$CiE!%g+3l7HH*)2|20?=HVR?D}{Hlid#mVe~1XpCvaSSB_vg;O4F zMpRuBCfPpFc8c@NU_T=Cz!w`EWY1H!DPq{0Z8SE6W78fIec1K=G15206Whs>$a;8T zsDQBpH1~8qqFMf=bmYecu+2y{AgT`+5~ox-x=5sP;w{_4i^yXZS0rOAJ3-o0SapXB zd>madmo8lzm47yK7TC6_QQJl~r{MZQ8;v~b6XXTboI0<{8^lKPGjh2L{gR!hu=z<% znBj>T7Q{VELO*F>a=yl6PKvR(X**C6XEks{L~ba|hP~9OOk$@om1<&q$hT%zHTDuV zVNIEGIWb773jt%DF|8LyXZIvY&E}$APzq!S-kK*&Tz{zo6S)Ko#)vd}X-KswwH$^; z;oayX1UP9}s{LOkMdB`>oMp=7zLc*~X~kBm+~*L9Tv5+RyBU!##nmik)EMX~)TnWL zgD4UyjYrvlnb=V}5>8omW%i;l^H%Lbt=h36G2xf+``{S!pOVbd-6R5*>z~<#s+``& zH=;^DD}T63GzGam`jfau6-EJvU7(a3B!j4MhQ`?rGgkfZ!~|YZlbMA)jeKWg zWVXrjFa}@{W;qAYJzKGFYMt67Gmf37ng1Mos74D_Ue!$cw^>2ME#< zDat!=+Jl=w#4ZrHeT}{r2g8^7$EW;$9Vu*|Q-4t6qLr5dSDX<{?&9i>#w;xUDkm&Z z(s=71BQ*k*lAzV4_&AbnWUK zyoVDQE=oiQo0n^*0n}v{O(V9a*~JnQk|;MzIMFq4hnM-pj6!n8&H}{vHgU6f36`57 zB|;yTk)0c;<+N#vrV1&n54GGoQztD6S@y5+=t?NRJqk3ZkXZbV@2^T2yhN~nPkhsEsY7Ew>cQX=>gsOnI$ zWh$v7ytpE&EY%xq*_-&FB~DC1$7RWL@aY3FqMNAKZfw4CTs?h_#S$Y!8SEq21Awhj z{wOOJHKx|5C=()WldYJS3XXAcmEeV{S7{E_C54Hx*QWlRJ@KY9>|GAW`hOsB2yGA2 zG^U$xe9bmoL`vQp%*8*D$5({6R?1}L<XnE4+Hufg9B&y)vZs1u`rfEjZ(xR-XxF z?u>bPS~cRs_A7fc`hQ6<_9(TwjJeC1aNImY4a3P<@*6x=pB6e7WLk(f8zM$;MA6=o^_VXtBT?M z@l%sQv~1$vq-tF27@e8795<$3mm(rB7aVi)u@W?V;o8j{9sEV>N-qn z0f;0Un;s!}ZjTSoO=TG$W9C27Z?WOP`)T^chJWprtOA)8yg8+@-~JWk>AfaA=jHL~ ztcNlyWSoXRhY_Qa)3Ma6Vw&{u!%7`C*%wz#CSIjaV6C@m5+QKI5v(p8GcEakD3vX} z%&6RCrNlQyR-(mjcYZFID%iP=8E8k4^6H4Oppp+;rk0V#fY}xdYUhR^w|#|evUD=G z7=ME1SSxM$aK%qz2W_$_H3}qh+agw_w){}0P$fg7Dakw|_+l!AlcOTt%aZY18dPeb zkz;li{7@k>o)NUiIANSEy6MX3s)UnKYTmxoSu*B4DUxvV>WEUl!nWTS_+e$L=qUMH z378HXDhz$8+V3qLshmV!O!o3i3?f)tmJtl95Hb}W8I(ly{|Yc z3bw&K)uWp6LG%>8BS#uQGWcTQf?y8~p_&X>R8f<{J^Fve3>2eThIG&2>44mUJ%5HT zHYC0{xPr!n%3r5RdoXT=Y6NydZF08c_@QdWfS6}Z2g|AoI~5&<;Ah=0l@TT9V6I*wBsa!zqp609v75kkMk;(xa28|-eR zLbifs_Mof>refqx6U0ywq){#vwyH!@+YkbB)63b57TqAPK|;uG5-Y8WU<8+0c|=vP zDgesh;rw3A6DU9lJh*L&AQE!cx_E7i)RN91T)O1DNTBOuESDI>0C33xXuVfOFiIBd zuE@28#AsuwhmPRa-TB3`lz)~4KrR5H^%wMtyp$ywv@`zzTEW;FN$q%%Klq}ebX81{ zjhXyV&B-cwZ4>eI#S*4lCEfJ(L@2}7ie=yYLj~4AZas2m`!P&}1WpclL23$)$CRyK z`C`j4ZystqF$(Nr+m|JZp2eOa%bzGFsakaU2D1g*n?K)Bl!5#$oqUW(w*dT?@Xg7bw7F0l6tpUt@KeVv0gp+wD zR)$~21!4&yw(pCI*nc7u^0QmO6}zfDYaWd!W7&5|j8KXMX!(~4%SV(|L1xffzYGB{ zAw!2>*@y)87A57=5M<7Tyzd5nAL|f9Wzicw>ov4^p>9>KLOj|5=0j-R0X@Q;UsKKT z7LbWf>3*hdp{ke~Jcs zlA^aZhG_~G!DX3FN#cmC2_V$IcBh13)tF+a;Q3qZ!<0De1;HLAw_Ch1b`HRak|P|3 z8m^#j0|Ps&a)08s4j#oQ<+ULmnA3#0>?FC5!x~0{nBome=!IIuROc1hL8J|dsS`cK znWwU_(QZP{CD&!s;(>5#MY!F%-P6+xVX&ZFE!uSGSwNChWjVxoFHAvm8zAZuwoPWE z!w^oSG`|kM%s`b%8ByK_gVXTD53u#7?r)_30P7HN<$t090sB!5ulq*>Zx;_#Ko;L2 zO6}hiL8qLz{bW%9p5$|)LU~=M-4HT9c&@uXPz(zYPVk;yAMr$$5{vd5xY-{f((9sz zb`y1>nhXy>mVJts<8vj7p5ttTj*^6T7c-#DkqCHup_L`7JZpu|Y8OcCpee>XZNBx37Qx%%{{WUCLBN&6$zrtx9!rW@pGd`)qUyzSR2STJ$esq6?yxP7Rm0Ly-Vh7`avg_mtz>G1HtFlxirWkhwf zGVt9M3`X}JG~Vvz?-(WOAVC{LGUAJL0s9J46n_F#;)oJX#&aA$2;KXk(IIL^4>`MY zK0Ln6Ty`i4O$&OjGK(P8s#c~+{upYY!^2eGXuc?w1wp+~CpeIxp+`iik*R}cmq|cS zmk2YP=Qv<#C0fQ?d)5wda~m0b3McK;_I;=UtL_SFaX%3L(4;|4IwRh3dELX+1dC!r z?0*kvEWXSYbY7N3w>fr-TK@nHK>G&moO!H5Lz!92k4~s^6wV3jhzmHqz%7($Pwvky zrGrqnVY82Sr-}%;Krc;N9bGYT8x4>e%@T&3C|xl?Y7CUd@kc%zcwzv`4auV35_J?p zp+qF@r)hAMDpO-aGUAj~1>AB6YhsDTY<~%J+(~-PSCI?^M#=)>A#;XmMh#waZ} zVgpkG3Q*bZ5tL3n3#4FY+=qtJJky7i0G53TUV_Yeq62ZG%A|S-+xNs8vVWo#z2TCV z7qbvE#M*TaC^+^ewq?F9V-i?esJ9C4Py-A$C7ym5gaYbYq8AUv1f?p2al$~i*@&UA z2b|YlFNPw4J%+B2dcms~aAaS#6b*pPXU^Z*gNG!*2NOLOBRN`;z1Ep3@g=cVE+7f1 zZ#u2I3dmldz@c}ShT)5r0e@5|xK`$cX?rjgE_CB>22G-NO(X10?S8L0D6Cm@*QIh}hL*1l^z| z436Q)V2r)t@kJILvb^=_i`0WMOkrA*(Q9&ZcUn^Kw&{CWBBI5p4}T}uFGXx6{iDG% zRVh-nCm3naG2*py(h1Pcoo!_{bw*YCSTZwqk&xO%F^Tgue`<7MEL8T5iKpagTe2R} z$XgO_?LVA!xcOF8uB#OZZJ5Yn=0uU{d0D42B5aFyYW?use0+`$XIXFe8J_e%t+*G< z2~0E9a6E;t-4by+e1Ab$VvUcz7v}J-5@L3Af#@-;s}C!S*#kvPsE6gC(k{< zZNil;i6*2l#IJ5fO+=p|xxD?}aCvD1Rh2(+tVbJT3B)6pAeB#K8$p1+{vfk#vtS*) z<9q{B7fs1|;LwO4Z3~CXNTqgi1Y{ zq;`1xd|4OE{DkFfUnM)myj3PxC6^^-!M-#hx1^JXGG)TC;%!eF)Yra{*feov9lE5D z?>H?W9;k{VH@b{=1G3Yj;a41RNw+5k{w^D1E^c1BR4yMIeN1&PqOK2-uTPA!x4a2* z5qIv@U{M*dS$~Rrs+nqh@A`ivWGP;lG`V>J0Rg8tMkC=|$Np&F#76Wwj%Vc^eRWkh zOvjDHTVR+4Z;0}dE{8HEy&_i?&^55)&NWWh92_7%7}bh26E=>S^gY3;Pr~n2o*p@) z;JA3QNb}#A%hgq5x%kXmTO00)>X~uw>toKk{8Z~iUw_Dms7>!KTcbopL9rDu;c+U% z3S}c_oa$IO>`ucrWH-u3-SI-DnnkZsq`e@q%ex~ooOzc@S;A2Q9+8Zh5umS8k6cd9#%UYF}LO>rV79j%(=m)50eee&sy}I6X=WR@H;J`mLRT{^0hoS5SRBzu}_t! z$HZ434S%|r+r^6s$GA&^InR;g=_0In^$vfu4^<{vr%RC&vjwfb0rsNOHpROeI)fsT za$?Z(Y;BIdFJ(E*-%?V=h5{LqIei3-Q>rdIizFGVD1f1HL6zz3In)YgL>q2}vXT=* zNNxuk;f<;3XHc)Dx<0IGSzLh-Ca&T|`CMhaWa z7Ad++Ec*{AJYQx2mmJI5L6X5(V0Og-I&_M5(J!LSvbhqP#%!qn0Cpr-dy+G9GW7Kt zFn@dHH$!FdM;z=6G2UzOL?PiExxq07vfZZ(Q0gw!9aayp5f(K!r54f>hd~NVyVkuq z#O=L83E}(Wslemh7Hu4tjkbgoIU+`$Xl6@3qUlKWydERCIrI8y(r7#gtDn9x;cJJNAEvb&v*_dri($W$=IyQtArX>&<3Qpma?y4q%_nfLccB3-_w?^$EW)Kxqx zFET(runRkch6r>quBucQwtt9OpO!7$vxb*=h+{LRjTmxk$oe<*eeE;*RfgP=GTvOm z7?-rEZ}f^&V>Ql&sYvE|hYTL1L4OZ5NosJ&UThruLv&GVDn?W`cO^NYONZg%fmmn^ zihE?V+w)N|`!IWuQCb-1pQQ(2GC%z<)Y|;7>uGgH+IH;tK+ustd|~CgM~O_EK7Ld% z`!f0<+@r(xpb`H7@R;={Vf_nusi{0|jWElcJ*r)rqZ~HzCC#7wF~189!+-9531*hr zTu59oRWw)uN{MqH#SEynU3iE&UD^t%aNPs_MW{~D4m?S9q3sMEz$$xO#KAgF)%J605mrh$hvZdBY$-nl1|!ctusA@X+5F7&hbgJy$3i*T(EP_H*aS2VkO4N z&o9FRQI;ZVlB&pE;fmCWD%HtVa0ye2Etf$|6^exo=a1F4>z8{YbW<>OateEAHCl)2 zWZk#o;edRL1yR1FOZy3}PbIE5*~j9_7wf1Eh>D-|UYU6Pe9#Xf>VHCjY@;Z|HB-^f zog~k!dwjiLY8K4J#xkv1*u0Y}Rx%_uuUCcuZ=|Izv9lIg&_>dU>Np;W^vnMMlgqGD zX1zI6CijCmQEYqq0YA*6)qXYFILzN;mzx&yl)wH_w%w;H-kdV@x?bp+!}1CT;^Q?G z{z5UqeoEuZW;RAPbAN#4cI!JPVv{m$J;nBaly6VSErhoab2aG!EZ}5vCMEWlg<;>W zSrhtFDc!eAykwy&=@e6Pls`{I<_p^cj$`^P+cJBe%QVxsU3Vjfak+cTP^(k)TQx>+ z(tGH6#*|TSd}T&>njaReW{^LcP35o{*=T-uc003KylG08et&B6ctr%nxQy9l}BDj&kdAD}-$vmI*LuWbzo* z+jzibhibC>yUC{qtKS5%uOV{vJJaZ!u5Y#)tRAQ*@ zs{1X5d(J44?0-eXD6wR)+v|c3#Pq~Fk0jYVk>UJMH!g7jEu~2}&S9^!azwI&=)c6l zZGxK5RK?#ETV(ygdJi5$cQ)xwZ1Ud;ek6rlD8dl7&rO(?a>oMeZtA$8GG)??^m?qu zg2euUHQ%H&AI?lmVn(ptI+_FlFmn6d>z-PwWRcE9%YQ9nHhxPZbeaoq-$cgVJs0=0 z($vw$lva4VZ5WiD6!TV>U&&Sy+?G%6dVlL5`kCXL-y=-nA4p6=fa=7@R0>>ueUpQei})tm{bamwN@p^k!bekO|I$?>V%I|uZ_ zki}9N(tm1=Zl(g)WZfht-9_$DSlF()I!u|cYsituUXECMY6j(32stl?aS;{gC3Uis zi|r!!EMWms4#JdEUR>Qo zjY->?u?te-gO(PZh_{&OMd>JBq`Oq*pG?|$RIw&pCA(_K%u`O-;SIWA^=@V3w$vZR z7Jrfpw%F1YNm3vrE+c0@$~G^$i)A~L=xOwtEIWv1FfB#vWv7dKBq!1<-Z1P>b*aA` zdYG;CK9;;UaYA3%PNV@jyFFoDfrT7PxJ9BwERv(u9i+|+phP4k;fBMLTQ=q$#0}S^ z1k?4l`p*C(LEOHaVdUY_nUInea$M3evX#~&v)g~9SVlWKkaWVpp5PvrF8GDSf+>Co zVJr-W=%{d+i226Vw~2kI$H|Qfl?I8FWN%G>H-3_m?ijh@zc`B>a-w)j!Kg@e&G^6yIGwC?l zNaKHPiIqjB_mecTL*X9eT-WzTAI9=MK1#98{Ffip#MM4$xY;&h3g6%FkFw#NspRo^ z2bUEf)S=cGId38{h0|i0m^ikV<>7~-fc+s`$gRu65~alG_eu1{GLpjld8qKk;FU(O`P#LQH5Biy|)b=h&; za?2aA$$69mK8ZcY1J|F6+Ji!_EJoc|#RX8IEF3`uGg5&a46t}xM=nS@fbX$w%iaa& z7A_FOZI3903O0N;=l=i{Tz3{_wyZq8n3EL^AaE?*;fgehvd&PpVgzl!XOtzJjG=#- zWO%r^h5@HV#B6ZdTdH`vVFggjWJ9XJxpoZIxo9x93K^tO_hktUjFAxruw4SXo)aPQ zK?MpJd_Us$VPU9SyR(RC#TpW#S?F0r4d$Y4%kgnQ#0iP9o!JhTK@E=JT$j6<3^1lu zk-N=rEr=CHPd8(cb+AKMxViq!62^atU$D{U??|&ds*PM>1PM@XSS4)>85=&Vei%1$ z)^rJ3F>hb=u%alGY}^Y}dVF5g5{{YI#Afq6d8osHy@YQ3x^?{G0V*V0Tk`mA5rz~j zrS9feei(&EW8I$Mwl7jSI}7aV)!~a#wh&Bi{kUR*53xO--Xx2N(N#UWQ`LV<#o>w3 z1B4yOcA(qEb?4M`!E(3>!Dr4m$L*6_cAUmmngu*209x$fFqHzwf zT9IH)I0bF${;_s0z!fdwUMQ9$PQ-HIOCUy8$AHJYk|4H?1&CU%!;gOwXW}muEF;8M zeUXUX=7SEoyXDmvMKjz%IlR}TpsEtK!V4Yg!mt4Ll5*t9VoMe#^@R>06`vA2JvIhJ3BB}=H6z0eFPy@VCKFj3rsNxXs#V2rNUC#3AJ+dqwd6%wFV% zMY2VUQVttuCDJ6x47o0&uT%(K2b^gKQHTgV((>u}pp>>GWL|&c%cVc8c-x)yB*p81 zP`a*wiO|90C7r08JTWZLR-kg*gUhNYk=S!DcML+zkdbogh+Hrkf%Ac2W-Ld~5ebrh z%rC5|=^|#xd-;?HVO9h#T6V8jSJ{Qy#SR%OZQ{F%D#6@z*xk+qG+|E-nxs~slOrcH z;=l04i+NH$aBqJ+lZ6TwaU^@qm6<{%fm9Ss?(O3L0NVq&5(Z$&14yzF&&#PEcED~% zATgEW%GjHdrH>Sy*8C)pxt*0^1i}gZm@?(ZGUL1F?uC}Vt^%Wsdadw12PSkeSgCcBp=I$?%!mJAlQwM8XgTwATt{r zG9sMf5+i>>&}|I5t?@(`s29?I^27=hOU;TNg``4CjONDPQ3VWU8@aMofl@ONbIgdi zQBuSiD`mo7A&c%_q)86(bAZ$pRSMj>Wp{ib+dwj?R@?4a9>UccLW>AG^$a!~jT#;b ziPTWcLL!P zU0i=Kra}#79k}xE@f0!;CK=8Om)V9?h%tCTgF?AVDF;1B>8CetXi=e9R-gsrp1ng1 z8Knc;GCg`qAO;+o5#f)dx+^++w#{u_`fxIXWY%kCX{4HkzD5Jvh8W zYz;>Y-kVobFP*SRx)RB-1ntTz?M2qX9^QWu9;I{xAW9c`(!EIpTy{J~yY@fji&9Tu z6H|p|X?F}1DN_S;c#Bo{bi>q@0PbSFP(j!cJg4E}>4+0`2yxP|RXO!(=MY@>Fi#0< z-=soEVHai^^N3v!T7rU#KNLdcL$o}4dr%AoM@*HAl9yNbVRPIdraT0R#qmOlj>CUv z9W1ip&!+F3I-xubTq}%|{R|Kj19yXtAV6I!LM=g=8#vv%`!HbvbFbRB!chnX#?)6N z`%wwK2;KKALNx4F_U$6(G(jRZ^S&s8_j|oN%c>zv!jfOrk*9_s6@s=op11LHh(=(I zda(AFrs#!^S#3tuPAG!+p~$mn3a5W4SrvoZYkEWpgt?arr?tdAF$A+1CF)e@>gtFR zzRb2<87JY02$O4f)IKk@6}T$MCe?St@kN&oU~&yqe+*Ri4=ai$g}1Ikf!v5NgMY9k z1~FJo+rZ1&h#SD0t~}JVhym+vQOl@e)HzU%g~yWc{{WmBRSeG!%B8G8jNE?&9We>m z2^EWyL;;Y=5e|_9RWZD_<%ktbigOmhZ(#JK3!1_F(x}Yk_e*UkM~a4GF?;m$-iTfT%KZ^UK-xq68($s|=Icf~f_G?=^q%K!Z^sn7Ar} z18f}j7{M|k(Fg3?3zaGu@eR7K3=4FFxG*_z@ZA=rg>bw70K)=99ak(|M#yByM&B2; z1yTvOK~(V+05u12-GQqbL5)w1iy3h(Qt8{V`yDNU^Bz1DLGLBrq3{4ok&4S}(ga$k$Dw?4r zGMe!NYIVP%Kc|1TA;WbkToa63cBM_2;gYD9WaA6$AG_Zg7bVuYOvvqxx_nOM+Qu+E z53v%c332u%Ff9vScSkonwl?WIJY>YnVhhlh9O50iV9BRyGuy{m5#5N24v<)K`o9wg zt@S&b(H4}(6PB1sk+tWReA0>X8D$#$}y)!4K2(I1A?!)0r%Q)Ws z7?n;xD*phiIRu1J31ca-R6Da>j#;+T^{)~YzUX3kDERDKuzd{bl_@zU$62RD3g2aU z!*XPzmX(vXuzfweGl!*QSQ|yKLHpyx@?Py@)nLWx;;NS?UhK3%MByhRBK?gjtU$~; z=i!YCz!87bj&#Eisq9BiW=E0?P3T=iwmY^+U@Q%)S23mykjE6)q-s**H7M|?m>Aq8 z6b!YPrD5MkJPoq5a(YIKD=znh%ZP{bjLe(z1~9F>O5Qrh$55GY87&? z2@t}v&WPn_3(1tlNL5DQZ!#$7BHzfvg*v)RyKgAH(;IaMQhk@CKKO|>fz+0+(l4}H z8T@}SC%E-6LvUx`?Lf+rgpjE+U+UNZO(%(4Ch-jLnEwENQ5BCf%xty*EO_QOj+mf|p$a&Ayi|XI@6n*&Y*GY=KWX`q_@3 zu^jG8{8Z@h@aw6I(__46NW^pnHc-MUnG=7=#w)o~Ivn)?uS^GG?l-K|8>Z6Uc*{n` zNPcih79qtRk=sOedZkdALf$#3G&gsrDD>Ztzz$c}k;FSi#+8RF4iwQl#7lL(C7+ag zj4SEl={S$>#hcK^n$>G;bn|t#oDrI3(Gwf4F>qBrTEm%`rA@KZyIgw@W2tJy#Y%sr ziJrxTPMf469hg*{EQ!YnRF)?lZca*3x{brKZ%M~Cw=*$UnUkYZ#hF%QrR8Nw23XJEDJF0W{%pYoDY7Y9Wj%wvG-qp!pFQHIhmvvmqQ!l>#Zq$ECY4en~EQN6y68u^RTrFEtyx-VxX z(lavD(y61rCMAXG4j!T5i`K>NNGGMpUlq59{7^LoV?&Xrq{Oir6|my2&^3R0nvRB? zD-_CFWhz^G*>G)ZVECI3nC}*1RbOiED^0#M{{RePDZQA#eI7M}z8d-khsHzuS46oL2Bkp;!!mG27dZo5JM#xIN04!x-t~W$^+2{}C!nJz z*rLeXp)MJh!*pM%zaXOs!g~`_>EV!z1Myq@;2G8jE+>*PoJ`G7+y+UZ_Jm@Y)=HGo z6N(8WWuAG`;U1_{K7oWaE+E>}o@9buIz~q1o0E>ED!H|Whf)6k6fLADtCOsp)C-Igm5;9_?P;;`~!bI3w z3HjTcc`WI?rtUeVWhkWOVU}GUqT>7FDX6s9wg&5fE(3On4cvb`LzFrg&gfO}ZV6^j z8O%sb4ah^o!`_}zvjzn6h zsU*RPWJQ_+x3BF-L-LF2U|hhZWcfu)*sAQ$Y>0k+>(N4*cbBg#CJ z9WFBTn{NwYd1`;fG^vGYZ?dp`@SLh`L?rR1z-I7k(ydHL%o02oHhZ;jolm(TxH$=4 zqaH+ibecEPt6}v@<{o6}*lxDr%kl3JD8BhJiLt^zA7uJw`*RP#@hOl3(Jd($}@hWW{-bQMRIe};&H<(p3*DVQlbc2 zhowaO&61?X?LDiYePNVY`4JTlNy*-%oILzC@jy0Gwy;J`dQ4R>$!5Q12IPFJBy{(r zg;v2roGZQ`91hurMM&w7NeS_|J5fQc!IxMqyA|6^<|xFB%A~=P%@{NyJ|!i|2k%&n ztu0U=HqU>revkV*9hYG#8gQ}#=*x+b2=O|qnW-#l@LHaInVpT1MZ^xa$!>^+3#fc0 z0b2u8PkK&$96K@e$fxhI6&9OMst$J5r5oK&G``X*bNk~4P7li$OA&p%PnRB>Uqjx| zy%6SRD)@GD`)P#R%*FY9hI&~&lWjWFF7emh0=$2mxH>Yg`I-|uf8jazW!rkH;99G0 zF*y4s$spKSwqm}?BBp4*=;`C+uaccCIktCCqR-ME&1-WbTPB$z$VKsUzB-tBDJnD3 zS?X3hExP6y+x8V^M-T{4GD4k}q|n_nCcVp_dPU^IuBIHA&0~wO?j-bQuQS?QI+h-o z$fbX3EpawU?4Odxd_0!M)nhw9#f^tj^tf*f?4G47DBaacvq-?dcN0yjYM*>`c{!C9 z&Hjb@H|bMe`ZjF#V-wcKa%H=M>&T1C+Kv|)ZpP|*c#ejuPPEyJZ4qu+8$-itM^V_! z-?W07dynFb)7(Cw6LVY|cw*`es%TG1%szip^W!ula5xtI@R^E>Xx~xB`)a}H@I7mH zCcYJbpJPf#$mN`6OIj^dvu}ine$+b*{7mfI8DLy@H?rNK((95WY`ssI8Gs-wZbV&E z??`esx`M--Onu1oYuhZg-!@A}#4^tTcIE|L9}u2eiv95Hv9Z~eekI+;Za75*MC^Z4 zex&mbm>it}MCd5Eq?&a_ZNn!pjwo{v&xs*io*^!nX7;sW8!DBLSe&&M(N{b%#LV<= zq49-elc-_|d3j+X_HH5`sL$({I5uWa7PHxg!r*g?8yS=dz@dVvEB*Ofyo&V{7Qm;R z7Riz@xxgQ0EuCOoVbIWAmz@6q)A4^?P(HHA$>Z)DVz1OpFVaeFTMM^~ur*qo=-d+$a|h!(l{L{pSy_hzi>scI^{>Ha_4vxnc&xEZwIUhkiCd={i)0yf2UPxP^9He>2E zm`jwC;z-b<8FV)`$uze}AWv@Y)neI8u(nyW@>06kpkuJT&lYyX4O>P=<#Qw(zr_p+ z+Y(IN8LVBZEFWhw43meHMrw?!h$d|UFA+s62M7t_xbj{i@f3d>17D~jyLYZu#8I&z z9`PrO*@h@u*xq1-j@x*;pt}h4+z|?}xyLI-(6dyR`Hl^pSgzRdy`LcpV|1 zR}2~y@F|vU_TuG5>O}|^dH3~21s#J47gtmbLtxFf`$H7UE+i<435hHN2MjoE2zm6t zjhuq+&e#c3MniujLR$M?%u!oo28n)mK-6SU*zo1w7pMiJVD#jJuZjn-31|f8tT9FD z7`TvfNH>Qd*4Pk0lS%kS3S?qPBF_6jbb{_Jr~$_<442ycP&FVDB;AsvN|mS*JUuab zg3Ru_)BPk2cObUKI#i%#ha!ZPE;3&XSV)=;yXt{qf=GXD)jg;nSVuE16o>_CF5g#~ zS;H5(DHz4p=sYmFotD#}PkFCQT8p)TB8aA5C}n^=Bp`m&OAr}7+=CLOY5}9lS|Iqp zbP+WH1o+|>79msE&SFFF_eE+69faFE=_@GE2 ztQU0R{uqCP;xr|0Q_Aj`gA9vehnkP=Ljc^09|(_8Lc=mcZ{hxsv3J<85g^pMa)=Z` zf)p1rPZ#k)C_>w8heZ9DA`20??Dk-y0kFGm0rpBHR3juMo=NR+{GwQ`z(5`kd`apo zShIIm4;O|hlqPMNChc81qJY!@akV29Uuj{H6)b-=o2qCkvEk2246;nii70$g3sJ*w zaAry65Eo(4l9BzG7A$r-o}LlPDT}b@s4`9%Fd4Krnr_zb?uZ0A7gEgb&&8A_*g29O zaoZ0*4)XS)E-Ir|$lWnifKb&(tY!(*aeOf++%HUWQ?`c)>l7`QLY#brbBI-jul0&2 z5ZiymP!7P%Y+&$;bNeG1<$xQIiF4#I3|8Pt5@LFE)wuszMUK&VsP7iGJT*@KDDwF@rQp%+o(rbD@St%KBxh7G(CJilfxp*o&h zTnze#2&Gdh-tSZ%pA<0{V{8q=w~MOSbR~aDVQrUsekjn2NnnCruV1qdRZ*%5_QZq! z7&5I=Hh59MBDafJVid|iRGho;@k3Q=3Xro+^s4h83=H-JVKCoH@&5qJ2pC7aS90g= zdr<{50XciFRp;S|SU08jiaa635EoIf$)jhT#q7i_7?77_nc+^Zn1e?nFWkS1AqRgf z%kKmJC>0<^<;c8QK?@c@x0Xc!qy*sI)doSN!j~__5E_y0aRy5O#Y}xjnU;5kTt8|8 z#SI8^41{fhA?D@yF8!#0u%=m1EqIIKfo0gW7Bcq-2*!3@8jP~pdoVz<>~}6a8pIP_ z59Ny6SE#LvVqBDyb0o-z#ndnomLq?X43_gxW+_&n>4yL)A+Or|;A5~N$B6yWVKZzy zVm$mYQP?2C>eDE&C)i7cybUk=P%KGcYTA+fKhi1KT=y~|YU#vZ3d}s8NA9N>Q?iCuvR0c8{^F&m4O+}4)Do) z?))&=?hjwA9-beHE~I48rsg}Bg<*38P&m(8;HA-8kZfe9u5eX( zdoe_ciFRn6GG0IWLdS?VjmLj8OQH}^hkvyU7AcV4YP=%>*p+c&JG04u3{b-kOeSQO z^+R0CYOX@{d-Vy!L3ua(fq!Nr# zE<848?SCi`jH8HN8=??!+nv_=KrsgbM4_7XX%e;}mmW|&l)8LY#ZiAsj>BRrkH>5< zlh_l4UG^6^6)Zv$68`|JVwixO6%)4GqSP6IzU)U;=_n%EL_F2W!_0;uQzK_RUoMz{ zTEk}w-@_0!Jlld`D22lW!37e#Rpc-v7D9!syioy}3fS($r(f|z0$fm!9pC6;4GOH2 znnWOGf6JyJ2h5O=YxaMjEDS-CZ%mRd!lrg2?mJd0wj{=kI70D?e+flvK-cOhkp%Kz zY9JMJ-659-T~SP<1?y&C3{w&pUSP~_0$%%KDSg3{WI>!u0O}-1co{4aN@R=6g+3n? zT}Z-Pw}oQtG9zYsbn!)4WOo(3_d$TCtt<0bgDfj~Xbybh0?L0SbvsjrAW_tNiBi?& z5EBHbCN`J+u?oJ-6NpQ%?L-=QNjclY)9l0`OIP6G>WD$hE_Cq324pqYXudBGvk=d= zGSk`f;rL?WHb{nnLhZV&P~~IW?2kKjD1#T+Se1vLv+YDJr)Em-6oX=gTs%H~Fa>H1 zkqIVo3aEl_jCg;)$3!itj5MY2V?Mfj4IQspbV-SFx zQXOu)lt3rA-OeX7_F$|S7arbRzHlW+U_{`rFXDz0djfFn{{V&|V|RtmZeOR<@iOG&b$YnESo$NfAEfgJdO*}<*{liCYHkV%wD0Q4 zeajs0%3}3;v45en2PI$oUY7p=p}oRVO0_4vmi3J|JSGKj$C;dLk7Kdu{Dzcyrs?&D zPVXghqLWv1jxo)bW>g9a*2?){}ioMNujZPv$a?IpyPi^4?@~5 z3i{fwf{W_e8Y!u}AvE_3tSRV0o~Wpu(b2;>@h?%(OiJtguAljue+js_X0anu+>70m z(Cj`T$~wN5%(v=0CYk98Bew5G`jntw+bMtIvgIyZL@C9TWaOvR)R~r7>TR#1zJTT# z*%Z|1xJ|rIrw_X*UlX9`;G6Xwk+S?qbcr#Tm>^Igyp)TlDC=O)ak8Hym+5N64LYiaUhFDd_uVP20SBaT&uy}u= zsJ|m(BKRBk(X$MtZyR>Q5GJHUhljp7{{WTE=`Bs1EHIiLJyK*ktt5{hEV6nP zT9$hI$YE-77%XADkggB#!;G%O8G+t&gO|`mVL7~<)1+Y=*;c6jU6pt=>DP)FXJ^Lz35r`;C(941XJ<4LUjOu?I$;R1F z4_D4H=W!XFn!_?FBTsKJ^@YS&Md3{iBLP~w2^iqr>SqcA+Jg)r3==1u_R z)fF?aW;9}5z2m|@66%0p%~!PwIY{Dn9Ranef>QQGVHomnr1KwtONjU z2Ka6EW0fCcu92mv<+8g-F^r)s@B88qUAgk*5P>taHR*^$T5Y=-c1k4RHd;%9{v}w~ zgBIA2?c#90-752MNN(6I3WS@wMol3YrPhQcY=wk7)FQ9V?1K&Em$_R&N=jc z+LIF44y{_4?X9~fiU5>CnA>)uvU#d==aOc;QKyKQy)LE~nR<(Fqwdbf0^zw?hj*?` z*oGh+A~!-mP|!XhRZV~OvpjuOqP$La;;^_ygW}WghbC>AZ?>tkaQ@l?k`1KPzzV&> zL-W!HGhd|8sYW<;nRfpG+D0q}ZE=sS<_In6$xlD{qs(MlIoad&s61IB(^S`py@h~f z8#AM3{T|qKyeZ{6NJ@j@zSih~R%+bTboJyvfyJr1aVe z@Q7#OC?*9DOU`<`SE?k*On@FPJt8x7l|Z!C?t0ScbB5n(7n}^H)Hte~m9#ITj`&R3DGO&QjPsCq!*tfluoP4jkjzrz(?DlJ62Lms|T#=H#$GnHx1wVarGc z>$8dKuo{9UpY5N)1_`s z(RbN^{YZbOC2$_mr1AJTvgBpC;Uc*RVACeR%Zh`|qgKDO_)2PNg1Fhs@`}p(@c|cS zGUE?PInL9$0(T)V30gmNbC)AIvS*~GS2pQXn9xYZ?C#=$@A5OxC1AOFHe+eLLyKH( z*Sdl?;$43WXW75%*d}8UQl6Jm4BQ3$;ki+zdIhh!$aSrp)T&t8YL>%izHG>N`b#M1 zW8+=AN4vBVpZzRVb7MJ~rklHqgszQAz{+E8PRh>D>26`YrOUMF{;mDddpzSxB>`@u zzI_{gA5U-FRFkJMCv=lGxiCUJC7&RUzYh=mQy+gPBUYzaeJxvLk@Q3KBXb8*o@u(3 zSO!ke>Loq}<=lyMGnt!W@uruk_=!6~c42QaGA|KZ=Nwjzn^W7wimFh4tEo$$wU-oA zE`}o;>Q0>1Qx8g#bncShz6NHaXR)a^iGy9k_?}*?FD_0bzIT8Mkz}zr{G`W}R!lb| z<@SHP$(@q)T9m`zX3@Lyj>qe9LC$O^(LbOs(rR;MyK-`uR&Ci9YKD)n%cX6=x*lK7 zJDeQ&`FvKz1wJ-IeEl{)i(l+7Z3DF`B)eZwW1wok5to%|;Zd{&3q|nqg+68Yi_yB1v6DL@fjbr7Z^bq%vA}-buA&Xf!K|DpjYsbWz)qJ>I1pAJr(5Y zGZ?~7Vw^vu%D&Xl5pf8p7@BG2n8Dy(wS~;o`cH~JkZfU8*}TOts4^0DcO~~9ShDhs zzY-={=N&wpbe^M~mUL;?D>!kPd>4*-f^uE7+)S^s;Bs950Mf@NHw}}k2TywE@MME!V-HR=^ttiq>0du%B}`#JTVlatFO_2Ha?i9vdA|03$HV#^`Xk)`9o(A24<#U?l2I-XixE))z@dD||qqEj3S)sT++r9-p@ zY_8?vijLt%Pq4Pi3s5#?LNcN}xkW@53@AGE@WU!4>SrZgZGmL~%VXZYD6K&i^bkk6 zAnHqp7Wl`*0;*ktXgVwnLR^{_&lTB^4bT#*Ia|8N%&C8J9ukH}VJp%An;Z2gV-AM`<9Qgb zhrWE!JeO@Kgb)~}L4`gmgTMkqSmvR?ZhuMJ% z@#t3Uh&6&`*r@DayYa? zei&I!0mleeSSpJWR=$Ot2?qA4@Y@ckunrMAx!Vh2-u2>0YOiM zMgf$O3KGE2E4(p7I|^aHJ`vI?SY5*lf`Rr*7h%c??j1O;))=5P=tz+^8Qc7#%8Y-8 zM6wNfq5$6BB6o#$Q3__+hYIt}e+)oDT%*n*0g>-H4_ESt97cpV;SLPQno)H=#g+#u3X?SSKjUmiKi)kQKaH zui=Ob9&K_Bd6Y3!t;vyJGP}h9%36Q2gdSS*iU=T&dRjb56;Ns}{yeSHDS}^N1SNKB z;(=B!!CM^oe~Jpl3mb~xwFE(8$cmEtQEI@{NeLN_ciKM`T9+MyG%4T10$6U5C@Jb7 zo47Jge$*1F1d#cD)E$T}Lcz6Di^4JU_;)=bB}qUcZJkH{&``lta^F0>J{y0aS&Nw1 zYwa;#5kp0~=y59H5#)ssi5eWX;TJz<6s#@e%iR$f%0(io7Wc(ZQEov`5|QRSJW#2P z3Rc(#T^q@6;kpzYPKRy0HfhBa%oAk_M2Ycue#`{R0qUM7ZCg{3LheWE0#6ePLw65{ z;elWpG&Fm~_MnKPcQYL!Ga!F3GIK2wapfa$g-r&!}1f`%-;tbQO4TdtFWS6|OyY^tKV!I9n%eZ=Bh=C2} z3eeBP4MGZ+Bqhl?!&JJ4aD=r^UuGPPUtzq{w(7rUh8hDf%6aQ~bi;oMs35ADW?A(` z?ntUbJSk>g)Lg(!M@A%8eYLsyysUxpwra4!lDgW-rs=@4!D%{pQPB&FARadCgc z5G5Q*w|6*!MA&u$CBnb85QB+%dwwCdAP~Wt9JO8*{jFeXNl{y&BoyGc?8QQZs0TGK z;X#83axj_{76?>sfntAdNY|MjOZ-qI+>9ory3_s`rBI`=&B=XoPM^Lknu9GeO@|Qq zbj3VuDxOwuGS9>9dof*#76E8=adiv@Y(oPsJeR9a6jed6;g;<@ipV17EI*NH0L4}X zjFca;$#d~QvlvA0+)xt|aA}-~F?kUs}OYaw68@j$2t5kd- z&Sq4_bLk%s6cm3rV^?KVT-T_fQqb?ePGh_@d{yA(^*{QUQyO;_gP*rr4_3D~NEe2#SA&SQ5-}7oDn%`>&5;Uu!K0_5$AQw5Q3bvjC1y45Cp@BI)4?h0bonaa)?qf zfpexHW2VSl)nFD11h`wvR_mhFV1aVpA>o1{jzC%QEzt{LL_H$CQ3?d{ z5g4lU^u&K4mjxTQhTjxIw*X$@hz$1{xGE{|mV8kKsMo6RY9Q7U-M|`sufq_G8IjbN z+5S-pG89m_hr}@jy98uzKl!2psj$tJ>1vLhS40@9EJT|O)*!&`l4Mql1&9&1)w9#5 z;))>R;m<#6Ah8cNx9QBH6surks^`@N#mrPtrbmB~aG`rpATx>^MROh^D5herByJSf z?8R7TaU56Y1S7Dv+#4>o=o*CGK+CU(mrM|E3i>MRa z!;Wt4TYu%s47EJlo#haxV7NkIp+Lf00Vgjh5 zoZx=}%l`n2ID=0%-PZIJKx{Z0Nl6#q5EfvKmj+$K)eu~`Uak(3h(s{sh^3c{#Sk}v zAVxN&-|<8MA~-0TL(9A`RRn&+Nn?)SS5eq=I4zLCaL+ zB?44-1WsIf@ctMs#AFA%>iA%ph02J_cdtwYcO$C@W303MF(ICN3C5q^bI+x$(F1 znCxFiUrx7uC#uCws7TAw>oRP1fffqke%^U3OguHm;yM2SAld%_m6dwGz}LS>HZUA% zv}`^CLQP7`soEuVsfsuAx+?OC>*@3rJLhe|_ z`mP+mOIYcy9I&=!lj0>mZB>6wr%&TanTg4V8LA07EfFaTS)chs@?=)=IoM&XW?>Zj zT##n(5F~Wd-9)#yNxQy|oHf~fA!ibLT%N<#Ni-RQwMn)dMc~i5GeU*tji`rhGOHg= za83cI$Vu+0!qYMy$neK8{{WTLbunSu)qF~yr54zl*sjhh6GZi@+eCj22Z*Dqz-#px z`H$!=kC9lr5I>5sB=tgL);~PyI$sWJ2buQACy9@JOjB|7Xel(;85ak&y}}|SoG_d9 z8}Z@SQ8%;N3espSnGK}ip%;XsQbv>~l{c@V4Pa)c>kK8AiJ^Cux<{P++O(Z-#+_BN zx+&DB9x>*~ta--iI|zTRB)w9M=V~m*EfkXEdz4e+TVqP}h;(TqD;Rz944Su#+9(-t zku~HoVwWvW3+70AdM<&XOw2H@Vy2|!iCeXnn;xiXF+%-KDUY_u^x3$>P8GV1!d+2^ zHz3hdRn_WS&Eu|>$<#zqmfI|2vOFH{+wriO34KRRG~O5c_Y;5mLy-jXGVi49%p*k# zT!SpKJFpQ!x7m)0MwCU?Iux`@w{IwVfIDOfPd=!YAnC4%l?g@b?}))P{m*M>bO{e{ zEiA5!p5;Vn^PKdBS4EM3+1}My?JgC=?80Xb%>Pj<9n{eqEwi`K9E!iJXIpX)Eara zw?rL8C`f-v%jQv9P-hqJCAObQsrozMIf^8PG70G{LAt3=$;eJ$C?-Zqps1bxdB<0W zEsq6ESuGw)R`AcJB%5KpoR~XDhUvwKIgqwo z&%}RGK)3A>+^ILjrNb8;!;ZrWwPI3T3w<=u+X`pU^y8KJ&811mpQ{_ak}RQfmFL7H z!oMOKp|+wcnrgvOE~{d?hQfZ)vzW4esG?%wIECShZ(^^awX|)eGqcKQ%~eWauy~f= z2$I6(&=l)5raJVQB!}L>NC;Vs&OZ#BW=7&(KYsMg*PRkg7onl zD`C4%cWGEuryL(0yggnh+LN_1y?zJ1Aen#tojB~F8)n(3Pn07uFnJtvUfLB`eBc{| zHkG~cmZKjoo20B&F$Go6SUsZ87dv5!n*-N68_+&*>jya7#c2-}v9AgVY1z`%JP&^( zh_LkZtB*T-%oT&DURpus#nDnE>%`@(Bk02?v*D87fNpGoBKtm<7B96-#6}pWT zJF}Vk&fKKrsZ#FEOjM?u-2e^TM5E8k&NIdq$&|R{EH8=o577f*^#1@uzendWHE5;V zs65KMaqy8IT9?TqvoYyoq0dIUS&x}som*Oa(-&y9C;MfgD)|J4@VH44M_qq>~WS<-}f4sLE9qKn^4+b1lmiaNt1RA(U zZw=q)5~Wxi!72|B!PKx%WUc5iMOXofb6B=470KVz1GpA9n0nq49vE%vL4?76%r#$R z8o4pMJx558g52ExjXPzP*?i!-q8ri_9xn_YHdJELv*dK{8QbLt8Z(iQvhiiaUMT3M zWpg%OIW0YhC946vXn}vt=NAtUnR$}94Zac;_K{&FW6h@N zH}9hF(nX2(HuSw07`Xl*pte?w!T!C3_xa@%@v~JIA@mz&`#_}an+La9!&ON-gsZ@X zXgHU&zVwXGh{c-&AqD9j=#44qHGm$WV=65-5m9CXfSPd%W=(&4hWg{%R3#~@wDsC=m)KN-Rhiu5GnCA?LBM$~wcdCp#s8lf< zQjy;>=6qLpVmh-CU#RBIzTkexQ+SYlQ=Thh9Dd+EUZaq9?SbqvuzddjC<*p7q_mv9 zjwq!`s=1ZW>@F?+hPyq-bDJ&N6*jj|c-wxTFfWOGmo zAT~2e#4>~=a}wfi+r&bBU6$s-S?L5*smZ`8EZcpn5TXnD2H=W<-^NvsCIM?b?_%djb z!xd?YM23Iu$%~#LQMmZ6`=gG(RyV)6d|O4Cazb8o2Y6$UaBz#M-vg5vwQ^?!)K0la zm`7#W9?nP>mkXS_zEJE-wF$@bl7TNK;)B5FvR83LbQgT#fr~2jEFU{I`RI>T*_|G7$0;tdlCG%>GPj24 zxSb5tiXpi&Pi6|hF@g>9<aLM5LR7r+hZA|Fce+F!L5J;vB5mhd!+GX!gg ziALdqh#=vY6s;xpu7Xu1#~aU_9Y__vjbf@qfX%0W2BoLP5~KA28v(Q8f)imx->cKw zh$Z7@a^3u(Y#OeNhHzp;WMK4TF; zhuMPY$sv^;VWhy17qtKcy+*GM+@j((Pw4_8Lb<_;^&CMr!SKWhC8Pw|8xD2BQ6h_I%8df8~fF)}{rW z-!W45zSI(ctY>lWf*%AH4NGecT&D?XZ~dY) zv;Z`IMFh&>*TV!J)M+^@b6)6jgMX;e)>KpeSg4&0R2w-jrd>Z26^3p=_H_*w+e_Jh zf<^(GW()N>DvohCBG}Bh_w=L~A%(H#uyGnIa1?G1d{Di?RTsPC&|=Hf24R~|G`1&l z!w89T;rL)CDnVP36SvxmEvI6|n^Qj*!wU;adx%pdhyjCd&K78(Cpfu}?L-Ba>NhLB z*~A4{L!PmHp@E16gd3u3lKVc)5EIyc2^M`L!~Q6RgL3uLr{RVz0jL#BpjZZth#~Uj z0vthsY)8a-#ZO~IWITeTd0hZ0r((n{T>YW;V)Y?J>|cXVbSM=waW}&ff!s@iFKKSD zeTP^YFLK3+p^~}Io|uM#Hr=h!Qo>#q1d)=SziKQ}#=zVnd8tDR2IGWWhS-FE5pg*8 zwf_JVLZh%n75$is#Gb+&aWBiN-;^5xt(o3%XW!ERO=w!D23^`gFmc#exFM<(31)Zc zeqGU8fNT()64bq@9Z4cg+ix!n8+(K&=hXvJl|=(LT(NtASqyZbqIkNvVys9*$##0c zjD;Kupz&Mr7tSC$gEtp6>WZy@fNx_jH+Mc54#7l=Y*!&q4~if`fPNdk*M=BPZ1A^e zWSlDyWq;%mw#$T&Zip<&GP&KsltuR|P$;f(pJ%n;bQd@-B3b2+QMrHE8W7oF)&n|v@6+>--W zp5rgI;)*L$0mt{6AzR^phy_nKG@=^9%3%dFarJP;)T6-Z5$2pW!LtmBVI&~a^i~~IBa*Csei*7kh+90LE?fa3#Z@V9w=c1 zH07^kg$_h1n0VyH;hGOrHX}uiAA5F?#UU3tF~nR~TwW*um6V8oo^!jaoI)#Xj+ktX zb?5B##)e`r(fM)MS#waqwf7#e{Jcv;{S zQTU*VDiNP08=?j|ZJq+l?8GW(LE9UEbN56AL$=+zuOaZjv3HSMo~)pP2H^KVF`d(6 zNr2xUN!{?p7wPDKk9uC?*YLzHF$~_9wDM8v>4+5u#Uh1rDBEmbayPKthTD_H6kIux zWNgg`i|saYH z;trirbt(G}8F@K)bo($f*ihhdcb_<0PJv;=YjWl6#eo>KJC)*Gkv=RH1@o^2+Y67-U6@zJWN%sC1p*6c(x# zfE=C2)xt1;YFeM%W^V$(epKUYLZq(Z)%|;v@e6mK>yCajTN5 z8e0so)Ic4d)!S~jbwx1q117!W)dL-Yh#S2pbV=+GBW%BBDqtCaviDh+$|VYf3__RN zZxEC&q|}HeZ9RHnh{T@aNt;=<3#T}^>=bqzJT@MGOa2%p6u=9ERJ)7$BpAj5<9Q}& z{+1z8+)n3?A#)(y8-CnCRgXAC+L9DNlxp`p`0|JlA|hT<0b@N*We}x(jvH_UIDcj% zDI>UT<=S_QK*hFJhQ8wAh(p(2W1q7S7Cm*=r^5okgC?>Jy{L)?*vP1g!3@(dhjMep z{4ooEQKQ3=^+W<#X7P^@_e3d1uN9|ibe9Z3U6{)S>h$)%Y9PCYXCk>&t+pWR1-GRx z)ViVtRSMZ>&i)>#shtH-jM`ra#f7~N1w{`e;!kEEsc^}X9(IHcNet3^gjY^q(kY45 zEeJ_7yI7VLVm#eBG zmui?Ixbx|P63kW(XNZV55+ROZ!m+Pho42fkYwV8*RQJO&dLLR}HZXG9p46L?MwC9d5LL zzrzrMw|l#gc!YXl65*O~Tm6*$;tORK;c@0X;th`CSt1*k#8Cf#ovU`r#;+hPzV{{YqL;o*ou3h{G6 z`%wk~Cy_Mp#2_b}B-i{=2na#0}xL6c)w;L06bQx zq8CgRNH%fnjBVwtT}TDTF4ZE)0%7jtgO0{_QL~OM{?u4KVg<{OH7+lT0(+VwL=%>t zX{F!J4rA;SPQ;78d_VI%3_>1%BG2Kv zDx^KUQ88PhRE7kIwd%Sof*o?v%l2XrZu5jzOCpVU& ze$)W!hi6dmT160*3?=99h7zIMa@$mk@QhT(#N2r=F}zIEhl&OiFHn0b=4L3r^1+t5 zi{``jg)y@oq*7}PU+P^zFdh+qCd-OKkQ+s9?Xk0y+sQe=Ltu-f6TD+q z49#j9xgO&>)W6mkdTd7W}zxWB*AeJ zFTOSTx75dv5~ouQ_HDL`W;T`@q{V@Lk`cL+d z^z`)wj7f&;4I>pPGsusBF5?XQ)BK}UV#Sdwkjk#u+TLH_bp9{;S2lNsXZuYOmsFUh zxOWV=GwT}oa*W)c!>sjshhGonaU%Gfq4a?LAX1)@(=z2rq|~J8td+#Wk`FmAFDBHvblq~?Fb|nvcOEoY9 zb;L!*#TAAf!OfM8{@{#^fQo;1F}>SLB_m~<(8Cj(ds?YVkcQ%6yMx*#O)mZ@^M93H zsng&iLmj=cyj0c?tlszP9^pG<%*<%+t82*E)oQjKn3wGVQfNb}(i=(zS(J5E{zXF* zW6C^=mtrEAUu&d)VX+J<8ybA9QmWYD>CF?>kHhB)AmN_G~*_Gc%v{4pyfE+b=NmleE3zS73bY4;P=AGq3ow8nQpE8>lD+{?HmnD2uw zCc~yjfMN$tj(|vi3@luB6sd9L5H($e-S9*R0R^T@3n7skzZ5XIjgRTlw{Z~k!e6+o z(qg46XkMNeTL6Q0-%>oHqq)#eV8Y_JszH&P>Sp0`77~hu%Z!B!FOah0r?_J`A-YW5 zfb#DU_@j(}#v3r`O64WDst9s}ITl>CNwEExsbc0A>N=H{34VbQ`#dmPkQGPGTCMBC zu|oridc2|#z&Yg*f<~azXK9e<@j*gZgMxCe-a%4+}R4uz+6KD*R3Ju$_xDO_^?fd_l>Mp}WHwvgGwVO*rt=$lvlZpyaTlb_fXgVj{E_ z?mMHlEzLMf(`rLhOF1M{47j=#ut4i$Nr{<%c{a(%2t+-i;fj_SiyIZ1=GY`@C`ikD z!X6$X;f(&VqHvTX zRAc6N?B5b%SBtXK=AOI9ZwYR)kBYr2bNybUMS2!gj)#oiVQwAsU0)^2IcG~8Sx%#Fv{G}8k{1w?b$FtGGYz3r z!!iRy+Y6H@BiLt}Q8Oap70gvW?x=4IBC87IBR@%Ys#U4>JleQ#32=~r+$9}J&5Axo z4^579{{UmN*f$@&UQN)+l)*X!cNRy{{ZWghS@ik?87UdF>-Z()LUUm@f%HJ$_<{gv`%jNuO+uHyC~MY&58XY!QX#G z{{W%87{cA5*Xc6!`fR_ilM@?ATlSYe@{dKq!|CGnTgda-^Ln{@d`geg8R_UXsq(;> zUSio_)jzs&-NR|&OuN=O-;(G0Yg3`d<6QaD<|LRS%^V>g@kf-GX=|N-$ciYJD^m{! zj~ORVwGb`H#$m@^0=S~*xb3JSB5#J{x8jNr3`Wz*c!$LSQs=leWLwcktBUBlgDhi& zbhqtAqcB^OzP^ReO-HsXv^jbd{KKOynuviF!b~l>roK`1kHd18o5lSuUoZC@K6Vy{ zvYl`A>iP>y?fYa>n`Xg(5^c^_=u+sj;Lc)I870+APG&sQ^6yykzmnlJH5rdHsM9uM6folG~14KeB(;4%6wWS@iqr) zVV(0_LR~iapdAH&!}NmL4%@L$qdq74#(^;C%Mo2QCS<#()4xtx`{8J`9~ril%6%>L zVDy@$dPU$iYE&uU`odgOw#gA%>j(1kfy;|a&|~CbuZcIe3Y3L&Wq5*H63K;1p7fi7 zZ9du%=BbzbQOwN?k!s3jmOcymN^D>Kj^k&arCd~pomUut=W`m8nool4anF)@)Jg&++SSLzuT)ijJx(!H~ZMPG|JtgH4{e`U{xYEQb zQhUbyJzqF#dyC2K;vA$@LpADxsEY`kaI6tgiVY-eEbjyC#4U<;*Dw$En-0BT+~awe zz&h*1M4`riJ7B?MHQF0C&gRKPxMMW+bsBN8$XjZ{3okPAqh>ajNWxMz)?GGfx`df- zNLPf%h<;JE8shsM<@6`|RxT06Q`i=w^~_HSyC{ZMh@T}C`}K|ZvTqelkgJgTVpE67 z3f?QDAFZJ>q9&)ibAmg^SG4iQ)U<0A6~6E*|#q&^}%w9T3j)WTzn}dg=F4xWRb@% zG0xk3qNBNQ?nt)JVU}eP!Fj^ZtE=yUa8ZQNCkSPnHDt2!7lc>m1Bf~9PSGa;x0_OK zU2bWz=0TTi7myE;rfDJ86_ji7D2})F4s0c(jd} zb8E!OpNy`xIA79J(2kpeD0&=TY_}a<*r0SG5l%su!~n^Zpnbg4s+rDnbR2^?x`bTE~`5`nrG4BKrc# zqs_{g=p(}gU>SwCCUW7$)LFp;V1p{8d2aejDtiJIw#l&1!xs}^K(r|{iq@=Gz4TwmLjNd;RI8MI4U@7`y4lyH0p`i7RRw&N$kLhp2w{s=f&*BR48L2 z2q&k83L{~KY*Q@Y$Q8aXvurG@RB${zi>4rcpr}|Pn`JM}Mj->;b*O{FMGBNO-(f+N zza;&bOTiKa5Z}rIp^Fi>UZ|#07+ZLMc%VHkg%41cjgT&ZD+75u(icI32R`_OGD0Mx zsRI@uhcaG6tXkd(AY8Y_@WIMh)rCwr@^`>82UEo{)eERI*hsx}PQgP1bJl3FOevHB z4Z5sdgH;maW0@)p01TieIy8e)DvezZsaB{LB}hPWhAQYNorvszrR?;<$8evpByW!-9-rq9Q9%OhHRlnFl?u@30LoC| zmc%)K?SrT{49j}y0LlK+MJ+RfQSV2{{UG*DvVVy zLY$CcO>pLQH`J<7RRA5f+^gb%3c!uab+`R26+r~?CrWjHKu=(542cJT z$n%C+AstQScAh9)_6B6Z9pR<1WlwNoHwH@r#E#D3F_wo~JIJ$k&23cTDw&{0HaaIJCdK-3sGscwEKh3X8Wo0mQj;rO7c0HH4; zx&*z*t>#(@boE3G^OF~beb6=&V2rk-sd##Qm>`u!p$O={BK`2Og16Um;xU3y8O*jF zS$ke6j0y|f_xv`%jT3Flk`yq4nOy!Kh6Z4RkQC;S7B0eq)#NaL1=P)`P3lnu&`_eL ztJMq`5mR~LxBe)r7NWU;<~&xxDw3hv6d$wf^+nW-B%IzRgTg)-sP0KqEt0jW^Xh{W z3S&z(h^ieyi&p5c#4dXgG{=c~^$dL6^*xDsS&NS^6fm8Kj6GrMS0KX0+>JB5DoWa=ImSy6&0sD)$C~}0h9JaX zE)iMq^uW}+4Cb7+E;|EoV&`%~3mY40&LJXn zGMHy~a-ng5YY@3%jL>?#mxUq~aiKHEZ*&!izT!q)Z_3ybIO0}Y+8%ifLhL2R?a>JS z)IfIt+hhp*U-yVu=3H(M6hixs8*eRE_=77@fJ^@Xwkl&_Qbmtmd^yE-B_ssFTE5g( zK^e?0@8=al4Q2!nq`JMRIE@Te8_l=5T)+5Y!Mu%sExC2)Yy$*0d6F(&F+>nIAPOfJ z*@37kVa)lU+y4L*R;0O&p55f6SANV*$s43|F4_*4MRx|n)_70j{;;qtITf_@i4$Pw z+*p$5n*RVufH0xFPxzn|mI{s#=)dV=h`|Rv-o0>E0=5p=mk-&28Z2!qRxO1zY$#pJ zx+qM4plMFDg*0rT^t+qF&)SJ7OINqQIDUtjz+YQ`+ zCT_J0knq5NNp=GCy}Bn95I`K2xAvk0$A(uR>FMI);fM&E(}iu~g|dq=)odE6dysBu z9bOob*cf&ix_LNAonF)(gHR;px~vf@1RO%skGP^0J;XZo*IYgNMa*bjL2ralUQt1R zLIQKFmhbqlh!Z}B`@|o!44|gS42O#Dr0Ip1TL}VQWe_ba4jHtqMfXGvLlF`$+3|d$ z1TVBI@c4bGg-2t^=c*qRLc)uiw9+bLWHAF#QlbgLZ0&V3WN&9!>agyoK#N6v*-}pplnS^g6Ee{3^*2zjz#3NE*>a>DrnB|M(>6pC%B<`2?Y`sDuPMN zxRNPU7F`J6T?Hy!PQ~vOxJShl0GqdAr&sMn9ImI4%hz8FKv{*`m&FhY*vZ>}&1`ZY zbWhW}m+B;m3)_k<7RCdOGKflxw%#S;LF$3)DilhE%aIf@L!p+!()&5q7n1Yrx z1rt4h!*6cw!Jjyqy04YvG76 zZsXT-{{Tr4hpzAO4-7yzART*nWuM`Q60i;DQdPoN%nt zDMA3_tDb@pWCUD2Jy2V*01^2?b`=k^7X7TCdxv2{w%wPt71$Hj+Ti8W6hU6i-W=lM zEFPf9m7(oL7B6}~=^_Hc9B_x&%why!US3P0L?MSB&vQvmDr0^`b>`!QuIiTU+K zFvAaU+P1@0fMnQobzB5mYH5m|N6HnyBX- z?oTDKWwE;rWqOC(9mmY#+969n>gVM69RlGmE>Y_^oVCx2))O;-Qc!61yd?9_&?*MY zFA|fouH;P?eDwFmZdc=#{6}Hrv^P&lN1MX9V-R8={;DZ66nC61DTz_Jue7NgT>Lz^ z{7s3;#_jyoul$F5UBEju+4O{(VY-*s)AIm?R*wq{7%?t)@;Z4_hmRGNtWz1;oZc{( z7bc0_!+anbm9gA^!^Ya2oY@z|ui}lLy|cNWtRx7TIdZ!1bZGSbFAx)`*_#>kl%nE^ zF_fsi*=hnd+*~5+`%$wFWymYhSvDNQ(({jR2GU17Lk`pkWY>vS+BDNr^l}6!V0mvCxkbp>_j?Mw3n#ZQ>;Aj;|Yk6&I-IyX}kCB71WzNSrrt z!DR`2dWe09<8;AZko30|%UN($gC7ZJY;rlc_lW6nvT2U}mzf8o#lh)tP>1BP<|=nv z+zZ@JXKt`!o<^<+=aTT0MPYTQHyjkw1w&_Iy^gU!&?xQ_^;^Rf?hHkZ1758aN^$2* zy0a+dyzPyDn7xgEwQZy|f^hLZBqcES0|BkXs|1*t!ED-6{pe4zw`sJ}#1%&Ol`4@T z+0A&3; z*gm5%@^$3JTNZ7KN>*9U%j=HSw%-E7ap4d33-L7h%{f}0dE)AD)Qayljp+v5KI59j z1S*uLC5(6+8)H(G%k?<>(o@lbWU5Y1$jg9#t;$x9Nkuoeh@6eTBb(|}nfy5Ofb_eq zrPh}U+Y-PIDfrT6R$0kqnwl0K%JzlD5hYGpr+hYY<@*Gojg%nEExzZ07{Z(`({?( z5!s}Qiw3gAJ7$@jdBQg>Y;(BH&L7Hu75zcPnwuJWK$APbW#?u*BX3S{w{?Ki+sM{f zgJ{^36CUbPpHX_^{{VJ>&y*#dq+;b#Fg8n%v2bRxXC~1X5js}e{=A}kd0eOEFT_)R zhqZ~|CnxHF-?Cj^Wn-fmVZwri#yMHKm>AgRYiF=)YZ|JL1 zIqjEH_^vT(!uz3{vW>95Fdcf3_Y}QaNSHAq;m@D^IY4DgD-4LSq_%HEMZx7P zjoP&LRe~E+rcY zA|tnmbzI|=`Ff4`aOp^zhS#QN3{@N}qZ9I2(LmJSNGXaOu`}7)nCHZQV{t_0%@Kd1P6q2h}_02(pW0hji}XYmhYyWAogywNq$k9b2OHVQVGh#ukjfj z*IW6*>^Spe>zwT2>iCimaxCgkqesv_rRh<$>3XdEss8|FD6ZV;7}_%~BIr8!6TUqs z;rKiLZdEv({$o$_l=>Tg^o4p{QGE{f8aPi5yvJ>Ejlhc?M0}BcNga-7FVpHVV8ii4 z;{GedQy7}DF;Jawo}8XFb<-{)C0OxXoUM_MPlpGlq)_;*9U(IWW8I^0VySNr!x2jm z>6D}AqO2)OP8>sE<4%ZdhIjKG|#G)?A(;eiE)u2%z}@WTLq5Zv)j7=ullhFvzo z7&R>zE@KdE{{Rrl*Cl0LZOJo33rmQTyj4C?(fnuRKTVxkO5|a_Bks@9G3ldD+s4Y- z!xfVUyJHI2Hu-2bCPd1j`KNS!1M)a=b8vpE7x}^FU3JoY<@#NIjLP4l*Q8X`-WidY zoWS_O(wwJmhT*4wyi1#s>*R{MrhnxhKj&fe`i*=ZPp8uUCoR+c6!EoK#Rit^XkOP1 zv4-tEM@6$KZNvWnM~*S9T2B)a_k34>lr5dUqNkUy66uEii}~4y#b;`V)5IV>{{T34 zHEAo?VhWT5&sVVWuA)h0`->xr~0V%bWyOEj*?=%CJHPn8}Y3u*mgG8Rl=zel1>WVq1LN8JKRDZx^90?c(-Qg`nNF_QFQo_93^O56A6Quk@Z3f6h9+(K4wuAl zxy_MOvBfpHnq@rR7YFQ=HLyJRm>-a_pZ`34UGhEV~U4mHwKaO12i3#j^}-`j*vkk}f&Pp*GsjzSPlI=L(K6 zAE;Dro}#xYh~+l*vw2)YDA=}h4KDqFE4L1RcZ?$@8XBeUwlqdK!{te;lBWuP4pDf0 z#n0^-U_5I*vRr*ju2Wt%-jj4+9E+(C^%!!D<2^FUHro)IAb;%6(P9;Vu;2{Npmd znxSgSSPrOchG@;kJ|g~*-N>qorOsp|07fl2#6j&WS~`)Dhg8>?rqr2z>s(~GEiN6> ztXg>#zWzjoVxZ4&6l`~mbnwJ~BB(ZhCQzHX2HzF_D6$V&7fI&I?c+o~9w;jVQFFB0 z-fh5hc00`%o{{RnvPNn|< zLsSstI1)|#F@~s9IfH_ zV#;R-JY2>qpmr5!ci#XL3Ko3fvD~#5EoTN0P#=cUfS%ygGL6P70}3@$VHq2_MKS`x zM0cM!35}E~8@A+Ln5vgh&9+6SPyA4(QLAVcTs)yv4I5&OyLoj$N(B*rp+Xj>YivnU z_0`m5xPwlZT=yJ{rUvI0u|WtVwKe4fLY0J(bu3DaF`dh-MIDW|(jmdOlwA7;(V#AP zv;P2^DVJegpbja|$hz_@G${VNN)y_MkPgfjIFJ{upaw25ce62(99T z)EI^^NxTgE#tBpwZ3sDkTUO-0-?J9M?oSlIPH;-1nTHL_@Mu0C+J%AKa5BCas}iF3 z7W_p66QFehN>73euntP^174rvi;m<1+`pG1hmg-@A}$e%yo_1|8Fd~|Ejt>rqWd{H zt+1jqf^tlVGKgnkoYggKg1B*$M0~|PNb^o8ZG(?-tCX_LkwzJR3@Mvrdz7+oTWsj9*|ckK{uBdbTXw()L6DTWx*8+Rfhu2peUom z1`uu7hN%NkRkjtjZh;)HvpdBk(6u6h^uU!C=nqo$e-3b|ii4;HyG|A9{{Re7_?3We z5pfTOAhutqH4?LbC4h+7AY5;Ga@+orDwZp744u0~{6!E`4W2>`J)aarg8G0tXg9Av z@WC?d7_cQPk2T`E_@b*(wG<*8D7;H@KtJswiE+D3ph+?$mfl|bqB4>sH-n}FQWo|N z!A!|eV90o~7eChwEQTH}=BW^egt#tU+?o4r4Z4xq#XaIW!0 zs3072lIpPv+!!7sC0-;LjHOt?4!I;TAX6UU0MK~4xT2~Q zC_!{e+=kKr02E4xQ3AT@!^Hqefa3P>GqeT`P^j)Gu5iUy!ZyG+u|k=&kX1NsijKf{ z4B1#YtN`7A)KIvtT0R(HH!wq&KCj}4RoX=fB0$tN2-mGL&MQ(7;jb={{*W~otSCVo z;`U<8>G>JBNiO_RQo@C|m}rFo+$x0;Pz`LnyfE1A1ojne$I}KKPdAl?0>w)iHuLb^ z_@d*m69S9Y8Dt~F6<~}Ii)vR5%Lfb!m0DE6dtD=cA2?&wSe2D?z9oVs8SZ1m^W!^lxP?PXZs@qV=d%~ zs5Bv}j1U`wG=*;=ScQtTo3_z}x`j}V6{nEGLK!$jC+!44S(I$yc^BGaU!8<%(@ z15i_c4sc|H!vkPubTfFbGRg!c2I*xGZ1jU7-Ta|qMw4?Jwap^xT}-n%yn8Qa_@Kib z-{z1?y9rM%%dfNi;#93jW?d5umxkCPs9>H;Gw+Q^9-vbt$EW(rEmW~-G)MgHBM?4z(K;+80b3mKnLFUPT6tuqr8k>zxeLpun)%=58GQm?a2gO^E>_#k`AU z8&0=xFS7tKe{j+9A`Fynh*b6&xHes%SHv+19gMWMYw<)6;2k(uIE5*P8@TdVfX`qp zg6|1#;OgqO1xAh=8=K3E;)zn!a1Hl~YXn9OZw713M%$oRsFeouD5^dcFU4#S{^NIl ze_Ielpu%BGnwQ);{{Se2lpD+rVV8@jpe#YW!61Ppzlsc2plxWmZhvULC5Tuz3hg{y zP&F6`g-cQ4fS$w=#$OPTe$-hHGAYHw5QCQ6w$fj-3o5Gv=6pO}E1_bTcG}&ih9Ohj zDl(bG3{Z`y!_M7cSQ?9Lc=Apk_@G69R4Z+bpAmSX1d*V&h{O)UwN>^;E}|O3+am9S z2ExVS10+rugi!KlyG*)A_F+t*%vh0a!W(iZTp)*C5)9lxDzIzriGki%Dh+sFdb z%PiI{SP7d9=Op;Y+JjaC!x@HcJd|LehpLSnm$>dh)B3@TriPOeyUi`?h+p)LoR?Am z09-I}?m9ij?@Fl$+V-K56#@E2ugj>OYsD5h2D1&I9>3`#qp)=ab>9pfMeacpWIUjQ zY1xh?V?mQ4?n|!XikaB9P_4P# zr53=>LB{)eYAA)UBZV!c$aMCi5J0(Ud!w`xOv=D}Ww!|uXm>2uk zt4M_^gK(A3eM1mn)?lqXmLW>|12EX4l8ErcENyo2?E0b^-bD#p&LG713M50Nq21LG z37v%;gf#HQOz4&f$#kmo@kKFe8Miu9#T8ML0UY!1{ivZg6}-Du9eSedIa_Cm)le+G z@hnQ2xUBm;Fgt*S_JLl1B)|T#V%dal_luuTiVi1Y9n5d_%f0|JJIjCJfmP6(9{KVZ zBIB{0;$P2SY9LakTYta41)D)k=ur~cuS{Ha01+2*m)Y?{LQdzU+*`{y1|J>53EiiL zD2}E&Pc-pG!-GT1>ijm{(*{S7fIvZ#7M~OcP%%0ThjqE|!Is8<3enXf7Yci%^IzeJ zSf>a_svuP|^}>Vj{5{YrMg~cm>(l!%MQ%?B+5(+lY9Tct2Z%FHn1$3UC%odi--aPm z+))+UEI?_qH>5&VqMISR~o@*DDh;fPwUgbx^MQ3*0OdCc6&NbQIak~I7=2n3zUa`w1>)Ik@}Hy+nD zAB1>f5u6Lp7cSz6PzN|e{uqJ74W=H*@>sZ?1j(>Z5~-G5FktosPg)sxq6a-7?k;*k zRH*JGM(C3Pn7uy__@XMXH5T4OuS`mWct{AvRJgyx0zpN8;_$>Nlt7m4!GqKYy^QZI z*NZ^X-gHkjQULH#d)Dm29+KC>X5r&DD9kN6TpI3+Oic&ECM6I=L z0{c;7uheXRy@zZ@skQ3H>QR$!QIhrZ1=7-goIeL0vHh>}69XG{_eapU+_lQRCMUpu#rk5GFIbRqr&|{Uhr~AtL)~t@ zMt#)QbiI|zVZ2oF%rkJ2Tm@~yF@bp0V&5*y%$%c4hHZ}tdp$9#h3{sj^qQ?RN5gJ2 zZ>^Xlw=WM=XXM6}jaXRGdo-c;>`yO{msWAeAb&gnA51r7+o~ z*E?i?#$BFS#=3~J3!?chwFwZ`8#|$`XA%jJ-NhW9Dte97Xm4YuFr1Io@(qBEq9}uG zcN&H*KuVjPu-rv12LAw@Cm>Mfi`w2gEDy9=OM+Z%%nPNL4w=U%oaVeJeJ|J!HftU1 z>Yp?vaT7zYRoX|FbGe1~rLD&@{{V&yuygKzGMIWQ#Hi9@WE%ofD}AVqhg8E5q-ReK3|D-rrahmqCLfv(xY<2H3cyAI#Mdas2!4j z*Cg>l1gbb~GWkHU2ket}W#1LK4#q|8-!4%=%M-X?4vk5dAA6&uekFpK0%WeeE=<*k zdxX|9gv>v2?l^*bnG(9xP8d^$ggb;?wZ!S-QG$-<-Z()W*lTe97`qM>B0_m5C<$dG z%7z8ENuB`b>rb|%I{=W zE_;J@l15Jo&$()hySA5!M}{V(n}O)zDan2}N?o)OyNSdf>lUcR+zX{4FL1q5k{^gk zO5e&2u(Szm+9-I!Mne!(u5zei$n=0W!9e|L2lm4DU6?z6B!tc^v-1A{sKnu5fdlB98zTM^hVLp~t`A_2bcKr6&XIe-57VLuL3;mwYAJQ-|||o-S=*d&$vv2QVhQDBWh+vD+?gpChwW<* z>|~c={V!-!^gu4pZQc?u3=DS)!z$(|s#gWYdU4W1$gR0X47KDp#IH?LW@dC`j7O^L zmMS|7x70m139Hk4>G{WI47j+nD7$@vmuWU87udcS#f-Px+nSgX{_A;Y#Dw3GMVe31 zcMD4KNl5NUN5t9}?~90k#HD(FxJ^sZ@?4q8mzr%y!eejc2Clym6wu6lA7?i_4+q2H zx(+gw_JRu&Y?gNovzzSjdr)y9p`-mF_IBR9VrW|m^0Dlb}p(Dit(in>CCn6=^n zcBzEvD)Z8FkG69}gzH4#jXY57Je5WA5u1&23bpA8MZAS&-JZ9oPxWO1mzuI#@D>U- zG}}2>ElDXEdU{)Ei$iXoILiEu_>W901xkAtPE1;e@m>2x8NGHNR2r5opCGrjO%K8@ zDE|N`7tF;a5BDLYs5ik2H4#qASVKpx4u8W82h(A4!*>da{wRR8&AsT8=)Y9-qS`|=scWTZvLuew zr)}YA6I&5`&3vP!_}&X;Zm$y$E0gK;{Ew&=Y;}jd4R#=ZO*Oiw;reR|3`_JA=gA3j z=dYZ4_vCt5s&SJR*!er@Dd|Bwx6PqWR%a#;v)rL7$AzZ<0L^2`bGd$|TOH0O8XCpy zS#sQhL}XFVHZu(i5E;9g4Zq=mmqKf~Uhn-_BmViA}6=Wvq}lNO;1l*j)7q%99*OR5EK6%;Km(;9r96-T&>tBZySortd#XZke59JhsE(mtSq48$MR!;plG{LrB?t|mxqfl6hG8Kl;2SyT&qhr zMVfhB5)o-j+KaBmbw^Rd+eE-}c#^9)>*56K#$7}2j+Q(-Q$#-cgKWppKMidYW|b+Y zq*|Am-;jjJ4YuVN`8;+{5jxkz<(zlu@OwMQk5gQl2M2nhz_II1_3liQz9$EXmnV^b zAC*J4y#RdxsyM2CMt*9J12Fbmrkvt)@9xjt-5#fn_}o~!nNC(-W?Y>reFCm*72T0g zTW)ocW$RObg)WYgJ}2-~GhRH6Gq!D?v5Bb9RVAJ|TE!H5WW3Oot_lx!eo@Wm@w)7$ zjtjIg%U+RQn$>Ny9X^#YD=#ngIBqw8pyH6f?^Iq(9$S?Qev`DgnVp7o^1Z|Bej*3jSlKU<@z{z6F^BNbs z+{Axi*Vv!J)vn6BvkXLLs@RjI&}kZ(RR_PmqLV_xKc$vCz0 zIX`coNOfNngVgPwQpQ!AR7#~dD|eN^9sdA)X33pkSL!w5aYos;d5HF-K2!LXsXttq zEX(wV7S_H&$v-H}+1g6oypC?#w#Q1A4gfQ1{hp{SIdn9Q9S2{ZkGco9kZ(dU{lqvJ@LcRXo?{89!F7(x#-uvF65**$2{nh<25KwrYH|{_0MW z)|bQXf=^<%A<3XA%ZG%2V#z(p2C4w#RuJ&p z8q+e*kcU%K?XwB8QVt-HiA-igcLh3L~ywQC1^Hg$sMlI$~HcYS@%Z z5tQ}ZR{T&=+<^pp^7}AT*nv}pvCaa72qV1G-^CRQ4T83RcL1Wrx4A4CytywZ7F`!= zneQCoY%(%5gdB|WUYHTe))wH3bj0p0y}m-gZ&()|j(Y60`2grZ7r#R46E?+KP571WW!;akMo){fSGa!gmQ(m%%s*!>+&DUoYGoY8;R_GekVLiZ} z=0&t6$spB0SJ*+7BtLXb=qF8%W#z}Z79vwYQJYURg|iYECg58>R3L0+gdSI`xQA~DJ@`+%kRLDvBYZ6$O3fzx%w+scee??7-DH~U(SeFaa8qCPt z-d6r7mlnt|nWrZb+Xk@K)Cue)MDNKUiQGvcm)e11&p62qxx`WlDPkYR#RNeFJOYsjB&F_O_PMEPl*)Hf6GzJn*cH4ARs5KXG?(`U7Sn1ZLUV9R%>vXKHv7DL>y zN;%*%3u5b@@ueN|r~7ZyYSTV0!~m ze`4~NE~~szQo+>2-m6rA0>ekj!z}**W)(ew8@}N8-4-pFX5`SedX=y&Fbk6-?Qs2= zRY2U0Tyo>q3^4`8#E}7DPi4s>2^q~5*CpTgf`}NZ3AZ?!v04F(`Pk+WlI+PwbEe?Z4= zj`v@bLLl*xxGq72_87_~;SuEr9^oR1A-9(}yUBfqZw~D8PiLwwCt<3n(qX%gH2g7O zJdI`?w9ALZ5CNGuq89aWL=IF3N(y^CF#r&pW;u}BF$l@9Z#E)@oDE2-3e>YC5#fna z8E=g3NTwj}Un4ySis(zh8Ar^He|{4$58VJMlzA%1@=xN6VyZf2r7nNzAv4&M0k>!x zClpvBTW-_;09>L4V+C;ryx^1&%(hN)=Mtm{E$6Ry^@1;QECEMe8$@7O&y-0Gw`Icu zP^-UaN+32K$mbU)@@Whn^e^LyXCc`1&AL$eruF8VVr59jb^fq~}>tMi4pqvpde4-Z2 zHFnVr1jpg`#27LyZA9wnh$VozmAdf}L?VGF30ui+wjkgxcSHumUFJuUa)>Ix_asZN zIDK1xs)Bp|Ch{`eT|I>(f)(S4|z zfqtN7+}>QP0Lo8Me~b|!Go&`6T6Q2^OZvE?>J3D^SBAX$U^M}(=uo|BjidZGMUqrQ z?p|Y;+UbH)j}x22%{^RP#>g#B(96CTs34$C;^w8(5tO)1i~VM)%lM(C`dToByy3To z0;3f)EL-kTxV@2z888q>gI=MCR;0zKC;qr|f>A~VExAy*f4_zZIs`&kshtf7JVBvF zi#%Bxjof>WC5N2!%*gct#)^lMRsHbVBM82{}bXXd^5gx};c) zt)QFBk0k#9;)ohgNOJXy@ZAuN?j&t#BXcrVk?2__`2BdE=;T73KKDPK?Jtz;uvbEHw55sKi5=R zj4(({+KKj{h8}X_*V=%NBalD{ku&g^)x1JfaXz z`q1`LAq+6>x+md_)Po&DjlzG0dWIJfut7msVdn)&HHl&ZaJQVG;svu9&hC9ZFvS8V zK}UsnxUJ!e*8E3`ZLuRC_+p`22pH`hd!hou*%IgCgPG8XjSkx^{`jRYu zJSJHWe^e5J-pYLz^nAiBY|qhlo=h{loTxRuecur(>mH-=SeF-58IjUgr^x5J#c=35 zQF3)lcqD7R67dWN%c)K?P=CQ|xSRJ>tL6gQffk}ipW;&NGg(PQU4jx`#rdM*8p zU$<{V=Ic@eSEfu$vpv+@!$oMizW)GxXT_gCe;K+$^j9L9qYdqHt+N`h)Yzh3+=Xo! zjZ6NK>^~p*h03*KnU{>n#?+CkNMgwb-j{i&nI(?&R(yrFBWrACPXNtPCaD;;xv4X7 zRHZ5`ac+%yA*Yljur&C9v5kC=OZ4m69bXRO;Tbz>lz|)Cnq$@pC%-OHi=593Lrxc` ze@~HB^mo9&q>Jx1d(tH8@T3n378b1=1slWcBuZpKMRs7Bb=`+(dHn~^nP#a^zWjPAoR zuFf~x<|_WwZ5C<9_*!Jt1LTp&=4Co)e}~eqqb8*}SoCs{nVY#mI2fVT#b z^p@H|;o1n(r3l7urNB1zS;=Aid^=*YWMp2Gc6tl!fBPSSW#(LO^qI_zi^ZD9e?nlz z5w4@I@a%v}$%-KH+r=4ZE`G$Z(@;Q6N8;g!C`vn%_d|zE4_nBV2BeR1!(OkZ2)c_G z2%#)mhhRvLPAC|1tURX3%v9$aY#Dcmy_L2jW?1}Nr&R#*bn`HPy*@E>JK1OQT zsE*BR({(qDX){(4DG6G(ko@BlfAcXT8Y3QA_>yewKNHLt*-Lk(>x+bPfPQ8mql=b) zBcJvIiiw@?)uoV)-B%Gprj!SJsaxL;DSiy8@F!i+uUNPzs;f_X{MEXlU zOgm~~gGn+mkvV}M_oc%WtD<^prB@t!NDPVgN;HgwuJ1A4fZRgAd}>SvOq`l{sY9u4 zp0iRiC#Rn;hU;OG8ZkCze}5txYZ}f@AFP-+6WJoV4m%2HX)sj=W~AIlUNK$Y4qZUM zPE0;(%ohJ;ape@5K-*{Yi{HjGd!Q3!w4$ zbrc+S9SCh~6SkUiv?Dz*EZfx+cLUR6%J$eWn{9f^kQWaR3PssKY#C&(R=p>%jvWvF zxf6%42<3DOW@s9?RH5TfPOLpEEo{9#+Jf})aR{RRQJu1Hf8;uGtQzAL`f^pLncdQ4 zWGU>HcUsM)KXfIiMMX6(DcVI-7gU^?pO|&$3#htcr`$HL#DbWfCx;{`0AYrc4jX}= zR1JwLHE>PSxQf^!%skHU@>pAgVI7@YG5-LD77>lW@3rCaebG{+JVCEb!vol=f^tl| zxMJ*B)@_k*e^DM7mLU^RoM=Fmcz!4%yOsK;AY^_kbAhN`h&jASqtZXM1gcv(xcv_O z5Ajc>h9?qfxvFj!l`E$zJCzc36PPrM%~#L5J6sROwpO!rsm$eb=+#r{lXKV$&`)A* zwvug8(3r6;7o_C%{;y`l!hc5HDVtJqYGLCahlIa$TB|q6Q{`i3;eSEQBZq)h zY2fptf8r>=(f;5e+vHxTzK^(ih61h9 z*bM$M!_-qPOm-fR0Z5M@Jj%#=XSfNfe7FNZi2)fBZQO@JpL0QI8ZF(-(ausO8NZzz7hi zgz|y5Khd0MVcHol;xc6Lcr4!7BT2qqOI4{a&NMp6vUh=zoOH*|2&UP9H56 zKe9fK`gZy;C(=jg?e+c*m|~l>%s9pJgc(^pnP<5!`wN$veo@B!x8o^0Q5dr~lAoS! zf4F)^;~kLWYQ`AEl)8-{6Hw+BR(4Kkmg#2`yFT<@Xuati96yc4km|4GuBMX49+DV` z79Fz_1-jRQ<+eH)_*jt5JjQ<_tme`ui&W?BL)QX{^-nXjiLpK|aT@}WvXA6;tn(&0 zk%|l@xQih}maH$f0&-+U$E^;cyTmZJf8dwu8@~wpT8$oAwwk)e#RrFm65Jz9X2n{Y zHv;-+Ov=r+txP&T*NQDT)fD-(wL;^IJwGb1(uSWY)bQGF?=2j=W-3e7D9eXsO6tYtW~riZwuj>iHTWf2}#kH;So;qHHUtge|$`D z%WCwSx6zN$UYkO#jcTubQn2?CKK}p}q`d6%j<*|wLzd3d;&lao&aZL`ukU2nh6IsTF-h)~Uutj8{G`_clRhVTX; zR_84_c{gt5X=#>O#$Ige7g5m1e;RI+<-JR6Q!`3JBw|-bjLQdm7l`u%?Gj6pI{Z$?rBsr3MYm9{xKr|o@?>PuGbZKC zim2R>&*}zaLxyvgw7stnR0m@0L+!(7A+?Q|y&zmu5@wT354?OZ8Ld7bf8|Y_YYTFB?Y|fZ1Q7JNXxi<%*U6cXO{;=#@`5NnA6PPx|g3_V9UD#YA zw){y8dqB)cvU1kZXsb(~eMhhH-%-<#3;Puiq0kG5Ild9HHrT^nc4uZKkn^ zrQDiwk6JF+GT=NdA0rjZ&2^4CaW$9vgf?wAQOLVo*-31V8aCxUf3s#-O%1EODv_~P zh2Ly_DEdG4$mlC`)2O2~&c$zL8!g?X3$Ruk z)W|5|BRKP7^AiVYS~n9(-yDu_A6;J)Um5r09(F{WoMmLDH!}9|5dQ#ri^@4Jnr@SN z<KU}ZTdP$@isP;!2ZX>4{u>f^#)jNsdu|eFLr_~t`IA& zFJ~wne8|sUk-<@!nKE>@t-EcbXr;sRft25fwe~Y3CUmxQBDkE)NV@C;Wq%Tzwu}PqJf`+(d5*^D^ceV7Js0AWZPIan zC6A_xaYo+oz9HD1a>}@wGVyRs4tBbc^bQv>f3j|q#^$m$bL`gcZF9eU9b1&FqCeX4 z6PEA46dj1pe_1f@(YMmtxy1nN#KYDarH>Su$V>KP7Hp?MdJS`nF_V*%L?Gso&V0KR z$#x(TmorpwRW2g-MiM>a6e#Ju6F%4plVxc`VsvJ{#`CmCsoiC-vM|)l3%A&2A}h>z zt&Wx#5b3i5CA<{y#PWz}`Z)EKYKZs7%2I zo@L91Bzpp(#ze0a>KLx&R6BH6UepAl>_}kW+4+OjM~WmcjK0H(d@k_@Yh*?uzQTWG z-XzT;tX`T&3}F|OtBL}_q%;m&!Y-E~fEc!sV}wsVdZ2p3tA%R@xaTYQqO~QaHjvHW zR}C}ce;A`gVPJC@J=Qpvi{Xl?X$(hCQhPnwz;ut(k+TxyF$zEmnM5>p26pGH52hW& z5pZU{7#b4b9LRb90L=$U0vs5(!J72K-AjqkqHf^vK|H$L&Q7zJa+X5KAzoWbU^yh!sZ-Nr~E!$!|CKk~&Z3TW8IVpqgGP++PIo|EIyVaUNW=nTY-$)8Uvh8YqIIN8Y_ ze*(+J2QC8)Tx^HK8WH}&w&Gl62=#`e46xQ}S9z>97zP;3N$-PR7;5{88@(#6+Nt5; zw%B1I#5O#3j1q*+v0{YdnwJa+wrvU~#8))8;kpf$qjAcaE-<&g7Q<##5peM!#e-oa zUDs$_P_kGPi*O7;K$o)v+!bvBag}=He_p7RC4fB0kN(oS1Uc>`TP%fPZiJ5D`z_9B zUK>j9IJ&r7gFT-!6-`XBXf4E_F|=qGr9RiGL*BiMK5p?Lc?JW zwzrp@K!NstKAz}=g;+dN_T7;6L@Ik2akAk=@bHve_a>kpXSnm?x-P?I0q#dHe})NI z2~!=PzrrxaVL)6FMa_C*DwnwcL2bDY+J*)KrYNa6=Joq4qBR|TCg-|7TbiD{{X`fh_xN0#$l&aL6sCQ4)fvZ zD2k+*2ri>qtV$B5K&~-f?INZ&e^%u25qH4Klohu#4F3RXD1y*!x&u-$TV$L!7f*&I z2q;{<_`F%cl|6$tTmzTC;eejuBj{H0*EKKo?Lmcojsd~qMez&`0kEOD!yX-Vy6-qT z)KFGbggRoGO$ho7JV~S#OIKJGZ>$wgsJV@be{o~QZ$J7#0UgQ}HNh~uf2ew+=L~vs zR!#2py56Xj6-|JVotU!YI!XNU$g=5YjCrm(wk3GB7s_25o z6H^44&g;GyreJGh&k_p6)CcK}+{kWdEv0X3W+;8Xj9W#u9iUKnT!+6%lZHSogCL#Z3foE?yZ>32Jl4M;d0 z5OeVHeemM!X6~H0Zh}y+pl?dur&Jw*Y1rAxTQpvAr6$1Y2hwu4e+GOO#VAz>;~T@x zdWssQ)WWxJmj2WTBRPSzA{Miugx01@rO^S7!v~A06DWk-4T(7IScKde4qgwsBB^4; ztz5qpEU4@%^$^#l2|;xk7?8}8ekdZ;e=gYBnsE)@C>sPgzk@!J@Ws}{5b<#aXjgkt zW2z!y#7=kH?8Fr@f1#%INb+B2rrV$@P&ci!C^AGa!f+A5$vj!{L>WyCh;r%bh&sb} zhp7@Fl*qP&&)pDgIft9MHR<-P;fMnb$(yx^Mm)(S;-%F9@GisIQ5)|602D5JggazO z!^t>D#R>t3@@=VeANimx1&3lguoQvZ1Z0NTvKigndGwS)e^eAr-S&UQ5U{O?l4%SA zXJ%Et3zvtBq+$%*m^VFUpKIEMndOYPnS4B4F?Jdr@0~e~P*d)zl_K{7|-?5l<6+<9MLJNA3u0;~~o8&e+f)tTLKpd_EuJh!iG< zrzVO1D6K%yODA&Voc*Y>;XpY~Au>hf3=9L3X?}La*d-giPP^w6T(J{1C&wB9L9>$k z%SXcnV?ZL8Puc!ZKyE?1E+QFtyihe1@(IZl_HFjOe=$EVbhUl9gr5Q4{vyT!Ol85DaFjw{LLECeygwIAKwZF)+s+^n8xrewo?ajLq6G>hcy3E#2Ev8T zn??ab3O8Sx^=TJSHz%&R^x^oRg-eRPMDt%43=M&P;RQ)gC5l+s*kMeO7dX0>D=YQo z5~3;)ax0U=?L}0H0vxn;{{UG*myi)1r=Z;se-SV>E)-Cmy7$E~eS{GZt=&Hi7$U7M z{{U(R#1z=vd3c~m9kJ;|>FJ0$7*l=}PZ04zD0AG%@g7|$KGao(*eEul-ku&Pwq2KF zE#zl%-4_v{k6U*SvzHWE1vWoo(SVf-#(F?_V(LhPBWV0K#l-9(1XpnP#e${;fe$2z ze@TID@SdgB0KlGbZamg4LQx$f5lF!WMZG$T2(8JRvs~1CQ2=$YX7T{L!_xw#VhB?f z=&?|vSV?gfoIHfSaA6)RML4=hVgacVt^qhn-}^BG5!w|`H0A8X2BI8ZF;TRK{4ofi zo^WruM_;oLAa@Prm)eL6Hh{VIyy62Bf3OE>a`(g-1;V_EQ>Nb!ltK;Oa?|R#N2VY> z!4VEw-4GGG&T;1itFbL<23$@>vec%H15(X1f>s)eAiB$nC?mu-a`ArDL5H`*x+fuq zYhc8fLK6E>J%Tb~SLQ{f(8~hCRnfmM+54jAbV!5OA#;Fb2uWs3a>OZ=5f=tse_T)- zlB!@tuTm~>H3(MlMRV^cl^~MmuRn?^q%coh8T?Slf&nhPx_-;kF4u*26QVieL7E6)~~cigW4qVyXb{Tzk`tko}ml);3Dg zTN0RU3zg}0uuDBeYf6(!<^NBdM z*1aq*cPo!!+WyBas$t2`t|(+zT0XMW_o9C|?Qp*?$2^htAzl&b zi%ePrE8obeZ6>+d*(Sxyf36-YV_#(=&W%FwJ6^8}Cl$qPa_8NLJe$vEczh&jO{KhE zFN!yO3~M_r6D^4#x9xF6`Hd^vXvITiE@rxf40t^D5frg#WgZE z5+^uR_6UioID(g3(+id^9Mij0uO&vHL2+0)kHZLY(K|JcAgfE#WZZq?-GgIsLBiRE z&tsl26(V!eGtw!yL6>GEB~OOYQV~Rsjx1DDpPP`gmZ#CpmEjbk$V-Ooo7{>Gw%Y?x z-!9FYPiD{+UZ_1uf1aegvwOx!ei)P7AavGUcHkBf+54bNYQ)xy$CrVGJT83HMq!A?Bi5eRtW z8}eC(wj5FNGC2DT;o50CDI+Q);U`OoqcgiV$TWpt5V(KRe=1sd?2?g{-w0j6+6fYU z@VLZ1pF$biwA7r-iTat6Bnt;F7`Dd5W*pWw*NNyaZe5xNS+#BwaR$VIix&@+C;L#W zN}bPCCCOfG3`evtCDkAr(eVixiK>1n#BH;_X1OmfXejW#!;-~y1+20!{h{J|d)7UO zrDema(U{^Tf6gu~{s6bD1k*V87EMbO*i@xv8WIr^2at&KyNfTw0Vv^HD0w}u;)iG3 zB_@c+`=M+Hd*82|RO4{2!G~%UN2YhP6d6+DA+)7K)~NI4_M&=!v)cX#EwgRl+53a%2V-<#4$z~mL;gt=VVQ>L*Qjy z5=Hlr&yE%?lZZ|c@$$-t?0sT;kf)JT`)smcyBto()0MMD}O(wUZh;hdJ06$Uw6 zD6sYF7bsF|6D>JVCm3&X*?0$v-k8#>peNXkoR|_#A@GG7lWdrSuvW!;tV?12jl4A3 zsxdYw?I&s%6lBdiI`I`L^VPFq;#4hegfMC}vtub3AQBKVWuiv*K#%Pj`k#@k@SmCE+f_UrUwj zS%{|aNwRA+)6(-CO&d=mntSr(zUX(Vz~oI0DSLjIV8uH@7Z(o~N=5qr0JIfmEOh?> ze@K%PLMri*4 z^8=sSirfpR>-$A6;1VOloLhT>Hk!mtONM*kxOlp{p;PQ=L?b7*fZH~sUa@WA`M|T6 zDnV4XfipCAiEjKp)zCL$hz?sE|?Z}2}pUV#~d_K`9f5mCy`W zCkE5c+Wn%64M8})NHB1q2_h!S)qrPOY*kcz9k8R~Sz}^;EwfWngXXi|psmFF5pHiu@}}TR%$w0CC6U{zbfT z9aFWhN=z~6hk_f!cqT?vs?4#ve`p$Q1-W45u3W#IcXMZ4f67w^9y3po#CuT26AOeJ0YfyUxljH|*R=3VhjqDlvG=9ac4VGx|@a3wV98 zuf*p}3odyEn4_rGIK$~No0>8qo~B-4UA&x_a_?xPT5;AsCRWPBM+y1ORl*|sQ0PzzJReq{!WH>ijFZI7%&DP) z30rES!*S(oZPG*SH_;LF#!S;>WoXsvfy=xiAn%BWy6t(SdTe>|e{$E-%0sEXlux27 z27NBOD*pgz*cNI^o3(mus*d@gL29&vnxjvmdG{|gT=MVF%>GA!+-$a05ZIbJkIv55 zJsvCD8OOCuF^Q=2R9gKN)nal@@mrcS??vwg?j+G;IyU-?S!pW8fmfZTF`|LkP;B>k zqLJ}@F(U{p!^U=Ef7x4+JDgwXiifEuPuXKEyej7Oz7gRlKhlf!3q>aeGb%zYHf8MH z;e!1#z}RwfMwslVWS*@&Q1!yG;w`egj+>ogUSvtvfQWjbiBU>kwrhgs_MJ167j`E2 zP5zM|mzu+pc1L5ZHaoM43=>LisabJtZA;8W1dHNW>2a{=f9K`O=RoZH5lT?x#O5Bh z*yuE=Fyh%SU-YD6gDT=l^nbnZ#ys4Gf%wy1CX3qVrgNkZFK9Oz9wOalR{YnxI9!f; zq15By-$}?DZ1j<+Z8!8b3E5h3=Wq#eI=<4!2bIlio+i}eP}Rit@XGEIDCIG|5OGVM zCT4RU8-Jt}e@2NjvwnI$?J;PxlIkUD=p-crYK>OW^ncN>X@QHV%(QL+5Q-1A8Y{FL zk`2iw$c~oj6uu^PjRz-^^8Wye3Sg>^r0NDIVvpkSLfEN|5R+RTlc-dRZs&I|W<=LW z=iidT;Wy$T@Tn~$hUKU%*(%63pJgtM5r{PC@ea(sfAlj^i)2u&RkovoM-yS^nzxGE z#J!bBQ=>q3IM-^JHFB6~1g~!~;()b*hW*DWZLbOL4a8C6>9^T{MAd9)oJ7dVyg=9& z2^o#Tf6{ltB|41EyE#MBgV3g19pXo+)M|qhlH1g9CbudiE8d6^n>KB9nb#guYqwGX=CPr3>xVX&~HtB+?W=IvZ0JAFpam_zZ>HWT8y6{Jw8q+|8paZG ze^QeUwhZvQXHVZ9pTlI+89C731FAM{iyo>_L|<%N0u;LLA<8{YYQLJCS5|@jOJ?+J zNE^mnDOY|N=H&cx&xui$qr}zQUfCsnlOQkCglCy-t~)a#%jFus8o#H{{LviDjO)^9 zUr9_4Ew!zS)TE}whI&<{Hi*X36HY@Of4`SFoo$Ho_TNh%EUOcTO{qOB7;4nXWk<(9u`5#)t+*2Qtm6Ga&`|QteYr}_$h#O(qYNX-T-DG^af31tAhbIkOlw%CDW|2dGK$0wQ>kGqZL?`I+K&I$y ziS)!Ncxs^zjLTu5#2q|kFf^HQJq94S<0xLj$ap}arZyj>p`__-%#h{cfrbKMNl~G+ z%(ktBFDvL~JS!?!F*fF03p;L%?D{$?iwbMnyilTRZ_+N0*MD+gv?TE3mRL_s^ie&`I z^kk3|+<}F@0bb}_#)Z6u7JT3(>J5)>eo)I76R@Jqz8DO#vd0XSyvFZ}Gt^5qgSH|c z4bepfSQH9T8i_P2ZjA=zHbsD`ce?=NVmH{-#*gkz< zloGDN+Kt_04(=Z^;)hBxHluf?(0L88RVYJkTc>yVM5t9TEROCifiNp?!vIBL4mPf9 z7ZI_7J2;nL{@9^**oACJ?Yqtfq1vEbZ>cWf9+(yO36L0$??fA}*X+f#>|H?=t>3i) zQq=cl;w82hVT>npe~{#YUf1G^iO?NJMqhW!ov;ksEmTP}aEg0SBHP%;=F`?842vXJ zC$dl)lGCx~*;@%Ni9N@ea#a;GfrcUCmAcekB8w|<3}tu^!{=?#K!+UN(LT?!6M8F9EJbY2Eo&u) zYlJgGxke~~nRC7gNEurq@Y`%$&WfJLn`ex8c&>%J7HdjO^}s*;H^|pTQuY4O0P+RWwiVRDW7KWt|78^ePN3T%C1ZOn% z@bkI{bpf1c=MOF`po>!-qSGJvp=EAN8{JvK$6`luuJFh+{uqp<%mLnU{ulx(4W=d* z{W_u|e-faGvTS&IM&D)+P*nFYJ!A1jlTtWrZakCrVg)^i%(jX9F$k1ueBv4vM@_kKihCsx zwdohVPj~%d1kAvuodP{Li^UKDn~o8f_1h2%i6<`Q_@WHQ19`&|4AbnCL9E&sy^#h> ze-Igl8@XtPf8mG(#)P3?%wh)8f35=i#tf=Si>=lcN=cSPi%zH|27}g1OOvRI z2)2z68k`|JQ>WpG$^}QP)DRCf>57=xL*{3=1eO=5E~Au~LFdjU;8Tlca7&jL`NChY z!P|2JXYBs~#Q?UFt|?6dMFja>2izb7D{yP#fBqP< z>LJNdZ2gpoQ(Oy@%>v=lC^`g@`eLA#Rte$Nar22l;lfblO-_Gn}Tvi+#2VhjpYNi$E`e}ZQzSYc3Y8|Uo7D-}{b#%|NZKMWOu6V<05R2cyi zSD#JR#0DfmHX?mIU&9csN$uJ=cQ03lAZNc;I&nlKM#VR1o_)~@p1}l^xQIpu#WCUY zSRi`{E+8aN3_>@sGIWJ?=j_A~)3F5M5t5>C#3V4A3)+gA(HKwbI&!xC5}VJ--YX%s9~yW+|r0x}BH z*th=x#TKT3g5|m>@j;7aF}yBm?DWJb1+qoU_+kY;%v>fK^!!l_#vBw2m0}lAcitLu zA8G}PG1cPuphKpG2&rmNe@xmIiYu2t4^&c*oMsy&T~ns(qUuz}f;Qn6kyR8distk9 zqFAO;O^<&}Nn$K8VfA`I?guEb6eY<@*qeaQHXy;T?Z~{~K-7uie=Q;gdnHbB2z!No z7`qVm^3%HBn6(9h3)v;>!bJpLpjF$`yTcU|uyrDd#2pgLqRUgnLuP1PaS32o5nK1f z63if--3Cej05n5Df^yK(Ap~y(d|$LZ@dzNE5qmM`?L;8nY2?2o>4;@!`i;%&vf}-i zgOCf)5om?Q@kAIke{_p1mx>_)5?UG#u?Qrb-RhR`_+YFCqzRHZLa7mp)rB9Gu~8cY z@iUD1;DwO9i@v zQ`r@rF>K^fZ5He;UVju#KtY*!M%>2bD3jQM<%I|}_M%5(4w+Nart~3^&z_!YIzNI z@#xPlUBhlyfAGm0h$DNf2=L|YByzESM=lJ$mQl%#U#U)Urp^^Ma>s8KY`C-GAz1B1 z+_gej_OUldb0&yv>HAPo##tLPqH7H86`Ig`w*p>a&Z%2^im)w}YH^A;l$kL>Ra?U}a_B})(D7DnT+e|=M{{{VQ*x$^PbrL0<_xd`OO zGuVUMnuNQx3Sg32=;lM0xqGA0@P9t9(#1X<7hEZ}Dn=}jy{ES`CDg(Af6hH79Ltl$ z!q~P^_K`W-C&*N|H7mXF_l}TQj@fMp=IU3u#EuoQml|~yGqZkD>ESNOppCZh#+r%L z4`ii#e@(``BXmV9=MPQHe2z`a(HU~!XEHyWbN>J%$W>Yzxah?H0MS>mHQEN-3cEDT z(iAYfZX1N0USE`Xyw9aN7?XJqr*lMua*s@5Q>esELSuLZ;fYafK;kN=W{`PAyes%t zFVS%q*O8^`!!uHxB;An_7lc%XGx{z*ISNMBe?1;$wxKNDI)0$+iA)*=t#LR=zVDQD z_*?DmhMp8WA=?OnP6^s59j<6F?-}N<1aq{)QJBci>v%*O6YVdUwc7RZ3+i4|wN` z*@IO)BWj+W-&|=kFi%_U6ejF3Wn<(;RQ`}BuNzz|+Y|vUd3!LXE)h~Wsr$qYNvOF2QS44{Xgvmuq^yaz9RH@*;Y4}B$&eP z(_C;!@0ZMpjL!V@?m!qcS5B1Ko%nd9<=v-xH_1@36ysESfxRFn>No!Yth1kdZO6&U z9V}qY_}{6nE88on)84gNe*#-nhSKUp{{XI$t~qt`Ft-CN9OLOhK%Z^K+?SJm=Ox8O z+gn}~PwAX&T+2Tq@GE2TIo_p-ot2p(nQWOaWX95n(68ckEJaRYX0I&zNWRo&bQ+@{ zu3C8R5`gsdSmbH|<*Rpw5mtb#Wtrg)Me%*1jTR~=LzKi1wi~2*f7=Vgb3oiZ*qQ{wntRRQO+N=bXCLjl(18_s1uH4ZgG96seKYEOw;j0Guk78 zoxi>>)k55qm6}P5e^%?gn7Qs$PQ^Ij4+@knzzwE^2+2@{u7<=6h^W_cB786*D1w(l z82cqKr3 zE`{GgM{**eh?|jQvoHOzFS6=Xs~bQE4<`7u>gj@x;7b)Me}*QQo_K_BFJwevk%^?H z%R*lWY?i5uJ!|9HSuB>~6}Y2jE-QSD&!1;jdK~&Q&}rD6%9+UTrX<)F?|(A&i_DTW z{+oR&rd7+ntfzKtw_vhvNtIr@Lr$kJ=kht6o@;06#AyEj zPfw*Hhq4+We_`w}z8ox5|{IZ$K#rO#(%U|f8wPor|Cspo&pw0-`a++<5oUE z{=fE>%7)gl6;EB`qrCQWjWNc-23h1~k{)uYh$n+L9-1MBEMiejIb?{Mk*PR&xZCCU zRH3ly1srDdgvI){@9f3c5F*Krg#Y43}MAnsP7yNa<1Rw}?& z>kLH(f2Fel?FH-`TV{Gm!){@cLJ}SuzHr5T7ZIcDPtiN{txVy1l#IKT0=`^?AC)rU zB=5^U=>fpqvW_M{V)ZM5`QFUa3{Vw88P~5zY zc!^9sR$?0ydPFxO%ZlU1BqhUd717Pj%xLpEsmAHB`(j%)#r~Ajou_#*LBg$fS+B$YL;%-|@Qn4uf1=Bv$CJmz+6hNwy( zJN+XzB=*R5fiWq&F_d#NV1#&b_F+7H)#+i#`!&ePuMsS4u=Mr_+?|K0{{Y?uw^GN0 zE2T-FZ;H_pPl_OZrjE??L)$mi6}DSfj>5=9ZieB@@gE3x?g+0pSfw`bx%$kmfBD0s z+n~*sHch=MMX3JN005Tu&nuvnc1x19nahIJa^dj7Z$VUWt)UrZ1>DZaV1)}s9}Eyt z6&ug8j!P3`!vgVO>~nvlhR>Epe5TrLh#u-62&`qUg>{^Nw=5QqFyof)`P%^f9wXrw z;&R41qMa_GoxC@AyrKB=TwQ80f52Gcb7naHn_Z{8Our)dz62iS9LMiXA*(ts4q7qy zN6}wyvRi)G{T~oZI3#mSCj3XjY0V;?chqglhS;UoDHwnBnHnoYKjM7-Wxo<4@>o(P zDC#r35!H(O^{Jig!-Pgf)pSR4ENByNX_^+1WRG#RLa{GRmM3N&7QlU?e}p?|awg4Z zj(sa=I6ap?NNLM{B)T%cGZGq8jj>xH+r+-sf$7--;Fm4Y5OsOSFPE16M(j9rZ!8MR$f5kJq{jU&|Ivr$L zBT>LOZyK`T)M;|A6PzcI+jIZ~LHoX3TlztMJ}2^6G`7<|id~FtP#ia?EKAI}nQvMf zOFuXd=c4rFlLP)AjXo=1`5Xi3;q+lU=!*QUa)~(YOAXB$0Sl$X#=d@BQL}^jjJUF{ zEdK!NV$1l(!PKo~{{Rz4(|_CCvLha<`yGjw;yfyQ(?P=B*2Zq;Y?)1mvYZ6tcD`-? z8+v0_6j(8)Tep$1<>Wgxsc#HxvA^{JhGmA&kBNPlYN?)cG=ZFDhk%q@fMr=XVZzQ+ zF^sx48Hm7kG|nYOHMS&^DT6CV#TJfmd5pH|BAcMhJb5E!`!M)rlYf!wp=QGSIajT9 z;UVR33^rK2Sbaj|SY##Hx1}}w;2Sa9!@}2zSFa$--I-fybwn-NoIP&}ov^l2SUYWrYJ#8hLcx~~E%hh*$`%b;3Mg^N6K3b3}@lOP;~oZq@!MsqQX=*2G&41`xqVaO?%=@pzzFLl0^V(6SjI zEQ=^rOR?c^kAK>WC9F6ZUI{~qs2p0A*<*UeedXw*gVk-f*-wn_usGiP9wxEaFg^w_7k1r3|fZU6C z--wiB!lTs-nb;_xtydt+lry=z#S0`LlW+5dfY%aI_a0^ULjk3{j1Z3weO@AnzQI+D zfZh9CP=6AkracPm`SS5Xf{x;bOoQU^#0)26^?4^<*2S{}5G(T~ zgMU#?I~Qs6_-_5Exs8>$2mn|7FQm$w1glAlhNh0$XD2 zXd9FF9=t;i7R&->(&g0w9m22)r4e0viY~zoTrTA4fgmS_`?KkSu($Rb%v@jlphdEY z7cWfLstHR_WOo4n0EgL$GBuZAad- zS5RihkJ^h?El16c@bW<}n333uIb^qnN<$9CIvLm`y60{~@Q=z3y@l9#BwNm_pplEz z4jOF^*f?UvZqWxZgDF(YZVmbPuKmzTNI<`>58?Zvu;tKcG~ql2aP%4pCoCk$5YA`@E{!rP=mP%Cd*yd}dBV6vG0FXD(m4mogT5W0hvYUBwWLpdOgJMC36GMH{$ReVBtQQ6*tSCx0!A`iL7Eg)uqT_iqQ(npksf_t!w}ByCFJSMho%Wdjfur= z&M0Dpwvhzj-;%+^Y^HV>E`R0Lrw&m;ueji5k-rtAq+CY9WN92VI4g1|QAHAxIF)yPQPqQ0`aO zCl@dALAeJ5=CG)Z6{rICM!EEr(D>;ZiS`DKSbFsMU_yU?5iU#J!+!@IfTo7-OZv-% zZi^LAX37W^++P$Dk4llX1h-M;3{kE{Ocl`GKs_)sm~Q8^{x1w2N`?n$@gng+hhSzm zYu^cC_XAg(Av=5hs4J2?4Q9GEFZrVNBIOA88P4R2)`pCxhTT#Nxdsyhyw!N3R2CbP zHt4-DaKL~jlRxo5?tcq*(3czF%_7A_*loK}Ore9K1hzh}ScOk>4hdNhEx?x>AuqK8 z#ft*%~q7@y?S|h4`m>UKpoAo;N=@&DhU>xxzRf4cB>XwVu5}^ajzq%@* z)F_Y$3Ml=kpjlc)sR=LemfRP4P-DyCCEF0kI{{V_0D!VcxZoe_gDmw!%WgNM3{{UITNpsfS5Ohi6;XI&J zOUZ$7x=X_Z3U@OyO5kPF@q2Lsc?IC=hz%r2zN)z-4M$l*ijgi>#VEb38E|hh&!!3| zV6$vR*6O@P5`QOQ#B5V#$L$F9MT?aRq#(H@1SoS1WwH|t*PLBJR?wkvi0)Fh*p(Q~ zp~mxrII95@u|vcf{{R#PhU;MRQnfCFg)dTgnYasxE%99jQH^=0;rT$YRx(B0pJpn+ zs|wtR>j3j$*+uUy9eI0EYNbpGyLrTsWok^@XbO8p5r2Kls3KCoLOjHv=5!H_;Vy~F zBu>aLctRiZMKCrzFYsx_{4pvK;h4)(;o*W6Dj=qmw6x+BiY>ypGEOhW5V{F*%!A>! zAU%P&=J7L5n1O;PI*CRgRs`X%hlU_P<_L`k&e(v|YG|c*vk)G@TyuDt+xVgaveF31 zN+5fY7k}I_0|57|LKWo@4ahdh2Sw!vTLSVdR^{rodajEtRP&Cne>jWo5}s_YJp18{ zp%^mWYLFU|7;Vkj=Drv@5c7q+JCZ9<97co)*m?U=OxpnU+<7ciMTlUA=sFNV2^HD( z!BiFiB3#pk7RXlQC5r(FGpJ3f8hBzyu}7R6M}H`?2r=OPFSQVW{95JH7pWnIxp{Fz zI|DEgzIiN2_XvG1J;*R(tx@E=+sjdSVyXu0!=WxeiUNbIJ7l+?wbc+7p#D{~6U{!) z+JRe1RYF!q*7Z-r3}9+(jgsvl%sBUCf7eOP4(P{j!@Q*hGKbXK5^Yz652)w)ZhU zTuw&OcEI$jB*jWxCb(_9x`sOI&u@`UjEbq_$4a2u$V@uTrNV+;@tS!p@fjFY*%W_B z3{QY$1!_$8hF%+h&&o96{Kwc^@e4))^uDG{Ql1)%ZnHC~2@gp|#r%TLhzZ1Rk$=w{ zNW`yg0~yUuTXM`A+CO}9d09P1G(>Bt`ib;)>_`2L`aemzGcUQRRgDvA_>kqACKdDtYqe9Y-`dts$)t% z7;e^!wixjdpGhzIN34&>V)-05%zxH3rZ)6|dT7b2e7vl=UDhI(Kz^4L5_2?okefKh z&a<>uW@hIAo+)QYE-sTmB~QK@)9I)a{LHGv2ca&^@SQ6h)v+!krO8&SbJGqw*KGBJ zCU>gnnxe1HK6&|U*>X2i9*>8{#XVVij^St0-lb2bwsYtxtnEVXXcEHis3O4__d0a zSpm5;=`XTAggCOKB;?qPxLd`=acL3}gf}N;V7(LT!#-I&HVJU>uM|JiVU}qnY5s=w zxyDIKU^u)Z6Vnp*fzf&n)SGX5cvv_wA%b5u`G$cGQwnc(Wu-c)DYqSs&b?K(~mfM9!DJvxP zf>ZX{Rgq*-X9$-;;(sL=uSAiUot5;EpJlf0BH{4~TLbBaoPy0q^rop(p3Ej45b<$B z{TCe>)Vtw-PMX~=SVg*o(`^YAw-T1CZz!)#pf${<#KM0`o*|Kza#_luy)EGq>J^CD zG(~ljXW-wZGJVNr!O4x`wgiLVy8RVDC>hJYFt7grB318AYJWa1nJKB~w{DQyUMq6{ z0HDD}MS_(#sqw363aaQ z0G6?8>n3N#qhI5%Mtm)c0mXicCiA70qz)jB%9(5L&sd8(@#zx@vrb}fKpGVEvxLb{ z%DdsWBW!^*Qh)iW@`tY{Um!nC_1UYjXP|vjmptG|)P#w?9x+z!UT3e8a)XXm#Oa`; zR=pN6wjTjBmug^iw|3>l%RkK%oH=)A`#(<>S7Kux5#4P z)v325vNKuueOkkRl|LXGkkA3lO5=4yLZhrtMSp(7laXN3_>?%5#?pbDBUQ%? z;#ST9SuDoh9KYU4n_xDNn{z zGF+VUFSQj#k-Mp3qU}J$#^NzsyfG=FE@Iz9+Mx-GB*Ev?Z-*!-$@U%UA#0`W3Ay(M z{-1^iYJUp0$+mF|Gk_>I5B$*0$U}yn-T2YVUVr&hLzZd|RG!`&3o zvO|^GiRsKyGrMnk@sV_o6kfC=HzK}8^8)%XR(~-kuby7RE#V*U2_cE@a~qR?66ZtN zEe~hXWiaKgIwi#Gxm}m?j=mH`UZAg+m)qW%QAKFcOG!7+Vd`2+NLbnK8s7p2}t29Ijt6vh?CN;^X4u`5!K}jkbyG zeO`KL_Z^rg32hS3g(IJj2E2oQQ+!I(E>xNM49@9s_UL#6mzqFd4FTX+Mk8XK1K3_=w~jP%n_F^tfQTi^X(?2 z{5t%LZ5z=Zk1n~ay~HKrj1T#I@@>QTtP$i-O8PF+m<}0GA}@!AGe6~X&-a@D07HOx zFX+oiBg?Ue&fnpMf3!2V$QI$^QzhuTN+Y=?z({zlwk!Rmo<2qXj~Lde=$k72%YV7J zJ|@tinEwDPmIOZmO5Iz~wo+gU=@tv@jlaq@ujg{d$Wwkbenl4)dJ;0(kv81qt-0N3 z+&^kJ@xO>$`i!~14zg>kN$8harf&*Nf@udkP`Df<`A1g|=J8LF&&~LzPZEQOHg{FS zFIzVwFwew7w#Qc=FBVT0Ir%u*Q-8@wZq7ovMv(`EjlNLTVh)+B8)2^!K-h%-3EG8A z0oP^f6!(r^Ws*V?5?Cm3l4^Zy`Y8UF37Sn}M|nB9s!HT9%Q##wg7e8e@!8^X@z>DL z;=`U7A>RK0NN>^Kv+t&3VC~~f8d8*9s!}DDWoAO#rhC?1-!LnA_k81-`F{=rV0%Zp z{{Zv3&UIz+KRR1?*={HFd)h@?3F8V()ejKST~;QgUlkiy+67eO1@0t_`14$HJ<1E% zlGExqHf@Fl%v3~tH^5kdXw@mo^+~yDR$Z8V;4pe2r#*uKH#uxrw_98bEK zo9Y=c$eU??HS_mJLkAD!#(#XAgJ@NDgw&bj*=z8Pqa6z>7^==zD_ci+jFbqi9}kBp z&6Ah)HDbfE5@#Px&YnTUMd8*txw$NDL@xU!!EIJ6a`ScToB<>~#nzAC?v4*L@?TS> zhlReTJIi1=s!(ZC@=Z9+$y=elMX5b0GCQ{=$w0(>D;tsaw;Z18H{7E-{>%{C1eF_3 z?)au)sBJBc<6o+0+XW*NY|w|p5<42PzLfek=%cm7tx}t4-nd*8w&>T1_6yM$P}LH`O)d=kpy)2b5YUe#h^$v~r?mVc7{Y@v$_>Utr_Cca zR4C%juV9_fOiMo5k+w?f%bthfvS^0GF46~TimwSJd-}|7;^ia4ma}_r_~i?h&AzXdr+-FnSc&S zsOqH(bpq8J%{kcEA|mO`u7x{jH1{~?(&y4%qQznAJrrGe(acKDAs?Pv#uUrywSFOF z%(#s$v#&-xdw;j8@VIQ$nU!$RjD@u;Q{0iMcvo8DQ7z8+soP$UdkK`-P8<5Mv6v#5 z$e%ZJxR1^gJWVYbO}M##xY)Q~5J}VVM&YL>nR}$jhT;6<&ptXeRO+MMO{cy3(wfKs z?$4_6F0A2^h~%zJGNgWybCTAF^35iX6V;t6|k+ z4;;NV#o=|l#jCtud|at3Q&QuBm}hv@&H0cK4~tmLHs=N5taNn0Nji|t6SgvbB2w>- zUx->akQBM~8_^eSvJPSCRyWshrx21yx{gB1oIKF5)jE2CS{j6&ZQ9^_B?lFiIr&#i zCTj@OYJVraM*Az?&~WPm>1)IRxnWu91tOOrm>=0Ft2kB#E>rUB>!n>E@Wpz7nosEw zW?LD0E7ql^8MxWw5h3Co3yKkzcFG4Pvw@G2lzMqg&$h}9#90J;JP*7_8E5%NLF~6= zGW8D52F=TAT^kb#mAK63OX0sqid8oaHb*b(9Df_F_e9O@;$IK&8|tw{+E&T?3oaa} z?#kW~gvM5Z9R5G)mda}xJ)PD4W2Y`w4~xSala#7*e;v>JDNn|jz1%o%He+qRE|_{` zQ>y;}kl%?!nA(_CohFn(uJ&MdvMxC}-=ud-WvZtVk5pbPY7}s<)WrJ~7FYpQ;*Aj- zG=Hz-RDUBd%V((tqtgPxV&kqe)k>3M&V~N~z7svoFO^`UV!0P;N0JE$7lze?RXER+ zE<+e!46M1h@eh@;TY>m7WtJb*of&Y2!yRT`l8=iqr(=J1g|CV(1yN_2O?gyjxBq?}< zdotQxZN+R$m8mvv9jkU}4B>floemx{zqG7$G}#v%R{%ssZjLWAkkD()?H|%{{@M1R zwQ8f5xMT&yBOZG;Mh}RKCbAq(V=^mu3??S0R#jx#TQ0|zn{6q0QaYSRF!BzB!G9Qo z>j`Gb3hrNqJw^uE&CQIs2=5O#)l(K-{7e%(e$*+H%*@6Kzy26xAjNHnHvSm44#G-Y zN96)C%kmH`T6w@s*h?s}AK8S?J(eAm-atBNVCe_Ti-r~ivFj2R$}ODA7nc+S`wF$6(rba)B&$F6GD50>}e6?SHYioFpjh6cAUOCm>Z8t=rS=!brj8*+Js*K+Kk% z$4$4vq*V7EaDvw#vlJen!_34i3$nY_$zYIQaV1a4Ge{s60J%)aHH)=I&q&TnI*g*3|VXLd7l=1Nfycz2yl0S6H+C;#BT1p zE-(0EGJv8Hyuae&?u8Ou_M_5KOb@E!H&<&4m?>>02yM4N{s>x{m)HR_Y3s@ejHk6G z^d6WN3OY=-#o8AQ-^B}JF@NpN-X0O*f(RRp+F~jH7hq3G?+@aJFf-U{^o_}qjmZ|G zm}4<2yW-2H4#3jGH(S#r;f5%|vy6y=aN7h+fF_Vka=$BJWhzv#J%8Q2iXwmn*=B9C)StYr}LZqQRuSp{Gr-Fu*@yZI1^RPl$b@f|=23 zd7d8(8iR^75n15LKZXQuXAroNYi@eM zBG@5qm6ux=F|yWlIe!(ulmKmzD3 zAm2IMhIQ9bThrx0AN89uu%(Ju83zHGnreT3^01A<$)_R;mDxkc1i3Jks&eg z#j_H74;$h$_fT2eWv}3Qz9;``Fw-bw(d=-iUMa@zSy9*hGAWVy~xcDU_(4jf&(V;^HFefq&eF>^68hy0~+OsHOvGq2@pQ zFhI{?OuOIT5Ohv}Ogfk7T|U%6U)=LATpdvb&tYAqcbRa1(jayeF9@7Kf{WzgmxT+% ztW-|OA>&rt%f9HbV`qs;!C!_T+(UP*zh4g#L<(3FkBIzU%t9+vZ@jwI{{Rd^TEO0T zg$Y*3fq(V^#eQkS;)o=0*>Gw%eW-z}RCT-YKrl|)5K`h?Ny0D;4XhSia&z4Y7ZIWR zBX=vt0fSc1?q4C(yTbu6DW_pKH-GTJlo%z^HSrhWg_WoZ>&PKxz%U9`Oz@T2e$*PU zEXGn^>r?#~DmXgog*Uvf4eWZI^_D1We(bT@m5GG3qJi&CSw+2=???ihNh zKwFx-SSf-ITbL5nc!x`3b_s(sG+dx1Ku8@kHN98>TEmfH4TT48OD<> zcPH@_2t<3*>witZ^@+U$5&_2Yx-W86_kSCdt=8x5MHC>VZ@bC~2+9EERDQ_81yC+9 zE`N8!1f-}jm$uu#_@bqP65|3_E!{mZ3gi&vt-M!!P*>QtV>_Fz&#Efp0l3-UlJ!AD zgHZ~>bLqGEqU=iVMp$efABqSnXh!bevM~VbJ^r|}fl&K{agt3uHo*ejCO3byQ2?`^ zCipY_P%K|j;f}9~;KjG?$QI&HA#mJ(IPR0d%%cNo!&7(WQ zzy4^rVZxhpxnk@q(ol`6U#;U6EK>z+ip!V7;epf<6ju9#{aXzj6|oYxK4Zhv5P*rX z%Ox-SKqxT+wJWXUJ|c+3sgx8`y6%`5>=Bm;lGN_$i>8Pqi@&<=D6K&Z5`T@HJm9Jg zjF%7!q)LP*d?RwC3#kDST6**8`=Y^fE;)JlVgU*z&sKVUs9@6l-+ zMX+<+^~7Ag@oHNc2Rc!BVCn_T5ib&~MGa37qH3H`E)a`%OQ78Z%ngre-O4La5t0(` z>Wftaav{LAVno=J-1WQ77A3<5LvfNPD8As-ShkJ$c(jH0FK}a|@_$IWfXoj2Pgs)! z0k~IiuM85XEDXZ$vlShIW->VnW%#1mLQzO55EfCyxf6Or2t8zm!B~RThmz%YGSH$I zF`@LaT)Oz8m^!HbUtS+50J{>BEbtC}Izcc*oT(#bB%NQg69@Sh7~7<6xT+s%^_3$k8dSnH<5T z=iz8O$zwMwlDHPkS}QwMPpBI!*`68nSJ~-OW*K4Qa4j_=Z z{(i0h03%joYJ{b5<_=Z8%OjZxFO+&7C*pGBWw%k|`8@ni-+#*KQj0;rGWeogiTQot zyh~|&G49h2)l}tKO>OA`=(adV4ZT~DoDyt!oM?xpIsA?TCyBd^`{^{6%y3Op9%36Q z(yq*I%HkpE9HWbyDyNyh6mO+V{@9$Jm7+IIepS|9Zy_KdUg*VzmRiGK+sM}v_#(AA z`l?*}mu18ZpMUvA&r_((&UFu<{{WzT{w%~2xRVx2oQ)n-kin-%jrHc5FLVRt*m$uv zM(OcBiAm2lFD%;)2n%e22#EBGCXtZqD(PxR*Q)^|D&x-(hz}H8M*2g#LiHTqwXLR! zRbH7n&D(cc33a+8de6qErnY0w^BdG|mNmvO+w7w5iM_Qxb9XP%2wu zXNZul=i<6CKP6z%Vz)82r={wQ&2NlJlS2DRoMwJb%Y<`u;GMK#ToXUqTAsREcv1U^ ze^hJXT)pwT15B(y&EV3L%jlYHc81U{D0SRh+xTyOrpnt)}0jpF~bZmQPvXuEbhgy|syNL&c zdZSK0GKtNTr=+msDnoSI3&u^{L^coK5A@aq#O&3tVbCMbb~eU&MUzB+;dI(aN|mJ8 zTLbBW%A#siahhG&4Ws@jra~&$#Ey3ifS96>;fmCcKyG1h=8VA5HDSBhJ+joB0xq7I z34cNjUcxPkWxLWP2r-(#>^vD0e-*wMWe7Bp*;Jdx;;Ud>TwF1mHWm$vM%uPVBQSET zc6P1QuD8xHCM_KPR$Ustp|F_;zxlWqKM(JXR5|OIu2lUSW#$Ztp&=WSr-maaUb8_J zHR$T|B33-_55v^~Vd=Dy6)22!&i5dx^2`Pst&1kbjTHA*&k~z;!C9k`qO4WF$?eN+6;RUp-;Vo9ZX@ z%>*TL(XVHYPmq}OlR!SLIDGR&i5#4v#~f}xje9+(gO41mQx=Ps+FQPSw2PigC`-a$ zl9A4Aj{``6fY52qJS;bH5h-ilupg_)H8_}v%C;NARP=CD4$&=c;S%b%y?-DZla@=K z9xtgEH6ww@2%eRdZsJ0Fp6Q_S^>-!pLbeaN37SqinUoBb!!PkbN+8^2 zyASP1ncd`8koZWtp?;A4w0|2ao3e|X$=6`q?e}RX;+ML zbA-GxQfc&c13N2Zd9BDh35@|Y3Er5@o?STFy22S zZXOhA>oxD9x1+|;zK|?bEYpT|mxct;XDZIO!|w)BbT#+tiQnZOtBJ$Qllnm9a}_#Q z$ogl5@NUmOjop$tDSt~DCg&wuQktEZBqvDGH8Wos{^X9^WyQu`gElTRW@|C>pXn3& zSF1aAtK zh_mAR;|6|yEM81jE-qY#*uZ{>s^fxlx0Ss@Bg33=GyZdb*?%2uKZ@6px3G_+e2q5M zW>hn^FX6g4nYo$V>0{txgJj{|h8Wvqz1%*`W0rR`sw@{;Uc+6mZfE#lV*@yTu8Rdf zVY{>95FclWA+f+=R$s9(FR`{+w>y6b!_$IX=N~h%>0-As;va?@zmDILUb>Pk?0!tO zxQD~j0shPv`hS7Uw!~$~^ey6-v?&1G29Mkla!r?k{R`=}X;xpKo^_Edi*zI137^KS z`4^X##ok}g7X?k7!!M`wedo`?uyI! zPyW*Z`j%3Ii~fiD-A+uFLSmaRy+StI{JBOp{8uqNNq-#0`6;7zo%BrOI$X1YRGC?> zE+7I&1e4^cq*Fg92U95T)!>ymM{XZR{Z5MX!;=_psZBA@*0jo#WWLhJM~(bW*!+x{ zco;Hu9Luy%Mm#}*PkGtSa$4K3XjH9FdUr>x;r?p|f7)?5ACAk9!>HGjQZ7oN3ASQG z?G3TkR)5)rmqS}^{4hY0_%|3&;F{dMN}n)N^HQdSBqeY9NVuIB6Qk<4(OvYrR4|St zk!8uklb>fHeWEW{GM(|-;q%rL(9hJShegZceG~Ni^a9zZ(~s!;{iVY$NznGOMVDU7 zI3j{>FFnhh>e*Y*yH5O;IsX8V{C}eUrayK5|9=4ePJ82JKRVl1^gqXY9ozGpHN>vFQQv!TMxob$YMRBPt$RkO7C|= zCXdS#nP#byaUy4?akcntTQ-eJvmKJswrcTqM*?y2&iSHl()+xU9ghm2hjLQ6iw0HV zK!4^jq4^4)AW=wKjwn|d6(ZvPv4wmK&rh?)sNWCmVqi~SGI(&u1?D_ZUwdmV7 zfbk>M=3CxMR2{hN;@uw)?u_`eX{y7qKT*#PODjp44~O<+aPTcRyVXkM9HDHX_9SR^ zXKD2zbT~^+o~SipsOMAqFYSlwI+VFO;RuMO++8UR#?&17chYbDuzetu+zW=vUVoQ# z1h91pPR6<2&6nJ3OjP9p}y>lD0 zfw5{O*2KVpd{8a!SOSWB z9PR2`(hp)3dgDX|GUEah0wr+?qHCNJuKJjFA{H9P(s_4)DxBjpH`qd{Sby4t@yOu` zryKoniirG5v4@f1ITYsHJ+YZQCo<(~s^4y^R2{o?BgE;ia(tn`jKg-SnYnP7*4a+i z^ljNhh7je#)YUFB@^7=M6{T$@`nh)%@=(l-XPv<;173VE_5;Yzq*MeQPI8M!#VMb^i~ z0hc^0jNW0Xz^a@VGWjGZF}IXogGFxlEgZwW?v(T^GYMS*ZKwl0&D z@Lnbds#%$RsO(`~ns{|ZRyM^C8A-~rC{70t%Z@fgF~8PWT7ty8Zjq%hNKbJHr%WA0 zzcPwfWVY=sJb&<@xCBH{Ga5OctY>29gYh5sj&M;pf)^gHdX zGOacgSv^Dqe`XTp*VAsJU*e2t%QD1wTqB%k8=F5#rLhh!T7^x*y6z>m84R#VVoQ|^UuqB427iiqF$Jz7e-FAPgVMmiuCCQT zi>f7ot&1iJsdmZn%#Vlp!EQwSjiwcD%TKvWTVobyI=EA$(tCZ!*2%onPIVIW#|xP4 zBQoVvmvJ-BOiZ{|+>y&@*xWOs1e`X{J75jACop|@M?RXm~cCfZx0~2G;GjE3I5y~KheV^HinAm-UZumH_ zg|Zbc3v+Upwf-ooeTr7m?ainuZ^idTPk(Y#en*L%lRHYm{us|H5s|yQlmn<149jWB zI-o~|W?4O*Vh}Fo>vG|U4UCgz!m2z))d4Z0OqdyR%O&M(11`Xvx;@zomA@1aBz|5V>Kyj9uaTP*o+YHJp{#D6)1Vr=<5`r&mws3_?PTNuAq8{(mTS zO0WkTM#I`Jm=^aV4cuon%y@<>5L`N_GH)=>ENe!@?uR^-VN%7!XhAc((Fg3sm$@=5 z+#2wtKyG_>OU!iA7AGs75mC81MgmZgmqs@|UlcVv62@C?3grI)3|)dm3d=~kuNR6I z{llr@8@a_;AxhehK3z~F1bDb}ihrKKsG>m~6a6e6GUu_98?{a=a=IB{svVO{@-K8G zpv{%ZlKr2$3dO>N5klcka3qxsB0_yJaHT}(a)rm2d>B{XkWp`3)&f1klozLbN&!%f z5eRAG_+sh~pyzr_#_RKlqKWYV<K|qaN*fsqXMd+re<&!Oj2lfA ziN(qvaycIJh~GQ%irkQ3^Ooy%-p{iXKn=Jqo_ugo0I(T_;Lq9W7&L;IX}nF=YtQ^p zWdfLYxqi-(3#bQXEj-k|E~|zhVB479YLN&mW-Yqz7=#ygVUm}h#Smbjn9TBDvQYq- z(6V;r(jYac2PQ2&d4F`o8;O88?ca;`q7jsA^UurXy83k_@P7;NS6Vt=gh2n#+Nd%LMj%)t_x_=2h&{Y^aFI<`3gY@j+B-w#M!_g?EUEpo^Zv8HW_LSYoKA8caAT&K(jQcLL*rS`Q+EC@U^p z^_|c*Eu48PDos})##Dhiu0Gc3{8NAOPN$>kH4H4V}&4}|IeVB~J4IGsxT;cx!;)q;#0Q(!u zv*&jdLh3Yeo;6;WD*_0avredGf>Yg(J$uFlm>@1o+ir0cid7puFP^>L7+`dfu`_5u z)FUL$M}NdOM3@GWd4_EqzZ4573ZhMv=1^25_7V-;nhZ*oAeqc#D@it^KILA!woJYK~1uAL5MOK z@i(7Q7uoQ{54nPCIDpkmIWNbbO5G5ZQ7bKHIEA%-AdQt1$vj@nLpuQ7@!YWij>oOK z+qj@C6-)@^HpH<)UT}uwNRXq_1QXWl?G3RQDeft<-yTX@X~Psr?htMrd?l7q3sWNI z_J8gpf~hJMw=T6iiUgfv`PfQ>d2Q-Y>o( z5sZ`99&rk=5;bV`AH@(e1gKi47bqJQ2nk#h(IAF_Fa9VRixBRbU8E3EAk0X0Sb+ky zM%0Kw4sh~_KrRxvc_;W{6}S>jh;tZ#9v?oDf?veEZlCKogb};D z#l;0;3AccAAF~BR1(F!g{qxWHAD^NIEY+Y6g1{a3Ums z`KYK+`sE6g;bN{5*!^>1UpJY2X!T4^K zE?Amh>*=P(>)&qT6@b11)h^v#U-LG!&eO0>CO}%H(%36{K&3!LOXjgtmjTjA#o|T1 zwTEjH7TdXNq(pi~GB#qh1oameZ?!_<7;@w+u5HsC6s{W(-f%cq6n}Mp8kcJrd7s%F zN4Fe)s!!fZt{HwP$nX1&3NuR6&4x-`FcRcc8O&A* z#kk9+7S1xEsrBr=SU6c14Zau#b_R-Cu-+Z*Tzp(zB0VvaGlkgNSO;P?JY;Qzw3*w} zad5^*8FOJ(Js6q8;D5}s#3eF9gn4lYr<5uYq|WZe=Vbc_l9sGWic7R!wF=_PiU~0N zgyKt`aYMnZ$;&n^wibw`qi;$^?0D7=kEXHWOv}*sdRqj@MPQCnE#mI+MQLD{Hg3-- zX(xu|?&@%EhS*JPR`$zPYDRKKNB#C?_rN}h^jOyIud6AV3l?` zrcpqdbBDx{Lh`{z+SMQis>t;SgaoP0ha09a`oy5SC=*@=CH z*lPY6HxEvt;eXxeTE$0l+8RewTN;@e4f-|$Hr;K=Brq+RM2}si846n$Hqo+5 z!8md40@3k)u^@d$fT9?uRj|D=gSDB>-xb|lP;r@tqkl9Ji~j(I7^5`<;mhLX4DazA zxmXP5A($I%dhcrQoD5+L)iB;Nn3pkBE-g4<{Ur`F4(0ZtHpz|781fzxg*O{ztL%Et z*(X>rL6~&&IJS7~ zBwk;X6mgVG@{couaXusqPkJCZc$cXl;ubtiPk+n^_oH21>z*5XHkUh}t!JjSaMqOG&!Xl%$i}w%*Kb9~a3YdMy1W zl*Y9;H=%#gZ;8Du>U7*ij|O6kgBvt`ihpyNw=-2vcfH=&d%3E4N3Hx%;IzK3Qx7wo z@BFME>UUn<{U~;I=ozuXrP7>`k!qgC;09b#t5Fj`Z4m z9?j$_iaD|*el8vu>EYoj`2%+NmMJQ1ypD~sEsxoaF@<2yHemPpqylV$E8cD&s}eLBd%^nqbPcV^F3A>U+bP@t2SND0BtH_OY5B*N<$n)n!PSW9;ryNq zpW68!HoY%Cg$dCKiTVik!VyxqMH_T}VaZ_}PtS8WZCvZpH!tIHaypfdoZD*jXT+Nm zsy!`Mr_3@fmA6{9{r*w!e;fI{9KU}SI6S|{Wx?d8yD3X~jCNJdc<&3>XE42Cnb{YL zUS?d7fx+P@IB=D;eMb5$zLXl?2d_xF&jUpIsLXZ3$E^)LSb zJD48{@_*@2qSi2fr5wc{5Xieron}l+jkOVdGbB<4@5x3MoR3q;2VESaRKx);q}zNy z;e&ko8n|{-?gO1*lo6-d-VBIK{4?XdB+Ev$JbJwE;enO z_JdSA+aWF;BbLvja{HY*=-0K4@wY1Dog;0f8?d^{^oq=kbSG>HrbjL-hVN<(l*1^{ zYptwo>7ArdW~C+@5^B}5;o;6RWyf;veAe2g4@?7%>J;gTb7f`SQ4)VHl1}AMbTf`S z8}G%40;bCpu$yy0PX7SACe(PL!!5C?Nvlr@jgDQ#!vevny1f%H+@?LplC)Z!m`YVOsab!TZxDz#ZNQz;yEh$U#L4=Gk0YKiTs!TKEi zDL>hs)1JaLDUIBPXpou{E)jc{N%MxISlxl{wPWiTTV`I=XLaco-?Xl6DU^DGOK%90 zDMlRr0V$&JF5G`9Sm)9;mYb)|6ow68rRJ~VY1xD1UzB>U;ruG=V}<#rTY6sSWO_Jb z6v8z?sHE_>AmOPDxN%%d&ou7Pqt#^N`k1{YJhm$)Tl`DMq=wpP`%S^cOr|)~hTcxN zhCE*MNaC;J^5br!Zzq}RElw@k^N9EUCr5B8Gn7(CKsA3Y{iZjk-jUG5Eg5QL+SjDt zPYKZGsI@j+wnr35jK>J@iK=Uyc%R~r#`<||y(hB#FFB=am5EwYjHNh$1$6moB;i_X&Jsjnk~GMmu?BO86m_r{D-)F(4+ta>A21Eu4JWl*>Xl#AIF zj{g7=wedK-#oHf7Hg7x=*lbsM>lrSe5hn~a+`|xbCdP4Z_Kyv=$(7DZ@O(s()WY_} zY_8P0vNr1t5)$jQNQ4h{V!m!abo(N5Ux^ zKLwYil;vhxWnkMG#cxy2@NmTo`Ny;3OG?z@&OcKrxMVi*J4FoAVC^3aX|B{>ZYQVI zF_-k9-d8(A^A5RbTkQ-Nq0(`8mU0(?^pwNZiDGg2;gt~{Aunn<&&1b>>v*4@Y*;^< zz9)YhdU$$QN#cobsA(??j9+oP(prB=!q{5JmgMqt{!fy*SEWvCpyMKyBFMOkIyn80 zMu<#JZBgcBO4l{}QKmA1=^E=CZGp+Dn?=n_;)LYQ^%XdJtP^JYLU^iKxLd@!6^|oH zF>$okI(uY0KxLLk)(GA0aFlOTL*+>}guZ{&85T1~u0i&=p{gJ18VU~7w!khK0J+nj zPYh+u>S1?hYZ`MGZ87POR3*w3-s8KKVXhe-WLJ2K7CBIM-DyizBhCuNvuIM*Gv-$( zh6I*b2IXl6;X?lanjt@=5mv3YKBDURLJ-Vh0JL@3V=aIh&NUi?x42 z#+zdETCZ;DrI~nrt7Ds)+NW0^v7k1&RQkeXgTdmCJIgnbl@n}h@x!4vaW5APXJk5Q zi%LXRu$jxP$U0keRRXO@5zbZb#rvYtGzIo<-H6r&H)KV{ba!}p40&Mbj2k)HC0iBe zYp zXQ*sQ?Wa0nE`bi2n7c%UmDnJ?e>hvAYwkE@%XI01p$BaA!l*GII637M$`gO6X5Je* zq86ESEb4iUC;PkBTjQD)T1 zJX~LD6zWwNGc5GyecC|kLQcbiWd~k=lnYLV%$aUico{{KBvDI>hH-yYB*EcrLscL9 zMT8`ox2K$3_aGUjZarbD1E>ci?$f5|B9%sya=qW{C^aC}=~T0;LHJ;mh7q%k3w($E z8=&{FH6sF-u`}xB1i<$I<36)Af&dybapi9p_@E_8ZHaN85R5N!9KMVTek*!ng!eMo z+rVLg1HD_HwCh6K}(+JQU3t6TQE9-1R^F@jj0x^_9=`A-Kxc~X+L2^WxM!_ z1Y9vu3vkDHwrEHv>GwK}cpC>AbQl$8`KN5oL4 z>`^B!L?e<92||UrB~5s~=ox`zjI+ez>k&sNL3-x%w?c(5ngVg}GKysoYZ{AC5ztAIWOPFUuq?V%veBy7Ng22Du^ccXLs<7U4mr_$lYzj z#iRog+W7?RqKBVU5OCpNT9SVZ%lD_c=#~b14qImHg-2g% z3LLTIytoI4_Mo>S#W^S9qT$0M9&Y3Je^_FR z8cjLX{{R#+BVe>RA!j46Ye-|%RX?a>?l_01q7g>i;m!)JCfnBVEYCS z^?rKA*h?nQZ>?Ov3^Q_HU`|YnR;qD-!v-964PJWT&3H@t!wFHVgpPb%FjgXoWt+4? z;fsHkBN8%nwQz<4Ttv=`J|ChFA{}(%wi7u;?hB0`hJpi-+EJO@vT90>gJ~zT;)Ptg z*ncli5e}H9${a;UEXvyfabt$%5QC>!Q)LB^&(&9HWCyG%kk8a1-aJs3=Sf?ah~M+FzQ_J)W!Ef|-4_`&RYr3XJIlC!%s&?( z1|sKa3P+v38@OTqus18y)U{#aR3G7uE1P3cl!}^_Awu@Ghs4+xai#orsQ&=o856Ty zNQa8<9~IG723-cT1c!2^xtEFvTgcVn-JW)b!^H|`Ek?whUVjfv9f6qG2OGD;Z`pr> zxo9WLF!iojeg_Nx*4NDj~&RXV@L65&&3J2>#=3@ub_`z6lZ2RNwrBz6K# zk#NMVNUS5Wi1YD9SSVp7FPZ-U%@=BsgcP`k+KY+Vb_Q&ZbX2G!H+G(V(N-XvP)8`c zgD^ChmmKm=oqr4*NHH}V_FK4D#gu{{RrhB8_28wRXKJcMZ^WGrw`eISz@%0Lnz}6jcXQABTo4 z(8e5ZU-iWnq8q3yEZwPfTWlFrkmANnkB5{%;x=W*{5{{b5M*~et)ToV15kg=6L)F* zKV~IJV%|K9!vrh?){i3d@bN?z<5x0p2R>fR4M>m)!ME_i1kIs4%eYsEh6bY8PlF_| zlwg8~17*L9`9OCsxVupoB@?gh6R|G3ONT)wG4^aBE*Eh_Q1;n)@2Bj zTcRl4z(!o-72+$R>Q#dSWJiCJh;Wn%EM>y)QH!XibHj0JJR|&2EQE>Bma=l#v5fi< zp}X;E15%=PVJ>v@Uk`=|V1gTULV~chQ6ePik}plsEEK@27G3O?D!}T1$gR>STfW7% zUPGaO3_@ZP!(jcMC^(&sp5jPQv`7{ySW19w_7OX=G*x9dFObG#TS)43} z2z!JQ6beN0NU*0?0^7xoMA-LMv#I zl|C{g8@->3AR;PQR-K5lNvT$dyd~m{&%|Gew+|p;OxT?gMtzs}IB)Xj3?XT2@%NSeFEB1Hvef>>L%x;4hiaZ0ptHL^m;V=AZmAu;)YFjdSTi zM|!D(Sy^_?;un8-?drBNXH%hv*ydTuCuN?n#@l_UTCt%ER?9X=QQAWjPRq7VH!qdM zJ`#-i^R6+`j^|eFCuJjqRJc;Iq*Eh889n>Ko$qvG(~F_AK~j;RGW zCNF!VPAJ(Fc}J@gW+ZC zjN`J)8{wI4-!2%^%5~H~XZ@&6fp2k@ZNp|x!RvUC1byqIOA2^5H71+b>9XxDoFIc5 zAw)IJ8?~yzRx#UzJRvG*4e>@C*>uIlzC@EGg)DzOLDLQHYDEx2EB2rm(4s_cfUwEz zB>FP$irkh9c9v$NbWp$!G?o&ff6Wyv5-82V3ocTDuxy(dP1Pmk!%`c5)C0>Fs4{0C zQ{>gbCxb4osHS}ZDYI2=%WV|8e9XJR@RAUymo62I@1I{2natzwsMS?}N=ZpOw|k<6 zwo`u&DBn#=wPVuAisfy<5w;@BNNtF5fjuRTVqwXro@kZ6fi5V`%51P};$vTtH&*nr zc!9)6jFaIQ$LTzs7^$r`fwdvUR~TH&zSp&!I|?)u_|i(nA8gf(6TSp+ie17&;moz= z8s`e-5gi3j9&JeR1o=#h#k?-D>DDyy`0RgzvNg)1xXIBB&BFXe8Yc)1QG~`WZu^{= z588(=7W$PbuaL@b97{48E8RZN6ftlxiJgtV+C07ePQw?B6tMD(jT);|8(>F`!r{B7 z0(jaA9I}+2T3jT(S4KHgVxtRLaW4>(`%pT9GJ|c95aNi+F4Y`U$(E)>oU??kFA0A| zOct(O==7XDQf;(#le@*m#TZ}Jj}uZdiK*e59IW{QZ+8We4;9f7o8%5}V!?*?e}!s5 zD03Ws$r21*+6+?7Cc?uz9m0=p?PMJGtt2Q_m`lO1aF)sN;QF?PU9$GNNTHh8g5OTr z?JiL*X||l=;kParsw|h>d^jD48OeWJ9xn_Upf@4$Cc!Y~C}$~}Jh>mVJSF+VJtg@K zM`$QGgV2hLP&?#iCuG-NAJz_VO06Ft=akS;u@|9@a++n><|XGX@DQjM^?~(tT60_E zQ>tE$c)prshe?<#eU2f5oSHR$vk@9Q55(^tLq(q+D}Pp_1sRl|tXM1lIUOG(OrILl8PEtX;;FbR#M z(HNCHja`Ir-Zk`$#N$uHH2Ht}w9Ch7IwGReUu^j#b@*S4%ayuRL-JD@-M)suN3IX_ zd7PKTdqt=*R>Y0udxGL(MS1$L?>~C}==OX+#9-rfXVr{rlP-4gs+%X$)9G5F?2idK zNudY~!3v8~tP&Brz1OdDMm~S#emIV|7xB2U^*8z-H1XCV#h#Sdi*R>HqSOL#DfjC#HfwLg=maV;1u z>RchTaIA<>lF9- z$8R4y7GLc@_BnaK9rZ1kuVVN=ZPb#Srj)$AGR*U>j#*yA&m zye{9?)CO}ib+3Q!lkVjnkMLi`xBgcrcK-7Re(f>aN8P{6k5*Ppzj}}f9Wo&?0*@_6^t~S*Q`vu0ke_QIepT#?^9Aok{!x>k z`2PS)`MA22$CDPCtb7pkwDe`ho`@S|oWU68n(a2K!KzBksHm|~Z#dG!w(Nh8A31>WguK`|V%_L2i1?9VPM7Im@jVXZz__WJ^oF#_T1tUONZRCpBw4ALpxJr_}QFQL1ya+tVrm?ZA`h7WwL5mNs>!K+hAwi zMXAZYpIZZO5Mi>e$g}!fpIxJI5Jp;1iuyQd;aPuowYZ_{muBVnGAK=kW6w)qyN`~< z_5#O~_%{+s&PzN(a_))saF(##ZK1eNu^xmwYnJqs*_9jt%crHtSeSEmrMgb@ROhES z(+P68FuKVqm4x(pB~YEfePovb*KU2#jgNxyY{$eg1-6y(46C~t{i+w;2!!n-@!=&lx#+|1GD2J(Hx8JAa* zAT%Iy7CekF>GO(nGGyJD;g?hvZXNs)GdXD?bK z!brNovS`h_8sBUCe#09JpgBE=YE4RmV{m`4JWspwPHO`!uxQev^>m*;eKLNS>|s42 zH6gQVNYdr4Q5uxs-7;Sg2PFBdSE{CWL`AYl>Y$sc|m%s zI>;2IT4}z|V~AQU$Y7|Pv%5Iuapk>xv7!-U6{>Gdq2W5N&h|frWM|4%=A3_#YHpnC zM7zd;P*p*5_ZM_y{H9gq+Zr7{Eal_Xn?!CCtT^Ge*;#9m@{Gypvnlj^Pkc&Rq~&F- zCy;$(Wy8pXpcNDvu;yJFeop0Yi`gESC4~blXn~%UYN%u&HzZR1MI52&_Bou) zTRx5KHx^7(X-%{oMnP!_4-7jgjUvZGY?f*4JPq$#Xi&QMeK77D=<8$SYZp&1i%g4h{5v;vp z&=f-WN;ILRkY`-PAESQ~UH<^ODcC4OUREm#^yZ~^7m71_t_=%b&t?_7RZELc6f+4( z-&1g4+O)v1D$!^?KMZV%V>VPSt>bCBY_>~jUE;Z7-N{m!iLsND21ITVTkS&aV&fT7 zdbDpoS(i_S0^^&GExMA4=sqZ|$jWGPt_zQ?|>66gMw z0$hl@5L2ai<-}J0)I^`KY`PrC)YkF#eV8_Kwhy$0skIa7)bt^55G;w5hTiiXQvWC&tQEj zix-f*JYG-^I9X@i8QDl=ToOHEb^;j4xo(LHXxMia(RqJ<*Ru(fE+<2l-1uR0+<0Mn z)y*M5#0G8axpMyi6a=74>^YZwOOy_vxnc0QHfMA#i>RjFy4YOC!5j?0r^*IYD+^bU zxxnfRzK1Qoo3Taei9LnG?DL(_1&y8?jJ#jrhbaJ)cHF)wIE|LWDTeoVe}*(93T7Bg zu%pg2Dwls9#xDiQui=K{23%(}-yg(78X^qukY@CPbB8PF7D1fjHtG9N<#MC?h{=Zg z<4Q?Nwg4^h=s)v8%#2&WB4^BPfs@ zE4Y7#4OCmM$oFOKJkz9N6u!V*Z!^feKFk`9P)6QvW~0RvtDtn!Bw10{#T7o_vI-XR zJt1=y1q=kZu(_vKOiAuO5Xm=j=K`c@(5=!5F34>RBGbiv%6!Qa7@U5a5nE0Xnn zm=J=YYoAOL15wjlvra3rfiF-LY`-u5D20Y@=Q_B2P<0@2QRKE={4lBzQ5ObD;e(VQ zONG90N)3awNB~HK#S&vvM)+~S2VP&IpZa*tF>>|-rP z#8)1%E1(ia?-E~<4Y39?fVDHq?QVgwO4SzPmp-dv*Qf^&-|sKy7C~alkZ}w_))m=% zlzL(U{@|UEwKV;ri&q#W))YfE9Xx+A7*j1lZg?~5g*Y}8jEjo}QZG@FZru_tV`bD^ zE$=j5a5BVD?TyEp@j)lJ#vjU$atu?bF&Z8+&8M$8xSfm~3p<-tJW*vk4#mCSIJEaE zPdI{B_`l8@5CkaBVqbE3DDitx%MPg0V6C|mq`G`CYE@j0oO0W`<3f;%o_c@XJH41N zlerNz_Fq5YhR1N~M?B&R+jsq-z<%Ph+_T`jtAwE9c10S^PIp~z3|T5La&VgtE>Jze zZ(*6ub!U7^mt!M2c8XVB5}}H1+kQ|)GDMJoyf1eC|c z!vZY@GUiK{$|0sZizo99d@+9lp2J6h9doOOE+b-?LK5GSL3S9X3!^pR_+SDMw+NwtSQZ3N5UzJ!@WFo-9l*TyR;9c{ z{upv8Q5G9LG$Ni0eW*HxP!CKi$;0AQDG)%&i6&O{aYI)i?tmvx7d$b3sExqhVv?kUsl&qo`hqf$ z)e0IO^M>M$76OL8}c<+%$hO)qdC7gHGXk zV8qaq;_3KdZlNAUW3de|7cd1TzHrRX4=E5#vzcQCw4DuZ!A%#o1EGM4Azx&jy_jR651^&4TNl@=hEkFyq} zTLv#yy73T?KCURW2Pxv{61F8uRx`z5<<%DvpjbO3fTMqS+*d`FV6C~XMaZHKg%mDi zcH5_fz0m`y<0L&iF#tgpqn78yqYx)xV|i)#zUYEis1Xg^`eFq!phU_z87KNkfmWa% zZr*%R3SqGdlIBAYNJJqJwR)gCg-HhI7OT|}Mng0KBHf3z7Q-2|V|Z8P6u_$mW#Qzv z{g}21hYf%62Tjo`MGD=eZd~ArQG8zhs0IRx9R*RHdBp?RTTEPe_@Jy3L7Q#}AnGF9 zT*)9R4OA1Z5nPvBcMlW`C`!5#B&HcC>M>z&Vhj*N9fXqT=nVh{l> z+`3{ANf!oo{4oe5giD`C5p%391Olx!Vw-Dr@aq3?PtjC2E~VLkfP(TaXSn!1Ng~mt=3!(X~H(VADUD4daFI*JWT#xRj$#Br0b`XKv1m zk4n=+qxGad^yL)MqcY`j4pGaYJ8YPfID9go7Z&!>r6}zxGSZD5hp&&3`rN9a8N% zq{HQK@eRr&7ghn)NGwgYC$U7Gh3!E4S~F!RRmq9Q-X%v2hpOn-l`wxw73qq@qFEcC zhUj%RRS8@(v}tNM6K}a_`%#}K6|x75D!}G@Z4xr!lVnbQ7_|IrNY>7zEau#d#D%GF z`H^(SnEYys{aF<~wknYZgwuBnHF!9nOY#~@#P%whoH8PFtJ#fliDSfDHpuCUgU)q0 zWS?paN(e3DxSH`rkjQ^FSLfo0L1NxXmL!U_6>iSAr(elx8dS|)CBTF z&taMQMqF@PbZz3g3KXe}6^d@#pQX(Zw>ax0CDU@o#o?JHOH~KbYJNx6jX(&CE3+sD z<1tl2?}mLaCuz=cJRDyVkiut<&{HCpyg%uvvZg5DwJ~=O5nH4WShhy^m&`xubxE7j zX_If_65)ZffkuD%7Mee#HW#dbs?;|>2@n0DoyqC!u+sL4hw7HwP?F;6xx^pm0V$w; z1|{04Qzn@g24R8{_FPD{7RU;Ey-k;AC#M<^_}oqy4M8a69dB(oR}FzFOf>O(AV@?| zGu&h)N){;e+Na3+x@?<5qASMl5XH5iZ=`A*Y3XlN#ZP})%r|GT5VbfaEt2+A+$6 zyBi%e)l+KWoEpV7krta9y{OulG!m_JIzb zC|bE}nh+*V82nLc!Jz71SytsvABG7W7acQjhFU?h73mJ;Z5K1@f<^#z_C?Etp_3xT z%MS4EPs14G)v`!&wVNyTg}MDti%BJ5v`Jr%Y9d$`oe_v&;N3 zEJ%Mq&Cd^tD{@^zcI!`0P-5y8na(`o;f6$x<>%GDa5n-}l%?WlWREl=hemA!5wcRf z=}^YS=ra{biicx1K}bt^Y-pd%ew{*&8oBb1)1&De##?a5^AA$$6H+vBb#0irMCMmM ztk=3ZxVX*v2DWj@$X+7<04Yr^v|mlEab17eul7kX6wydb59vZYJM{KdW72**_-6(t z^=fkY{{X}-m1?GkalmfnKB)Cblswh8N6l44?m^-BVlz|-LA;%L>U`k5)0UfLgeB~( zE!c8HTcqvZqu=Q+u56DM$e~mUT4|F@O63uK5;`0|$a+!fPg0v17b5=S>#o$K_DO&A z&-yhM9mO4jla1wAx|dKP4eYedg<%4(c75OHlwT+D>i+=E<8?3nj&CE4mmiV&-ugg2 z4fc!ZXST`ADYOYWj?pe<*r!J_m8dhGhSKVsxsP|tC0%T>XFj~jk2s*glX)+c z5|9ya^YKJt3KHGx1Vf{J5q7YyGxdKo!<#+2 z+`XA}mx?Ss)Zbo*{+W2I(YCN(P)%;aXjB~n=_LMe+*#)+fUF z4o&I_Ix{nexH-ejg@{gE^lqabX4m#d7h{c`)b@u`Df;B})lzh`^{?qlxq%}7?BlEaE6GfM%3}**-d>}jb`R++%@Y>3+`EN1%fg^d2;3x{ z`KOe90}nG5YQ}C}FG+e$!-w{%AkNWSZeDA5P!d9y-SHO}lwg0#%Kj$_-xaLN7t-QZ zt5Mme7i=6iDzypwjfRTrR9qC)JG9m_sz2Z*{&7s@u%q$uGkc^iMN>31aUYp;rZjx&`gSlbbbLRFLxjY)M`-)u z7-pQ6W0(1K-Yb1h7Qywc%z&rEY<&kC^qkH#-F-qoXjrJ5r8??Q9A`w(v>1ZFAcxN7c*fLHXZ?~lVB=^)G-1@ z>L)8z8<`|h(lO*ypv%%}eW2AB>mtjmra1XZ!qh6?+5>6fRq*iL3Ef9B?3$6PE7H0D z02F^X4yf@HFv3h8zSI(y*DSaNzROZ=6NchQ+r|-;>CKwEbc1A`DD<2*={Xso*ix=s z7cVPgqRMkJxtJP&tRRxkaaD5aB#VHSDuG3QMiYxvij^tlK^Hue#S56&C$CJhSd|=^ z1eTij#9@kOi!oj+p*oNm42y)nW&(v!$QOTf1!`L2GkRs5AZvRHZSC3R;fkMdRkj_s z70Y}>33M8bpn&2eEs!{n&AE1-C>c^%aLe863x4Eb^KV`QoGFV&&~b3G)VN?v799f% zAWP~sxD(gCzFpZKZYKPN`P^jgEi-@KXM%aIN0$euzJ|HU#S^5-t_+fhBPO!mZW=< z_Qr!ku7Er`EcJgBT9vJc-R`+_gK61nwKHjD?}`Fs@UwB};3yVtjP5V9 z`FLSguu&B;+0c4;grH_NLjy0Cs|$bB5J@-GgT;1#hALo!86Swhyq8WQ6#+ z7x2ZGxe(!7rMYnM!9rN3VGemLR82~=0qW4dY96dwv4<$7i{6QvU#!0ho$q zxpZxFUT_ zZsT^nsO5L?!AufV#k<3`Icsmj5lS-4cj&ytp{haCc9>+HBLGsU<(9LzKY2uE8$&!K zdPF4!-nIy#e+)rl#Z-RR!v>@rx6fSe(jx;?EuFve#fMX#B!pbn>#cu}n7bXz6Y@iI zW06`=h@FAlm6alfcL)?Boe@avqpS(+)VTnJRFI@WpBcncfiw zU+XwqgHzUzE-d|=5WHGz)zSw`el&P@Ij|ak0b`HN`!ndcj3)_w8GVH&c8UZW_m=d`Ylw7$} zkRNB==$wiu754_@zaE?_F)y-E6K$#)(#jUNZ|K>q=VIpj>CU>!n;du_@eg%NcoRf z;eFl6hc(VC#)o1m?k2t z11B;8Uu%c#z=VGYTno6W*kj2t z-x#9!x?xunGFa(MqVpg9v619W%PlUK(+X^$zjD<@q^PzeL*ekocgzW6k(CB@P99t_ zow78Llw^NNEzgMy_+umG84vqO8FAiPFpH6tcP}m@c3m(M<-}}pF3A0;8xg8MmS)u$ z2Vgn{xi8%a><9;>kBZrb4$;r4ttr#+1#ms=6U2vuBX~tA;cqYzc9T?I+?_l>iV5dp<%Nrd%{(P{-wA~@M{z>n<(b?=(-%O`GRhKWu+6j( zTFZY4YM;di6SATzH+Ee__(#J9VC*Hv?hNhXPAH&&lViIq_)DvbAQc)+W`S>p2|$<{ z6PELVEmUmd$T|;9T*iw6WKJesSA3w=h#*LF>EakG2xA~x{wreVv1;21mR~Y}W(icl z-C-eIMFmo!M2*R4sKlraW!6Zvw}+|-hlGET1hX4PA)s458T7AAR^ZOc5LzNElZF~y zLbfWQuT(-Z84e@?t+u6ZMG#^O?0jUS;qdgt30FcpJ)&6*KyNs~LvfK!eNqOD`i zk|*Kt#Re;RcQ4wAUgS){B7ej8#YbRTHa8--NNH>AB%VRhc!Xej%6S6s5&JMg;&vy9 zlU}HSvuMb;gnsYwK(NIYIny{Lb_%3ZB9~WB!xufs0m*eO zdc06JD^NFk)$uGyV#e-q9~XZJ#gGe}S|?0FXM|N3C}OZ`p_?u-$_9ndIEQ-usHpA@ zLoz$vJX%8~i5(-hNN>$xDi*XmWyQWFNIYb%)j8b}MY57o=7YlxS^~? z9J03_rFFlx0TH2$-%!BK*01oxrYZ<_?i11?$56KY#jj?#pm5!6Rnq+#?iW9#bH6FT z{YEd`GmVqe@-|*9k`ktq?btv-r5`qAWXtI>-^cD~`sGpn-bS9)w*Jg@(L!bkCr}yd zk!%%%n9yp$n?z>ZxL|+EY>g&|^rXk%T2>pSGUut6>$jwqI$Tr+dzzt~F16tGe8x1s zdKc*I@a(Q~!ZIKf9w=Apl)p*Kw$FVZR}shyj<#fETsFnFZHl9vbLii&Ht>BOToId3 zOSpUE8&9afvYj!5@W?V_nDGevJUPanPox$uQgpnn;r{^L4N8B&wr-DJ;e*2Cc)mu`am4QFjYmd16^vnw z{;5J_IF1vQ)dc0wpxX%}sqGE7ONhmyFS8l(HrULw)zodRq*O{r22>8~ZE0h3c$yA! zoWu=F+=f6U;*Ng{x+4;n4$jxi2; zqF=rhmoAOzgx0v5(*kl}+}wyW!liK)#RzQs8%=ncyBB&zRVrpz)SVzATdgZ#8TK50 z;+0q0lhaNTsa&I-w-wMJOO@H|di3Gkg>Tu0dP3hO$a;T$?r`|F^ zE!r2e4vsRJe^Ig)a}Yl|<<7ZuXk2RmOW15x0$m~kAf}ah!6^ib(RLw_%(;vK3==)c z5Qnz}Nf3X58ykEv2oL~6RER?x(f7#2kZhV0>iaqQM}c9(x!cCb`Wu_`vGVU%UDL3l_ShqJfp3TrciiEAAc28^*0DiQcAF@z~;wujI ziTQsWxjHtgOnk0{KM1?2KTF%g(r-3uO-u-vNf!x1zel1o=&()tPfyh!6>Pg6>}?TY z#Sw-6k3?3m#m%x+>Zktzm^X;JNZeA2ZZT_zWNg+rs!kZo?;Rp=tpIC?iG9eC6N~jw zUMO$RNAIJDXghV>)N0@TOwe4&iehtx(ocVIE?>Sod~d_$o~G$Bzboo={twuW!95J+ zhGJ;Hwiu3FZdc|qA!z;G$dT4gCl`;@!qGG3HJ79Z((UzgGZGX=+L2^^STQNUB0lv< zR&U8P^y29@CkqmJ9Cx&DOAJf3IhUWDoPCyFCAFb(c%u_DPm=@KqtWpE{w=5pT5f-D zi9A32D(cD=Z9z6{<3O3n`0qPsIYxAVXxN zc_8q@-NUe9Z+K$8F>1lx2Mvx~tp5P4GkX_**l@~S{QeB za^c~Fs1>n0h3M$}!r}7xqYhcoeaoz@#i5dTV>sx5y{7#W#86WM6f1KU+KUpUO9yT4 zrBaDfONDLa?uD>BfUV3$DO~}^V%TP+l{4D7FIZ6!BQ*sx6kilwi=x}CuI-P|)@yBl z6)Ib}gsM`s7qWaC0ZAVq=tx`n8|hL9=$u91|jDrc=PdF;(>Q1NarRY zHW^SNDodOIOYAk6#D`CKcLi&$@mxNC7=cbxp}i8mkSs~E`t*x#)TSFYO3-%hq+d9G z9!p^AU-UNNkUM!2SFk7bY;8fuD>x?2Y) ztHq2Qjvk)MeAxO$K7~Ag=wZIZlq<2;&EDget;i1b*Qe7E;R?1$Uu!7G9?Ul|Q%**@3%(9c-t@g-rph_il^ z>FzkjA?XE$NurUJlc$j1DhzawmVC8}<|@yKpGEvEXvuwz)T=Y)D=$xW@!>NewXt8H z`9=<>Fg7NS64Qpg4(M`!uMFLMTB|%c5XJ-mdvDM68`JB4v1twKOEVQrKSA}glYzoBEt(C8et$Sp^4kV`H;I;0)OA+sDO*g% z)IUp%Z7Te<4dn3dFD^>cq3f;&J zzRqc+e1Gzu+@IWiAB6YBbUhBeD>We76EdN_5MD6K;_}WF3bI~B(DbK)xjV>iQQ_i> ztwplzug!_P@gj(EaEOS-f@z( z#IQ)z8lGn6+`8D)46(N2Ut=-3eChgyMILh;^s&o1AzB3;wsWaV@ zOhq{sUxGe^iH=7H`!c_@YL(Is4ct+t+E95u{U)ZbRTNp`9eTbfMp@E~!k-ga<0`S( zLZzW9ko+kanYKp9va-y_87h7li8UOFprtgU&;2YqC_O7Cc) z62ycVvrs~FUMMAC=k|(PlDDi~g(`|}rdDL7Z^aC;bWrS1CRyG4S#d(_IYkXoEb2I| z$hL6~RIWU(gSa;c-9sle=ln1+CYbJDunkLp)Nqj9r-~yuxbiJCk9LM!&#KtVnY3&~ z*`{&yxwlfkrZ~>Q(1CiKB%)$nP_`V3OwnafM%3ydwhc(jvDUClaSbJ%Nt zGKeCyKNLnnOlZS}{7@{Us1hr_%qUTWb3c?*D=5H3;dAMUGoZa)7$=Bzx)-Pp9Kg+Q zAax;%H;Vk=Dlqo;-f%F0u&eGqt{7XO6bv&PwaE$=Ho7w0(ztoA3dJ!4%e`^JUI#=D z8<@LDLjvR0<_RDf0kC#wzAhLF7OcU4`(3=EwE=ESjQ4VI#Z;??ZerusD^g3226N1! zYD(-7DhRPPB)B)H1dNs8KcZvc(d1ghDZNF*=wy>cRE1)$XEVP~(UiI+8i0CL=Bm&Rf5H)>y2q}9HG=O;w z%Wc^J#606_Bbsq|U@9Cjfp3B&p&ZWU%TaiF!d(^xSa&ZkkV1p7Vc{{(Dx)L0!K7GPu-Vp2hr(J!0)kwB5+uzyRv1_V8+WJlgOywJUML8il#0p$>3ekJ8+LI z7&6(mA<1(cQ=f(hpc5>LdbMBvD80a>orV!XII+>}7+lTFk|B4IOy<0}VgeX5%zdbY z2*XLjy;pF-b&ZOJPQ28AZh{vq6RTbyYAR!4oa0R*%yW;KG1z7&BxWu#L@p8bpeO~2 ziFuBcU$gANs_459*?AV_UM?=LY9kcG)?5+{(5|>+JE?XEdGPx%AUteXj`L6bu(nZj z6|P!weW+O>L=$%N;aGsbxZK*bGfo?e!w`yKip> zB_)Vyr>;EXQEElhCIo{&!Z5^u;xZtsv#wY z?l?}KYY>YY0^xUE)etpNEx0%F7Y~Qph!0ps+$-`}feP&#&Rky1Tt>^NL3mrW6ZpJv z)?JZB@2>kjKWYM_!OReV5`V0q0;n~}gFhGah)RW*5%#W#2yt@L$slSml+N&P$qFi_ zcRhC=Tm=+=EHiFG$T?zT$iIp+xdXQL@ceK!1%nA&%ke;pXj2>RF;5gQFo`rmnGYxx zAfC};@c3f2CBlh$?lM&u!(X@^G+ogDUE};B`N$}1H~EM1hN*& z44+D@X3HUTmpUAXk&5)DoFy0^C=8{Rn=iXPBO_&h0TV>Dt7_w!tR`~?Ka+30Aq@K= zBQDss18Bx~Vpn6va^kdlVOB1+982&XR{sF;L0kqTCELyh#Z)75{qVL8af=yyf+`eh zri?jFxpW)85rAhDH*%VS8V#{7UvZn%ob|RZ)G6vUDtZp7$^zn}u+Cycd*GWfWPIeF z@52Ruk2lzaElNcla_e-BO5`FZq)>E+qmX9gsvefXdd1j+tWrR(;qfTOjC_GLu~4s4 zcPd1K`!UtSm|UYlRj4*?nJSEJSJ|;6YBF-(6i>AbiP3d)-CZzH!RvlPfJx04hvMps z6!G;iLV{wDQ3DZA2#?%QbL?q}*y)x>l0s#FfVf*zl9vyP-vk!qU^SNApZeg7i69A# z_l21eLt=IV4IC0Pv3d1Hs|GTdQ!Rc`1y5o&Y0FRD3_`|FmA0oJ!w1}e#p2@6oK;F@ zGTg<-uZkqWnNY~Rz#>0t?vm07DheFM%>AssC>oAXt~>lNDxnEh#E;?d!wj!1D$f6Z z0KOqr5F_Ws5Yed&&k^R41rFJ4ZPSbV;f1eARAC4*U-YpGc`_{cVgcz232_z8A`D)U zWZq|y58;SUQOp~5hr7cNw_q;`S(DugFulFE56JF6)ptKisH83C^F)9 zH*u2Yyio|sH*hmTPii51kVxMKLl8xOPc%i#y5F;8LCT_>E#PO$*nFt%+&9v&-Tszn_$cL$sZL#=Eqh9ObdSqRymvkpvkW2P zw1YJ+8)6yd2JQ{of9{A}M!~(}73UCUq(OC!+)+~-Etg_k8&rJ$%vWJb+VUEIxt>Lw zRf#Y41mN4@_-=@#nb?jdz;1&n8$xyOg)O3i~l+5~P>pPL;4m?sVA_}=6Z*wB9l-7!Qj)12(i7^p0NmjdI?Cr!ph(H zpp}Yt*jav!J*Z((Bc{gH%I_#t_AWaP8R6mo0NoOM1klnQhwae0Lqst4Roiv(KuU+X zB_!NH4L#9yF2szP0c*!}5T|_-3^~E95|u7ImM%M#t>KGNQ`mVfNtQ_sRRNT9E$pv` z7%+In!UC4`#I}eyX5n0a)2bLOe90A=U8S*A7!?ycy8i&_1s%+++YHHwhDcW0S3!l< z2Ef3W&??l|DRYi>4*{3TJg?@z5r5|5H}E-B{#H_*iRdcS}A}L&MWkZvOys3vY(y?)~umIPN9b+p@udG zq|wxLRuRJONegzjX!OHjDq+gS&Olm~4Qw7RuWAmWnY2zyB^Ww&T+Pu6A$|w(Db+DRNbRkk-{rvro+#D$P3Q72Ht7 zMp5eYBPKSrFyJ^QJy~4`>6&AbMNVTynDH4e+JanSo0dY+1kKu?xQ3aUYmo*+3g9VhjtU?JU?J|f#Xd<4>60XXb z&`~Z7qNX%ejAv?V7f_6EJ<$|`3B}7!n7C32-j(^l5b~0&LKYp6)xM?LT@Gn)6mE5* zD|)iLbN$gx)Lc%Rr236l!?Z!wDjUt_M`;fi+R`|G4vX?Si0x7ZX!OnTTtrE7@e6RT z7HgrCOsGn5Plk zGBX4P#naQIG8+&#kwDEg$vl!p+wEQ=Z-xie#zh9?thB^|0?F{U7WDAK?yxaEjy2ll zHQ)AskyPC`ruB3#BJjso2Z@h2nW?e6B;9SrUac;UjwtCdzqt{Y22jfYV2K6IBAADW zZr11$7!tQdh(L|FebE82J%{iue-xOdHkVYMQt8}Fncev$Tt>mUf7$;4qF&MQmMx~z zR}R+c=TbXbc5+q58p%1XWQdw1GZq$ddaFKvCvd?M7}#AyC!F!FD=NVp|yscrxj>1Y|c_S;vk1k1?I9cldZ!4v4^b1ZC|qvhQXw zCNGm4>dtLV{AnRDJTvjdjA0Kg1&(1Jmcwj<-}2*@-ibvL`bbzD!2`Z zbjx>X3?q&kZ;w9c3W>0G+8Z_h080&js3Dsj+T@Wc5{G0}!_EeTX3*uK-RQhS4BJ5& zwn@@tyHnzp@Rt!mGq5GLO^}zgM}$X)Fg4`Rsy=1WEmjH;3GO%&Lgf;*CBimZX$29Y zZbI+4AgT;7rsgf>6;&yMcCP4@P(^mTxj;${xRRW=lqOKMagHFw3rVK9{{V!4d@&Vz zgO7fbpnVxO*(K>8u@^8j?9vV*qY}ADe8~kL1m@m;eNo+d@_5}|M>{_amoKWGC&JIA z17S7Ig!)us!=96MnL3o|O*BS5DpsnG=X4|ZV>hR6EBPsh@;q(-05s-Dsuu>GG3=qv zJ0Tw9*F`ZZdPYUsgv($8WUdr{DW5|T80ris^j+gTap<*B+60Tzd&zu=DZ`mey5D*K z0Mb0iFZhcVEVcNZ%s+)}n5=Qla+fhlsXZ++**5!RqF`fb3df_t$z1rCE++CgSyGJ4 z^&1O2zB{dlfTibUJ9w;YuByxO(@uy9CBb(I3=j00u;$VA7wG-^Tyl_qZi!KvYHCg4 zvH1zlSktJwTABXK?M|I0um?R+L?~L5MJH%__Wzp8SerX&| z65;6*k3ZPf&$D>!2WVx6=Nku{(&Q2LV_poUKe^YKZo0*Kl-rFs4qPd5L$aZX&}E1z zAySihk{gA=QR4ANXUDQKpEvz3wl3z&_K`4P+4ec(H$bhlLtRsU__K`Z?b2+*-xf6M z!`0!sG$njZde=w>#YaTy66%JpxW8209)@_Dk0Z6(dsWt0ny0*ICd2(FpP%!I2$E@s zS3Zxo=y!xA>+=;#1vW}nTBr#y1#QK}%zeq6791*!Wp!jzp8YE}5_M`k)TZU4en&Hw zA^!jjZhFe}ob>j8YaFG9>2s92vu7^Z@0%p)>dr8#8q<=neJ}Jluh(lEHJ5UwBQ-7z ziU^suq9yXqH1{1jmnVtPl)CXfCL`j4T7dF4m}7Z3NQD;4petAu?LT(*U0)0#;4-s~ zsBKlG9+h->92;RWgP7$Vr(m^_nVMnd?X;AB(nF!TdPKs1ExL|W+qMI=9#L zi<~`OM>kd|{gvmKaCpa~=Ni}C9>+=j6+V^^Y!_})^ojSkbb8q%1}FNlKIQjyLorUx z{Ab_tkF#>srk<9#2waw$4k79nL4Tw#GW=K!siT8_kZ+@usz&BAz;Yz>Hxsk;)&(vc z^;HD^k&}#n{{W|Jd`!7ZuvD9-@6JzOKq9f6q zoW@m(9e?{bO>E|*m$VZ_XqLJgiS4RK2bsxSO4(|EcQ^^^GMmnaOKoupOv#A04;qbw z#cJmtCFVZzvmZd?B^2G55A?P~CdAu5kuMf-c;%%)blGTlRmOLI7W`1#Z47LurfBZX zH*-bngf6&vOYVss%6{gs^nj8&_Rr>}9vhUixX5_9L)q=*A$CLE=##?Q{6>o&@9;E(})8ggdiPGdo`jq+$BKy@U z3obI?6~n|NH(LyT$1%{0uae8tTsPUc`b2}K5zt${h#7hA4A73>i^B&~AFrZHhm4-2 zPF#g^ej)au_;S#>dz2rfqjics4Y3k{i`KAzr)hnHhVozJb2&VA^qBaGPsN+S6;kk_ z4;Q*U&lUHHSo8c8)-JLW99MznT;l5dTE^yKIoEkjZHg+0()7Xjb%Rb;;!*pe^k5Fne z)_|;C%^k$OP`g!vxUWmEVi**=m{>^@X97Qm{+zC~8~+i^UL>22~BSn)kq?G{B)Ay8nAk0dl%7c#-L`7S=4#!gO8Vm~bh%@m z=9Vgq5xm8zZeGu_urmOomK;AQLK<-p{?@S&CM8lv-b2NtYC@h178RCW*vYM>S|uwn~&^+AYGL+5lYy@RuVi6z~h zYXP|e!U(hQ{>)K!1ogwLEFdK3R~4|z0KkdP<-DM3P@A~@&?czFW!r4(fw0Pmam#vO zC$UV`6TI;+R8t9zp+(LJOM8x1u&wg*Pi7SA8R3|_bz1_6RkUPY zA=P+cilH!}n7dVisT9DucxI7m2;OR94MDKg$2+4!k%o>oSLX*E%4FD`-*<){sueT= z*>o;N;_*YGOz#6%0I%2V!-;XLjoNuHR5m+;$OXxCL)WSq3=+P9ILsY?wicvL?%fxh z1hG>UGAnmiJs>JiRRxY$j}%FQAVO$F{ntQLe9nwFnDgp@6%S(q*a&58C5IR`lFYwm zDtnPwM(*SMqKmPC8+qLoVQkeFB2If(LYcM=$1Rn-_?8ziIwwIOM%Y&AM(ey$RU|=z zZtJ4Pw@4>AA&RDVZGv%syuG-%Qo#`2%ajyGf>16B<)e8NS%r%&{US#0IUVU-GL%_Vimao)Ve^-1`l|gw8|hVM< zk(Kpf{t|(H+6BU@#HbB@Eg5_@HOG7%NTj{(RzpyMz>4x7g}Vs9^=8V}4yx zTYy+IE#!8jN$dz=X48OG2~2KNpW%kejb^Yn8{g^DF+oBNUS~T?_KF6j*i&rXq+2Ol zk<^^6+AoS1T9nK<78GrUM`3{ngz`va#=xH9OswY?6m|yVnd{Zzf>Dt>5aWY?4=8MQ zB+iDfOk1yiiVD>Lj(T86@QfJ4=Q)kx5zZbZ*LYK~*W4MrfsVq@7T zX$b0s@DEoMR7Q)j9Q@Ys{gjIqtz#^oPJSHX1{{emxT@0`x8$grAqzNn$wI$d1q5=>}d8doT5GFECTcG=( zSX;$~HsF$F?L|s}t6-OVFcSbbu&b;w%dQmth!wf3PvPl>H@F=^nV7XnILqPpK*R&w z$xW$$91%8+v#-zdpFgH|W$+k$gt#W9-k`|#ZLVYmg%v}C| z!X{~=zdLwF9F9dJrzOSSL-wHJOI0AN)V=s{x5KP##^F!5kx0apvTJy|zs2H?#}h4x znM%ylXh)iG+u@Fyj9#HFxTB&$s)oufvL)3&W+Qt678FwHW?V!=17YIZXLcF*eVDs} zJ;w|(o&Fvn$^zxoItcPn@h4A;2|@sW=C11p9m?DdW*PQ;QEKFlz+O(JUk|eqB#OgH ziwJc0{itG=LrntbcX1362-%|J$YF}WB7q_$@q9c`?&O~0hlbxc5{&P-$#^a(DuYm< z=JUnj@Wo1m{>DtL)4S}&5LlLBZ-?3#8Idl+go#hx5lDAX~%Qh#M*EK%C&egnV9c0Aw4u zeNYTYjf`dli1b4-=$PzKLBq)H~ zK^GoT27xDv{dm8{15-ONf)Kxmt>N&*sTSbNj5O)#iiHiq7d-l)%+Vr$E)qQlrpzi5W9gS$fBdL!6@K~mSY6O6XD4Vi}+y3BnLaVbn!(@Y+V46 z0G>(0HpP){c}s(aAP-?Zr339m8m0y7ZA2Yn1Yn%Y#8-bvg~FZ}2#=~DGuZXk@TU#m z!w|7)lX?$?pwx%AWo_4gT?b+WLDW;(fQbE|?0IGn#KT*QjF1HE>6}svs;RIMXO+Vkn{Yhp9w=Z(`%Hu^V()kv1lv zcP>aqNQ2TpzAI22M*{HKnhmfq+*<@~#lKD{c^H2xT`TOw7g9=9>q9Q+fa#!=moD|^ zq(GT=KbLc;SM6}b1(jpuJ(gYLnXeQwfV)_GCR}=rqCV6*z%r5ykgqLKEfC;1=6>AhS>bBD{?)P#1h#ZRPz@pVTnQ%6l$ z)P9oKq3Jm}c?NC}cy1ye6l>!kwxOkE>i+;%%!!vFE-2`K*2Q)&xK6qC#D@M@OptOC zv{vlnBjVAc)l+?XI_$jajyF9zCL~!}CE-Qo2&&JCp50wXRp9(SlAy~VIuA^AaIS}C ztxc0nlvY50h@plSeRUO$sbUhAt5akeBmHQZ+11Q>!BNb)^6RA95?S_U?ec_AV^S|k zsP?qU%i{-slemQn7So{_*Ql(0V9bbI>w-Vxg;=OYo}wyyY*Ls_?$$1TtggGN5$dQ| z@i&_ky{&qCN={QsT1w{}E~4;{-wMp_CdSRRsi3isq~%t$WT5e~C7%e!YBc!;3$?41 zr*@+_-0uA*9p1L(!dEW|K`s@5=&)TVK9WZ`4YfLdySIdRk6TIuFCXe`!jV0tx)pFzbV_q&hIF@Ra z`i9XRaLE`Q-QFnGi;J%yCn*Uzh-PLwo-&87CHjdNl))EN83Ykq5Q1DTEYcW5+WX)p zc0pr*E=Lp%f{dU!lQi?B+f+iQ6O6-6Yy+C(KqHp*~B2y)DrkY>E13nsR9+`PUyqpOyaLpsS2 zmq{k^A_en4vp972X-u#bTdfWg>LQOe^1oAmp^mjH6)H=!b6K|{Vj?_4(|1e_xA8Qr ztPfck>2Pe?qk$874;Y|`r}Ku#^nO_gnLTSvl0lw7yfNjRL6oTyf+ z46^?K)v}^4WPa0Rx&Hv1RhoVvHWeFGnW__SKJ_MCK!?IhXzA4zEYaejBv-_Ua+6bk zm}FRHMW+qsm#D2BJWw`y_>kmHykJF@+LYkT%>;{+1cq(faEkonp~kc+n=vIyjr8%awFiMZrXdOzcsMh>tbp6hk+x6-av2BZ*QQXVA+A(Q&5T zX)@S>keH=6nYz*Ni>jRnzU5fi!{hpYjnpSB=})Lz7nAU&TphyT09GDskxH=+cs=jf9l&qd6jy_+p zQuKWEE12}g*d=olR4UaO8Nrxwh8+>OMZxYgUTUeVbJvBH`k2=IiX2txDd^{ahh33*n@M0>bxjz+F@QZ#Iu zJt$>Bcy<2(3~p2BV&?6bZ+(0}5ACt>3a*&WfE@{go*o5wj}o7;b4BytpoaX3;k$jM*;S zr#4fx7aLcJw_9}$qY|`*VQHP9*M{u9W8b7&QJ+o9Gc^}%a($UpZeWKkPw75>iH+A&P+?kYHR#CT#9k0XjZQ ztZ=_6&s<(dSBJ-A>R-{Luqx!QqfEr*MVCgIDhSb&{31qNkv!vn=iHtnXJ*R2C$NrU zqOK-52kC+I8)F3G(#8}SP;HsZifN$X9Mirve+zzLxcPrgm&D_KUxVwfuBXqL=2ez$ zkEttc^AGGaCdh{6f)vD-9mJEEW^uQr9wtn9xMHhd(=#F4-h-b>gysXR$;wrx9c|X~ z0GPVcA1|6f!!|#E6LMx;{Vk)r_Q8TSTlAdl>Ry|VrDf^3lXRm<#Ijj~}^BuFLl7JI19>tjda%q~m$%uW|W`{`gEb zx?9LXKFtE-YA$OA81D^LouJkw=GrSxT+pHtCZ}y|haRp-N1)=$5x3TkedJ*e2;Zr_tYNjm&_{8=YMv9UKy_rhKcTaXl~EHAr0h zF`6XNUYVN}9id=3hgxrWIVvh(UheqGk$y&m?0oC1($=3i<44paqC#HnA}jBx)V||F z4=8dtR2<=d`h)bkgEI{|xi-q-FBWlYmqbp3><7_(w<=!RwtFKZTbg3(8+W<}@-$C^ zcr(ks*y>}%ul&HlE(F9V5~zy)MHv`b8d@CN>V>w`575dZw!xvd&zdCpmc9 zQHBTfZ`qE^M0-6ZR;4`OP0gUREBw|HoHdT7Vd|=XrJlKp3b!iUxJ$7oDBnX24S*4; zr>3e>sUJt07GB-l1?<{aYXa;s$Hn^OfUvdp4%?g=FF0{=Iv7=D#m+AmNem@|Q%U+k zU_I=g)(yAQq1)o}SXWdL{ko1)+O`qx%(&&ch_4l}?A{{h>~c=nuzAvIq-fo#)x+U| zPhc5;O?LCn)9Oslwd%>UL*Xu!O;R+ecxH##&!cD3$(_G!LS9O3;VR7+DR=m~lPw7# zowjr`b^ib~=<%x+G%phBAKFO4E+Qb&9+94ccvI3EruHDi#MU3iVmB3|YZJMS-e2oiOqTskKQ*q?||F-U$NQLOt=NX(o4n zMjJ@in^4`_B2DLMc(Q{>1+lSD+dTgO*BCOTD)6)37z8=;`taUiE zT)jtD6L`8$r8^<4jdFu)e95FMV{7|=m@+9dh^5e}mX}Evi`tCRqBAy>tjtR8WymcK zE+{hvNcSbscWSNh!pRR(z3CB88t%YxG@1IqDF-bQ;vPi-$?y){JxAw`w~dyUSeRi7 z(Ph#LeoK^k{{X}CJx(#^apBU{iqxAfCkpQucZNBC?8YoU<9S)(+;MRfMss$cSk0yRuS^AtG25=!!53oX zMK@5i2Quo3U{ys7@@J$~i5-s|{#+~K{%|Fjm7MQ}7YbhXb5bOn5l$};-3E)XK9<QGBfDicn!N*Z8}7FqrJpapUH2j ze5bc-SuAzbM8B~sO3)Ji)5A|lo8CDstiEE|P-^>skhfF5v{REpG6hW%(0v(#SRJtjPhhrw-xaN=nUuGP5hSY&#sw_*r3;t*k z#(Q3TqdcRN7w#~x30%L!6Vh>qn3}VnSB4fr+`Pw$3fP23*zK9k(h4Lk(_{`Wb!Q8q z0tCk&SJ{ZlT|+khX{;)xTyrkra)~MmvkqGY?u#6%6|&xQh*hkAAePnX;fM=#nBM#_ zRV|xf9CMF93>rYLz+Df7iX93Ydx$aM_+g}05?-(eA=We#xCb)!ZHQ%c8oJ%O!_*PH z!*|+)Wzg03A2DDSaluqXAc1b}d{A=3)l4}ryZcc{3#p4PC}ngl7pPk;@Mmv^7R(d* z3MPD@HzmYa_V0p!1&F@JE#TsWGMWJ&*}5xG3m(+mG)NstCy;KtE^$FIkwhI576Pf3 z_>bATDq~>Janm-U?y%!#ywrIo$-Qpy7ZJ zrg`{bfk$vJaNTbxfIDx*XS&~T_eJVNt-+IdYX&e2jQ)OqD1bZzCd5zLiqru|8obY( zSz43JFMg~40L2wOgVv8W&AuPS6HpGUEtc*1doV1a1rhjod@!exi|h@?`0`j(CWf<# za$mc+qC<&pp_7d6%#jO7YN)i04T-yWy_klJ=s`DkIH{cphXz8Nldsw7h{F@H=L>YY zeV?@onHr#fo*^D3v)PED!fXKW^uYr?fx4VN&&3f`1Hd-x#8+h%J%X@7)iU-*EIR~k zZC`vsf(cv#o*0A-MQP-abt*drY_t2~#>PzxAoZG-p432HtPz=iSD^m@-XJG9OS!?C zkHsG~q@BV_o$Ekz{{Y(#8@VMU+$Q*wh;4<6Wh9w@aClSUfVG8=rCqf26oH|29t)Rr z-No%fy)YRE4G|HFaO6M53uV!8gM{eG1_T2)S{YmRqNlJdHke>-%l2V566hd?-dsru zb_G{J+!Q0>{9G|%!wO}Xsq9^cEz8|%tOdr|18zpl(g{UaQ&Qz-h1HZ^;Fpl?iVl{= zFhK}^OSpg1L_k!+vW@{~!}ej84MaIECByAP*pXolOX-pb!qmR84a(enU&9(Zkw4*(SES2ACAaxARLC?kFf~gpB)EfA77!v9@V7DMwejkPo zwgtgAZVmp?mR&G484;mc-=qmu2s|1m z@k9i{7MUgg0BRt&aV?s1PN)fBlpr`Mp$|+@4+8i68Wy*HC_?~v9^Nyj2*nE*+KIxJU5AF}W@E zD-|kQx6kax#Bi_mA|90$Zz33f&=TB@o|oTpE*O3}`vUP|wTP*E;LSLa*y&))p-hDe zxl2ae;J-h**zWQ2YIq@`8xaG2Drmdc)JCMjE(-8-XK-pYX$#bOiT3zD%|0f(BT_k9y+E zg(4C?jDwaQgm-v;P_QM(2Q_}H6{rb9Hubk9+IV873W(W;oV3R>{{Y(;9f}+>@ZXZd zNIKqd9_X?w?8!ZA*{6ra z0>HWAdeGOD5Ua?CoVQit_F@G=zVTlBF$k;+v+pijcy;RP${=(G<2}&=+>M-IhfWV> zAwV2uNB$^+YBO*Zw>~I?dO+9wqxx1lU8)ApqwPrf_JBVQ|C_CuSTG-Rf46L;;Y- zWRdT5RP*nCGNskUo;(?Ckk}{={kB5c~Ss^Y~ zwUQT?*@gpA{HxZoIdwyOi*+B#xFc=1MOBJbQbtsawNL8|1yqrj=l=jVltHu}%eQl% z>xvHYC6`0Cdf|!#PKOs{e`|&cBEVc&iI`@#_d$_=Bv`k*wNr`?z)Ff99X=So;O-2Q zd&XRCM}^L>6i3FcF)~o0;;ID225_8YyPC-FP{UP5Fjdt974-Vt#SLC2XM z61R#l;hv*oAHc;L*1Z<#l;e&W)vIfeBiYuD_BZ6 zT%v;{Fr)4cA*YjFeW9`+o#O*iD<5Qku)0^~Fszw2+d`X$$JUn?b_{T{!&{*2Vuh&A zcAsAp&DSMF%fbS@Swd{UPq5ChN5mt$N9xk@`{1}O$h1%XD4WU>^%VSJgzA%qExA$$ zjkg!p-Dr=T7MwPAEpqrivnxfFzPQm^(imHp3Myxp@kKYQ@f5DWp2Bq^pX`o*Rsp~u zQzOua!kK>*RWgPca9r~yCUwSzL=;4B3vKp)hAY!=u?|!8nxM-_?JRp)I^@I6AzUPm ze<<6B#LV&3a^Bk{LA+q%H+Jg5w(hP`(!(7lJj_+(>B2#B!>S+)jEcRK7Nmh$lio$t zD+L;8dofI;v47bNiBe_eCmQvCZrp+-oFpik#-2O+W_-T>M^kz{{UQ^aJ2`rGCXxEu zKyn@E@V?T2SnFf_lF#;~dJ5&sYd(}%H)@!j$=YP$%FDO{HcSns+Qs_#q{jSagQI-} zeIT}Pv%g2`w6%K^QsiE8tn1*xxR<^JgQ_QfuwBg&fu&9-`e63;g|^v$j4-^O8E<^m zJRehaD8}!h$eTA-%J2x)CRqU+nrAP? z9%Gx!W4*?~r8<{pcq}DlnH6r*9IfGw4^;UbM8&2y-3F2}EPOGgAaO0cv@#Fy!EQ@I zU?iv0)fwy#pgSXR@QfONs8k=wv&JZc1go)b+9W0AEOTC%emp{NvDh+=j$ev2)CORJ zCEWzR#0uM2bLR{KdxsW9u+xUv2vUPLE9K&gViOd(9l%~g(;2flp$9HbJTF><>iw@3 z(a)ANApTUhn%iyA5cUa&O^J8Iiqgn-*^7wS6+oB zb}g4e#IA`GqF7Lk-Wawn!g^C!Qw1Hy^~aOyNrs1;gRjZ~n`0%8M%$JLnWfcAX^}3g zh=<1+vtwlo4pIjJ?P2^oGT4{dAugPuX_H3ShAeo)XLKF5%Q%3{xW4eM>=>a5FSMTc zQp3Yr69{}OK1Pgxd(kG7gKKTRl<<{$T#&@NiL95LY>&!vXQi0I`b;wKoSW9DSbot1tkLmDrYPOrkX;H2!{PYy_y=AAE@6G6fx zerp}bPvm+^W6NoK9EJ3R(^|w*4b#=V=Im6IONedn{n6upxg0uYvYR;huC-(2m!>bH zYug^J%hhCro}ktoJxYAB^xO1CpY&DQ zywxR0rl|8@q|+xY;eCbWpS?LO1Tlcp1lb9}klkL1dr114uvC}aJf2i0zJ2K#xtMOY zHt_RRs&*C~p2!Q&t;)D~wj;AtB<{9fi7u=Ec%={?73* z;))|~__Vq<f7Av?+($XLl()N?<}Z z>%*ExY;ZG-`eRG#CzY|KV@B&+#344VfR(@U!AEeBi{8g}sa3$0E9uoKi8XU8gbL9p zYUN~q@fi}(ohNeoEbaP!wK~gl)6TuT6zV18jY#$mYRYs@EZVgr1kuWFr#RHOHVRxt z3CaNDKpem7hJQ_tHS~=7PR^k+$ge^I#o1NQ7&K*Mdb zJ2>pb-P}a!Tv^9Y0`!3Fu608%cAyeYC`AOYahD<0e>uZ-B5k0rN$-PEiq^r5gbppj zqgbZ6!?qJn3XCn&q$zWew#|T6g;<)BF}Vbm()N-C8>XaZuSv^WZrS<}JiD^u&+x?~ zY=<=B3OPv6yvYv<6527c6bVym`T%-FKj`zfSrT>Q8g%oiFEAb*S1Xt01l=ai8?TEW zWq!DBf2~Yq_6vB-!$@tA_>p6snL=ct5jQ$=F5U%c3}d|*YX0HVHMwj}IM3G1wJ`j@ z>YqNL`TL>CpKK|xtd24F==9daf4s}47tDT;@Z!*Ve)6qCqQ7%O=!%K(+GXV)e==<7 zjc*@zTDiR&r|1H zNpV7cT-&2m;qZ;raDtBCok?c_8_YS!y*b=+=cLzEaviDi=S*r#Z%dfV-n7Y z*BLZ=-r*>q;w&}9bbqX{^-+gERP?K$LR}<^g$a4E+BeAj$@*akV1Gz_Sxc5LQ72G% ze>KnkDCvG1FY=BXM)I+xs~DKh?8wNYf3@lXI7pYV0lTH$F;kpuVk6$G86Ha&a#o{> zdWF2o8mt`$e_9c1;){oDB|{?0mBfm#*P1m_+h6H&AOD#c5{eGxnn%f8aGh z^7G!N4#O&C%9nCLifGZ}p1Vk3Q9Bgm<;~@BaKi3d`-vIvi{iQ%3o4#D+nd*&u(DD` zGdXR!maxWR<~Ch6b5t(YFi60u(0D=a)gUOI&+dUXqXfhhKHg(vd|zhhsErHFItNMF zi_=Cc*~pN78)1@+Wo(LFB=%}je{$NhYFlVBRavJR_7zk23hoRAKaC09N zX~P{x9G>iLJRc{!WwB9@L&iDzo2a+Qi3n@c9Ypsz*^Ic_66T-743A?JpeI+oVwkmI zu`dRFFe4Y-5lj)qR5^`-aJ;x+8HKX~BDrW_!ifzOTP)Dm7%i8%sigplBsRy+SWdaybz2^Za zGA6+pm?G66X|Eum2a@v_9*_aoL+0ier8&h)5HlVm>V;$=<(?zzgoRPlP7w0t5V>K? zbC)-)R46Xu+i*u%vVsRne?iA$7$L}D(riEl#j3+gp1h&)J0loz&nPt{ zu-)PAgO1>OU- zlim-$7Fm(Yn!vh{S#fLK32$QTA;ie?fncL3jortb33ogkEANQPe>NByW=E*-doVKu zV*=T7{{R#PMbV@nv%4`{x}ahRT{&nM6-& zE+a%%Jl_fXUlbHZ%NzzmUu*eA7f^+7SBeUxuvXm5<^5u&G%cLwX>}nX%x*%Do7nUf zC8Xv!GX1DBRFsq~lO+5wverN>%Zv(GDRMXy%`h>FdKo>E4ywN zGh(7blfpcqe^c0x78ShSbAg`XM3Z)z3@Mbg5>|+2kgzHy%lSkBl*>Fe=fewDEO^)s zSzUMhF;pr$1l`ZvJS`g$y5wp(|fLVJ!C~_*56S5kh=N?PN4JyP9 zQecG3!amGB7$O#OV2JZhpAQT)gD0F8^oeW zf1E-Ha%WuEir?ahRSyix55)%xRP>4yLrr*!*o?I$i8@aPp4U&q5#&odNpGRde+b7T ze=!pgWTwri+PzVSAbpA2bk=95wZ#aYz|8EiQ)Ul^2mb(cWXuJNEhVye1v-eM6uyDm zNo=Fj?g6`t*@R`vEg7fCveW52dXEjUnpka#7HIwU@W9xX9XC(l9vGk@>>ZYEi>N`w zc;cQ5u;2#ujPLA44cYwqU>V1mDPLLkf1;*rEWpe3z*xI$#4~bgR;&%gPf(|%cY&8o z3^`JoBBv=LCz^~hwie8!JoPL^bRA&f7?!aWIY&nTvl~~#8c_;_VzXSsDa5w*L(vLH z(NL>V$(yqu7Yt|~k8cvcIWD83!<4rnYEt{gT;cemW*O0C7ZOQ`j&|_JV~vmve@Rs) zN3`UF!d}#LhAikJHid78loE_i!kinq6ckNHse>CePMsn%2DJt*UGR_bX8^?tZNwnE z<>7-R!Av=8`6si~Fjgs8LAaw5!=iKv!tx)!CqTBHjV23RztKS?E`~&|)qGG4NK*vmuV>+b=_DcU(RcjOV><+9 zX5MiMux8)P^ZZ2+OQwU1f=8MZT7s{k$jn>Bz9=v!u&wpLNRTxu!5KB-&zunGLB-bw zLR>m^K-e}~idQ#w=0n0ivjhqzj|RRZ&4nPf#BN>MK?TAv0-e%2+>I&Fvy$bu37 z0E>noEWjQzzn9^NEynH@!IF$YO2WvugP6oMsFykiE+Y7%0Okma%|_qzfTG3BBH^Q47=oBDa?j6hgq6WM%P0AeS7te>^-f2ndlEi^C9r z)UEKuA>nX%k}e};GCV<=L6FY!)62yL5L_wji<gvl1qY6@h693gBnSd|GBPYE7{ZxjeFdhhXP1rS3mj$SKM+Jfvr;^ta>xS*7S z$s)AiHv}Z)iA8;%W-26HMu&~v&0)e_htlQ3i}6GWUqpnke~Eh+{{UDe1&Ikv#4=Ha zD=ARJ`8Rll7xrL@4ob+m!IJo3lq>9d+Sp=?{{X`m5ujLj+lgjL_(mEodxmUoWMGib zV1ibjP;jBchPv-=;)8@2y`E53`HB^>6+xI3LP4VR`!MuSSwYXz!l@^_aCl3_)e7X` z^;U!M<*QiMe|WlDk&*OQB%hvA1tKwlck zP^e&*TryC48o5P!VABoB$_7ST&K_kG)Y+pRG6ND~YE3O%0UhD|1sX8n%OrMKD0NI= zb4Ezef9}J>?MFi&A6`aDVin1vaP^H4zfb`YT^2xOTIP`mCqhUT0>Z_zFPsozMprIy zvKSV`OGqUlmnJ*BQFRs#$&0Ov7!6(|<>81d^0-G83^2i5F=RYfbApKi!cnp=ei#X2 zjPA>ZBryz&YsxHOuw`Np@U|+ERR+chzC8vDe^N^ZVohF77>cFL=yno)oHDnFqAGwO zX|Bw4iIGJWGg0J@3Z9&|CQ2Bp1<8s&1A3o0%kB`4m=Y@z0s4<{ljmv ze?F;&=<^RYj^ZuwlWTDieyd)e$^~)$;HC6a@%28C-6))qnhAq%A+$+&y0eMWzaV<# zc+EbP-W!!LoB^6Hpx$tbaODw^e#J&j4`t+V_tsXwFk8D*h+IbZ+CBxiHdl zw%D-j&3kCCGcPJ+@c{au;%**I=hx;+t)e1WtfxUKiYtFI!Vh}xUMGcs0mcw;+^K@8oz@#g@;e@V(L zsm@HYH(a+tGZ3%sdA`w)-fHdO$b)j?Ui3lE1{50;yFJ1RjJB#ix@-7SIb8lh(&Aw{ zV`I1+WMmmo>h_3uqs}|}jCZl&p}jDzEM-|0A&T_Y!7J>xN{LKbOXBH+V@x>txpGYt zrZdd!TR7@oPVH`rBslg?RCs*hf0Ws7!|6$J6uLBF#%E{P`H}OD0E^tNSrEiuaBLN( zQ-|9A7-e)2R>+0Sk|3rErD=*p4j_`F$@Zs*$YD(3(6R2piE&loL5_Y{*`X^1dPcE1 zQpDuPVYWfAQWvh`CGyG#(@Or~50_<)mTb8cV;c1uB1f|PTE?_0dyDE!f1s);Ro0)n z8Da$7-E1w3h}n^s6xARmHW}d^+QpSDWV1xgViFY3P-_bgAZ;+(5E2(mR-h*LHI6c% z%hYCGX~6<-SkVx(DZ3 z`%#Vf`B&m?j!q~xHXNt9e~KgqR}e_GIdGtf7YmWI3SD}Pua#um*d~o9f@m`GblFy< z*=%}1dduFhnOZkU*h2k=_Ko#qfp1HQN5$<%{0<`~GhJ<(mODU`dSy7B)VN{Xo+7%L zF})kaWE6By_spGS8{S38oxiRY8(l!{`ggiGhoIn`4 z>}O=-v^n<*Vq>w%N#stL<#Bu|4#b-j@1uLl;+z|?JTWHz8>&`I6E`}5{H?c36C7II z?fapbc$!jS<(-YQe_UB1=}~}aa@8sr)(Mie-q|C)5pOMQ%`{x%xZFE=1)nef0BDBW zZ_ty6wuOeuuRs&RwKsuX8CJbywrk5YT~jskSl63@uM-YDnPa7onY&Ect#i@0Y%^3! z!#$R%MVLW5dP}8Hlia9L%$RSHwO{IX4#ReU-Yvt@a|>r?e|fYYh*mfptTbbW# z+Mq9EnP%RB)JS6v^k}~Pj-B*x$6LS|LMym#_@kwTnpDG)KE~ym5!~dw$_2l0rZf+x zhR0jl1_79u7XJWAksF%fLN9z`&Fl&vJSVBAD>~z3Ijbb2Z-^n--seLeP%b^dB0R2x zF4h+%HQ8Pge@_pF34%t^zqSXo8pSqOc}$mfaq$~O8WlGMeDX+ET|EN5uG zk&})9vpQ1J`GwArwAinIy;cyI^5<&-o0PD|uG&7q>iZ~}Wy$jG4NP%fJtDqQv(soZ zu4Q)XHaDUk)*ji;Fqid$+fEu>J`#<1U*c+ovmaBoe`kyt#MDVyw#w$F8$$cx`C~vj z>5GVGCxfq!!Blv!yvU$lWt5)JfDq87n{| zDagA|!M&s$&AYk&aVt|wX2kwSDQ$yiWhXM@g5Msc_Mq^%r&Cc?rOrxWxu&jd(JmrS z3dWWxeNYux6`i;!Y8rbhhm(CQ@)A0eFN98W)e@VP^0t8Ku2#*vsSL`a*B53`lX_69i zhVBtB3P2<1V=0QZ7~j&|npX!V%`YDiugV{ZexfoQKlFlNpv6^RuSUH;+vLdw%e{p6 z`=Og_TE?(P2oOWzyr&Y`=uHM1S7=;HP*qtII^ zf02E;TM&qv;%_gUb@8iuN1n^d&nHe`Ty)J^LAD%lx6O|Y#TQnAF_UGMcC2C?4qf~@ zKt4v0q3Sr5ZgOY|)DUnjP9fsw62tK3XcILtFxYz5?YLA*?Cr`H-ZeJ*%ID_$>5RZ` zU=K~aT!}7h%1=I%4IRB#bN*28!?u}~e~vCyUtHu0gux7VS;DCrC+a>GNf3!Yta>ep<$rTTcj~0j$9~WQyQ-? zca&f5=N(F?`)V3omSIwxm! zZRDF`rErczZ?cf%Ml*M8a|pAFDn#?vlT!I`mx))y9nZt@mHL=RCNAEQzbHF1lX;O+ zm6yxx$7e1)PQ5BMzY~EvNFAYIfBAY$vz9hCm+;4#=HaJ_7aydil*AOk?L;ZmKZYzlr-+11lIx;_ zm01;Xf@R$SF`#Io(}vYgwG>jiju@It^YGmZpxB!ZyH2@ZtSlC9sP%VFDP-vd#RItotP^NZ>z@Rh+0kK9AU z!ns2T4bL1@DFC?Oh-t+SMHb))H-svX;Yk8qEdKZ<8tx4AOueXZHUi|mkZ{7HbOqmA z&zv$l62eP1;guvt%r?$nW-Oz|+I}dluyj}lyS2~xqA(1nts&Cbe^rRjquTdG)r+-^ z>Prb_DuPdeGKi`}2HlV>x*HZzHr=EMY3^i%ue+&kfYgOi3i9O_QF8S#*?;Yd)ROxO zE{`u}CZy3-k!J7MiUr_EmYMlQ>PQHj^6`HR7|DJ@vm7C(x*7y_2KHC`pzKS6Jx}dG*1@Z6&hOs<$)cI6 zB$-Yn_Mk%)LWsRK#cBv(NP3b6dxFk;Qqm~9>`5XPiwgj6G~J<^6hbroM#MYN9QvXT zQMA%rpJbrwRe%>4+JFpb;cp=7T=xfJ?=>!%i~_#FxzI`9f9*znL2Vlm)oL$KQ23(D zh|!%h%H1x4MIfOrF<%rCsOp4m!y91Orl{qU?@wp>LgF+8!S0~RczF~{7q}d^XzSsN zn6P(|U>5zDxSb1OR_l^`P*xIi#JxPS%|`A~^5t{V4%w02YMdhn>X#Ftg3nW5T>QbjEgJY{Q0E zc(*4^0K1BCvLb+(&@B5BfpPna66{ioyNr-wa>WO3Tu22H6JdedX5D&=+JlD$SwmV} zlg0ev6+YsdmtNuFi$~CFK`xv)) z8FY~GKsT@;zg8I`TLavTz#ek*)VQsKg(GB9H*WdG0+SsyUxt1PdAQG*$oh3QTx(NT>-gyiMP@f2F9vKD@D zQMh`me-TuGs2!)_i-rcoMA2^^EQ^Quq3TNw9CME@_$5;9VY`KPw%ZW6_VEbGT6w1} zKNLc(_72fx99>ZZRr-q;33)^haviscPueJgW;QLkt5Y>Dt{8=i-xqzq^22u^SZw3g z+>7>NI~Kz=E;D@ZGkILEWKEGbN-PFGr!}@D1})BZv20f)rx)UkPG@H<5;E(~-z%dN$UE6%qq1Bvk(hBQpXyb>$7*hZ1A5mv>Yt zg*+_?;eeEV6{m6k)bT{Nh9qb1uO1UYI(Na5DF40I^>YZk7Q# zOSlU$*TV-4Dsg>?N~JtQnDIlQ%BsN)H7meL?8B5e?nG1>yX6}ZHVdI##1tOYf2jWe z@kW$EU5YLwqV{v?*H|E-*r`>8T27l`m`AFWEKMv<21(CY>2ZpfDN~Z-+uHbgqp^&+ z><-SHqe6!n3^?9KEA4SXD-Ya>7YL5w<heqS{i27*VNE(BDnw{xiFR6kaiJiR#C3#6 zuMvNSA7Wes+$Zrt7Zb2XH*@*K7%I`^kg#S3&p3j4t>qS_#8eo)$M&*=EFidTn5yvn zP_S8a5MIxpF@uQFT9B>GotY03MTli#;7o92uobXW6fX$d!~9TSN`x8De?PSVrPy-Z zz_=NBVlY;5Nbv>U$w7=&22+-vOAxv45sNd|*@z52$5QzaX%H7dL|eWuho&IaT?2Yy zGtSb8RQDN_-yU8c;fPxSaIOt}F$kc@ve8B$K+fkih$>WXF)ePf1E|VkS#QZWMj$}M zhlf6K2smfe-EhT3?17sVf9~NTi&h|EaC!8=*n)tNv|gXuf*`azPlu)fh-F^&n)Jbo zaa-%SVgS8HHK~zgxM*GeTphhSs5A0>)_M!svARZFu z5C~Cixp-^S5NfuJ6PIcK0Jb2`;zx?qPMCy<66K;934Y8f93Vtjf1kq*A?@b|YXedO zE4jfC6TZ^*L?8))?;BzeZ*|(NTuz7u!nHNHqKFH5yytveM#yAF-4wNgg&<#LQv9xq zAhycxV-SLfcr`0x5W{{;(on$Sb{VqH-|<8WquG8S$hx8dQc|AZO0QHfVlgEy-HWBX zQ3AN+W$g_3?HC5Ee}=3TB*?XiDk)5ax*_Fl&@36N=~pPhpF~f_nI2s-=Oq%ZeJK z4DL%W1O)aKJ%rl6aaJrZPK(lCvlqAqq2<}*x7v#ceJ0SCe=+dDZb(VC-Jz0lETYx4 zLE9s5J`{two`awcm58lkN)N%YEfUx;9!IS3fU3dR3Xh~mc6l%DLnE;&NwQnC&3s*6 zD6og@@j)XCZ(>E8tQ0}gH4BDm4%>P1y4Zl!D7TV%LkeJwiElr{Y#NjpobvH;eoFR5my=jJV zEhd#OYm+ZbH+8vi>mGVjN2PCNJF^7Ic3OO>ApWJH0=MNFkwliZMH^irI)hQQR}iyH zJBWie#Ym$wy#iG=HrPESBw41tD=y~o;^86pM>9Tsf0|)7aVFOroS&?{GRbcMiAaX% zRtNhT+XjkUjyS2|^WA|Mfbnk2rZmnal9i}UO6_eXzw->3mA)RBQ*pkgh|C;uW(HYc*VvP2)0ML%65X}aX!%C$Tx3BH z$SY1vf18?@IMk+fU_@Q(&Io9NEVXv}xfhVV|cj)G(VPY=a?~@p~}O(}KAKIkF{AiGcm^slZ0aHFIR| zH+8Q8LgyBUxdxIrWZSCtTZCQsqSZD(a8F6>{gE->ddSfnaW*EUC=$|Zz{n5bgY=dH ze^#kGLBMlW=~91q5^}e2-PB(%bZNuHXYn($sRfCsp-HN|!({Bh30rw^i9+(>&`Q=m zk@`Nfhh_FBhQ@DtMlu6!#HFrUe)#en%BEhkxrJNOQhHtNFxZH_B5pe8Q3EPMEA3~N zQHRyWJZm{o!_uz`HA~-csN1{Z%P;l&f1;-*BDDir<#LoPj_QV17Y(G!_e8cYsL%(B zDD8lW;rW39AO6QnNnrl~l|K<_jbK|Cno41o#hMHE2_anLX~h9KF%6XZk7^aenq*C? z495uEL%Z^}52k(~GBKn5Cg>Bh(~>VEKUDhCGBpCcb8Hfh@6i)@i8brsO}_cGLlm1~X=q3THqd2i(o zCggW7Epg{;L{(yobj0P<)>|vYtVnCIC`Yy=9HS!+Qc6vt!o>^5RdNBRkP-S|!-p zn<2ubP^q^1kBZR}9_aG?%$w;w7a14ibdDFKw@sE2R`j=sV-7UY(5Adg<`$Mo>vQ2x zW*E#hv23(%eMpe-z%vM~L(8|^kpg9?_EnTCbMXGuRctQRAIq{!s>ZZZe@y6d-xiSJ zdx6s|_rnUfLC?AdrB8FhxhxTV#Q|oYY5{>BNlKK_j$SBJFy$=t$q|)gQ8~tZtzhOf z0&5OEHBuregWM4DHEdU@p$QfwWof}G0suCEibiaQqc+?btU%#}x53Y*Dq~|zx%OZI zVm`SvGLi&;3{t2jzNk68JCt~eHLBZ!tSNu8H3OnoHr&Xodd(kl>IciGix>_0?s8Y^3x zE+aJPm9iCqjNn$DS8&Y0F)(zd+ zoMLqH9Yfwt~~3Yf7^yuO-e+fdB(kb2KF*UHVTP<%upjJbU)GKwqV(w+2mV#!e(fY3jD1k zhhs&5A?wtAG-qVAYLw&CY|7ltQ`WEz4-XfTC%!Z#vGl3W zrq0TP!@rjjms|bBi5Bj${i_($-^Au^ntCMTJZV*Ae<>82h@II18!9?|{m_KXh|;-o z!Sqkj{{UgQt7AJolVcM+(9RGisvaV1^VT#=G#W0?Cd|FZ zuZkF=nYI>SS&vRjo~Sl2(a*MN zjf?H4>e;6)ldF27xVUppP>x#f#OZJ{Yo(8=f1SIwNjBma1Hp81a;}HUkZ(GzAHp$) z!+kP;1Gw2w?5wTsFDe$FtP;*H+UY7)NNLH0l1GiVE>hwVwJnNPD&%b5 zeplJ5Uux;+)M6-fh>gp3HuiEG>?=GKWwB znR6f!(6#%alYyVa%<)rv?AOy$nJY3#6$?y@hzp08x-K#NRL+vo;%@Do`c2UB^yg)n z+!48ufn0?L=-qmaxU=o5$HY(mQuPsNzLnJXn*BudqlA|9=7vAeqZa{b$zwM)f2o~R z`GHWH%anRG2#qImf*=j`qmo44!LUYL{7}G0BH<367!?>|V~KNtAjp^2doc(~P~No1 z!wgmyUGUvYcFn&i(2fki>ZLWj`#%go>{e<|T{ChQOe|dYGTWWrC>sog7Eh9LNU36s zi`jGK23DZX^7sj0s$+Zei?J$ae|>K+6b-@D87DQlBH_xZDTZg3R(-PWyNVdSK)KNS zDy#3MTVI#u8fqFT(i;QC4N_`E5B{y*3%H>Z*lbAdUCup6!vIB#c@HJZ-hD9#GKXcvCCwt5 zHbKn0(JDdEyeVp15>&Mo-&tq=hAD|LprT9Ne-sfyuGb~~sDu*Wf9dR`Tz4P`xPwS~ zKo{5|@R1za2hZBJDNCV&Zot#3 z6)HJ|I6rPKqE{}&lXigc!bXU+i%5c@3_0BwsSmIxhakx~liG>|Z1aHFWfcf9Ut>oL zgD)0^79A9BS@*!yI*prXc##SO1!e}|aB23gh7zncBOy$`6jZ3u21G%uRY9?obC21# zC=aI zVlFOA!^?^m5UaQ~>gs~2p5~+e&U(-BN66LBK#n*ZG$a}RC^*Us0GPvO%I2TL4oBG{ zM(<0TI;=E17-j67EBIpSOAbbA?G_}db{#hL$|G&Re`Yc;45OCGK*u-<>;s_fxqtx4 z2<_zqxdNbGm?1pVoKY23LbCP8oFZ7E#IW6TW-7m7^K2-9qVV#8U|2gc{CplL5my|u zE0XFzwHIKn7qbZlR_py>)kB5}xi55GLs!{{8D2PIFzXoX`ilPm@j-zsVa7?d&w?Om z3Tc}{f9pqz2}*U?&BNojx(E+U3Aw>?@mgPMAT$TOz0E&nE~BHQ$hbS5VpO^83F)B` z)1Op3P#Tf5k8o#o*F#XI7);I}{Q6<=fsxY{3v`A|Y`T$?)7!HyD{d%d0hrkjIWg6m z1REh_Oz|NGa3mw9yCg6k2D*SAh2gKappf{Vi0Qco3qIx5J{?u z<#a)S(5>gi5E}|@?g+S@7TrPHBqJR9VT{G9ix-Gq%I!WVwFz576s|o;VI$!~HubEE zfB3&@AtgfZt_-|U0gl3VomrPx!w^gM6t9skkStJV#-dukS2yDh2$h1$x z8Np#BmnYizRv;oqUh8uhmmr53GF+3|_HEW737NvH1h{&Al- zxl)AWlW0fwqnzOC3k@b$r-z0!xVL>58Vx+SNjzCbH*qRl=MhH=O#Sqq$8R9ZR=!nJ0=Sq?M@jY3L!Z6bnYej^V~sN0%4;e=rmz zcM{_;EQ7|b4Z5#XDUb$go)+yq{{WN?G+Xx_O1A?2ikBrNC3rp1qFSe>CG*zUgmu{w zIU~M>c40L875b!}pB1p=VRS0^nu-qr5)H~; zC}(j^(rcVWN8O*hSZa@qr7sXSf18AzTt8+y9Ayq1q*a@>?R-(%!ciOrYk72}Ke=$C zOzeuO8Z-sR6im2uf(;}crTp#qV)r9=Aa6k!x{GTW-eexhe}W}O*aX9+yvw9vh@!8c zO_oYa`!O9uW*W^oU=k!!g3Yojam6Fci@G$ZwOtJyEWYTyz{-Ll3-h;Mf9nw#A-C}; z#kcJ*awX!r?7E6AL*Br>!WrAdIa?8m+yE{UBu*lWu}UMfD1j$*N`w)ajJm(l!4Nxc zDP3tm)D{lQ>fnxEAK`)m7|WAHXQ%wI5~Zq(W#t_goKO?wIc{?PL~#T3kSR=WHSfEM z2|$_I;VXX(5SC%on}Uauf8JjdK-Kg)7le$h;kqE$VI=37XfXq@BYc&S%&mwJ*~bw> zM+~9@pcJkTvk)U9<;Sc+VA~ZCh%i?xP>Se)Zbow&YQ88N1gti5&ghmbGcxx8WN|=F}B-z-3^G*3^t zT-T}~yHrs*ZoGL!8m+M+D|u%C#9%}sKGa=_Fkq8|JATvk6aw#65hOjLFaAUwF;Y!_ge)PE?>WK8P*yI+ zEkga6ktGD&p=g}qw@H5Kn4ZAP0{pJvv( zMaOw@eo^I_`m0mZDm1N+pfoc{c1aYxh@*1&i$}8zG1kzfu&jYPZUG&J#x5N+r&|iQUftuoUd;#L@SfS z1gcetf4S+2d4|aiu>3;=t5WG=5~i{epiU@*Xg?Hai!o1)V5H(aOw7f=z}lB}M*LW` zXJ%iC0QAW`d8|GZ0R~yG+KV8bD|P1;QDC0XBE|?JyGRu&dQUc8KKQAP77$#?e4>ai zeeeD7Rv@O^IfK$Ag@+cwoll%yh8gX)=p$)Ge~`gWs`4nPVi;M&D3Zi7Gbi|9VstEB z&T@jWT7knaOg81*a7x8cnWopKWl&VEwh3QEscWUzf_afkxb%%HmttEPCB9DY{wQLw ztw5!7fUX--*oBD~xvE9O1fcsu-<8oNzQm|AnD&Q3fM(fMD|n_<*?GCkrJf^x?3|7H%z%S@_Vu$=NMi-%r+-S(LY6eUG>MK-WO~r?z~d+ zyN&I)5n82@FWnwP^7Kre^e=9ZeI$uW!D?K?jm25b0OF2YQ;738_ZR) zF@s{5enF9Mg6p`6S51ESwO`t3=p(UJQxh>DK=m#_UMz?$B_Yc^?t_Q@qDtA;F<-#LF}If0fPR zj^Iy6NQcFD{a{zs0jg?`O1X-Ka}7$3NyfKQu8qVaZ&DURpY)A9d1h{*c|Fb!^p2<# zdqQzoD^qaaE}YRLvxWLjW2vs+oO$lI45LNCGcNP2o{>=Gpy|59&GQ&P)DzMOIEmps z1R%X(bSLOFr>hdq3689aaYcHYe=eihX@g|)LjA5N7GTLq8q}5m(z}5_gyGAKvC#2okmOJtGh^M=4pkSq9I`%fY*FF3 zdBqT2w>hLLdj}nd5^}2Hj|^B)rIwdTNl?v3kn2 z_e2JwM0hfRV3|x1SrI`}w~eN_1(EzwqmCPuCma2the`#8YLVYfwvo$DF&J@Fhq*}(Be`rmH(k~o~;%Htd z9>Wn$>9N{VSfZWUog1BRhwVZ+t?I2q#T08A!|4e0dJVK}9<@*0iA>70Q&OeWLf|1P zf0THX%G^#$<@(9gud9ibkA}E{Me2OxlFkXX<0PPBTsEpl+c-JbJ}*^IpJ&AD(nCDS zUz}_*viwV3B(0$`e@pL(OAaNblwq-zpb}=6I93j2cNdBe^$tU3Y?YEK!zzLuT~KK) z@&VIkxb*Ct?VECYGPzrmmje0j#uJdWH6|IZJBKTB^Nkgz$)Rxk;X@U)cjm=$yEwxK z$jt!{tZfLs74MHd%tnl-K|lMdZVVq-Y;Bf!ZUV~F@d~77f9%7T-081Jyl~|7fm7cg zy_|$f?juO)Vr!|JkeW7>)Vc^=;{N~)DU?dD*ye9fU5=JA!6j3dZt1#j9}Zke-x#v4 zn!{RY)Nmy8XDaole-3e_Uu$DhHTskZJ8#Y(S`W_zf&>Kj6R!Ko|DG1bUAI!9-Avm0qHOsoiE0^!Nx2};-jtgD_FZ6 zOHRXmTM-jm-*mN?oVPE+vEl?IyTVlR$l`6spRSjhQyCAn=s z*>s;hcD;mZc#^9zKU0{RnwxWs-+YzF4Ss7HxYmSLheWzY)|5E!+Mhbk^YZes#WJ@ zo4BrwSrWZNS(;8w#MF7r6NqJMO^LGgJ=78*>dhBIg=AgzF?e#eGwMI14%8ajcE)Kn z5iQ}90515gSm9@egV4X&wm9b;b8thnnQPueK(~_@=rzKvHWMpbhe~JU519N1lZoYcQ3o|S=kn19Cf81>Sg8Fe{ z%qxj0E>AgguR0=NIoc`j@`=Xe^%t9q@vl)p`f#@ON%Vrocy6Umo(HDs@}$JTU2$es zYy2_L1>zTf)bQZETap>xTzW+i@RhL$IHtY1cvTA6 zVmVFJZ%vNY3GLclrh+#{s|d#3e@8)J+JUeFXeY|<7%S*&!G5Ie0NG~Mc*F;W3ONj8 z>ViJ4i;k^2EvHcxziJ%0c4o|s<);Q=vT?C89jF?cZji({0X%NeN0Lm1q>BWC6D zJmaayIQd*~z>|MwH!B?H#ChV~^6H3E;~O|va)?uOfN{%LD21!+Y+kVSe~3T?OJ;)* z7I6f&+n*FA!30J8FuN9@?aQAishx&m>vaAY8I6z|ogZh}im83U+i+)W2-w61?md4D z2BaocgD)~JXpt3#%v2ujZ{dmp##_6J*sBu4g*)#3n3gWXmUx4xuKS^LItG>xF4+n| z+-xBIlHze<+0pYTf3r6B;UGV}a|qVnpb%!z*_&f`%aFkuPQ^at>Q> zzrz65j}i+e_%lc)8y&!q_oNV@4mbQzlPaOfwX;4QvwvEZgBoC0+Rib1L<^CrL!1E)Bx(NSlzRrb*!~eV!N!hhZDf&Mpv+yA!|pK1Y=Jy`bnS)5#1W58&DiFaoNzN8QyMw$BXOv!~I-u3z z5Iobx6H+hKL8*n2L!-C{1cWk+?o%+;%!^eY3=F7I19vXVe=i8ZZbBQrS6r*&@kE~F zJR@+Dbj1tS&se_bT*XLaM~{Z17zAhMnR68IonEO!!g&c5-=C{NnPQu;s ze(1eO69EV9aKJL`5QD_do){ri*-?|GCk}l>mzOyB+2}{|^svu07`qB-(dQ_r;+Mni zLsi7sVN$~Tf8fx6?-seVTE2??MuKvur-zE+hx#0y^+VJB|MltahS51{Fk*C%24Qr^ zXr!gXk)u1My9A{hlo;J5t)zg6fPh@0e(rtmKVU!Y{i^dk=RD#LCj~*~J2jQ49-}Kc zr^Ck@InkEZs`ELP*by`-*!gYz)w|^M8bVhAJ~yQPlJoZ%Hw>Uo=SnT!5yTT<7YuktEYLr8CCQjfIcvOw z0?;YppHVX#4wia)V0W9LYRx_vws{%da&}bFK%oMX8aH5+PBQ#Z z{`rXbN-F3UFkFSCCE>WJg3QP(-KtOdm&BB;5n&elt8HlaF)r#6JPkOkO3-SEQ)^FD zq_9?1(LzoK-X|h{kH3Ups_RK-D0t?vIn?Z>7KKJX41m5+u4NFi7KEU(oR6C?$h+w# zaBgikXjE$ARUfr+;pn=fJewEO{6zc4^+G0(^g}}r{}^I`u!on${*j^(P(9(dII)XZ z&Uf)YQiaw>7!pKdA|z+8s%fN(&|lnFidfh+$-5-Ai7$w%0$T+zmZ_tDe{kv|2W3L) zfjWf}3^jB|Z!p5a8R%GH!T3~7TG;3)w8TSs#1JiR!LQ)%a+jn-$az5f$@VY_M_n&Y zL$hq)qeUE&zxLv+hRXU%Ac9J0#pjf<f*T)`Eu$LkzU`j)74>}t$a zbi1RUA`9^hnMj)$)M9J=4nX_jS`7^hLx!0ri8q#CSBBXLX@$K<>kY=ITjty@e*dly z*t)U({UJOTT@l`g7cp*xZ*eoqbh}XW$j?Xdy2*UC&alud@3d>sJMB)89c9naVedHO zn@Otm;@7VHx7LheJs*QX7O%4_(4@cQbe!O$+GtgVn=ZRrglFF@G6=teyF72 zogpAQrDk)FisXwU65nOict+YWD|S1X$;bK-8Mmq1vGA?bz`$iC5zxAF)|Rea6|Ou_ zg%8wyrfF@@O{4sI6m!id5Qq0&0ThQd-fz@t!G!GM<L_S_`) z5d#r0*j}VYQqu)Z@Q)GJ^y++w`K3udi+4t>>*peFn(y-2W6%%(vU}vR5}MI61&TzQ z5?q~~;sN|Z#g6vE=|rGstskhSsXdPxMr4Z|x+EXM^3794#_uxf*&J515^)Wu(4NW~ zyak}P8*xU%W;?lgrXBo3c5k*(qTEg8%+EM zYosZi1i!VMM^8i-#|2K zJy7tYA5JA33;ABFS~H=v%E3$G+gm!I!NG|sZN2DERQ|N&K?n517bW zYkMjr8*9Cs?^O~58ck6clEQ&SbmMGx3#C{5ry5c=3&Yxg{zz4P2aF*Q^i8swT#`Ng z1?AF++4tDXCT`3Tkxnr2{A%a%BgvNACz9h0iE1;)IC@Z#gcaw@Wy_w&9*YhIvR4bA zB~xU}is|n#snMo-Y$3|dAB0#5JO?q8~E?j2>mcl zO$cEBe;Dt8JOU^SzZyhm>z9{4S}xqwz^lGwx`Nj9&?y6Czi z^;QV*ptumtpQd4y#6Al(;Q2L9MdZ4Ct=F5{`Y94tWny!}dJd-FGwIYivdyPhdvaK> zT9YA1GSvBC5lg9=Z#7-gZqI$jb%cRAmZA?YIVC#*q7n3N*)CSiMsk*QZ{gHE}S9~T7I0?k8f2Cp-} z6KaY`)?U<`#Jw=PjadlroBi=pn!unf-~^B#rzZ095A1aj7G9h8KJ^;=&ifRkhDG`k zz1CCZF`;mErp1JTsb1b(;x-$JmsV5uolOS)(BWA8EmSxY&8sD-Hk~sD#>8IQ98rwL zW<%K#y55^~xCR7oi}`4%sYZ8Q@Qwc#QJHY)S(VCvm}PgE&MTCToln{e<5sPjv%}n4 zi109u-2ka?$BNMQWPZgfDpro%fsC;IR`NHw@3{*J5-_)Rq@A$8N4~?OmMQw+@S*0q zbc=BPYuQkEyk>m0A-U5z;fzhu)i2q^Cf>H!O!8hXmlHJhKp6!>^m9Ets ziuJJv;XL4X|IiqsG-~!UbWxAJ!PrI7T!PtL%XfcNQ5cTSmeYCd`s~iyq{c_h5NkKi z%~XNV5hb28YhF9&R(6iA1ScaGqFsWMjYYr0ophoJ3USLuZq(-ewDsir_sGfyw|<&1TiG}o7P^IPINAm@RD9RuK@7jDAyGALd*tFpO}&f>db~-YNcs8W>l*%_ zRb9&bNLt&sILigx9=(<{Vq$X%wBl=zA4;`sKG0#E^|m66mH}PdvbXecf2}b-F?NAoth&xjiA+#K-_8ch$TQVsL#?=0Wr9YCWdwEr{?=G<~i7*2_ED`IN_v*;4LVp_dfpPr`v5%PP{y z_#WOf#{xa{CHetmeglN+w~zFgCXDqaS+|7(X4*1kX6F~D?D1Jf__TD^#xi+@5~|Zv z$Pcm$>@`inFF&?DQcYD#qoSxYpCUS0^is4!h&d9Y$MhRVUjP@%K-W>S}#Xmf-&iHBR4Ox)~=P8Qt|M2MnnZ2W}JU zkV(zT|` zyO}D|Me#%s{|^H`l508{GrX5z_#8Q0GoJmfx`HRu=@0e%gY-dMq=C$T^E59-roU=Z z*hFxjfUgbYL^@Cg2tT5nIgeHD-pC_NhXwTGhEQDn<|z_JzlGf0+6TGTSr-zT=^t~c zc13zHQB6pGT_x0QFos+n2N;*NUr$bX9jVe)wE5zg+(pmUO7Q!Y8&$ zyW~L&?Ua`Y2VbAb^?BWT$=%v*1DNi+{b6xn2~Q0b*Em?gbBEL1sy$8$>{O@zy24Hq z`}^lLH7f6vHs!11mKnm=jf7fGih@eUH2+y#{-{~i;Wp!}e@8wi@(dQ*SMlh4pQ8zVrv_(dkF0vYGL2MEi8#8b;TAlk?ITm#>LcYev*; zImJEZD-kzrbr93ePqsIT3vlPK4EfqDK#m&y)QyFO{CL7neWlDE zCrN_`gt5`PEPB!-DMD`Ff!^%=47Ypay{%^#xspL4U^*0eKovcgs(ctlrkEkGEuK){ zy!9gdQNPfnnCfs<+9ug})xWF*E}MTM{AkB;?3$h(sT+mgsid=C5+Zv5Rkr0$B@A56 zK&^XuiCdXj=H{E>_QV@4Mrxn7;6T=80fhaC(ZT{R>yBMtfV#Bf%9Yzo4F1|8)rUg1 z)a~eNS_z2Dl=D~oD3CGLyC4g*Jm>|6i}bO?uhRBHYt@Th^aa;~Rv`tn#$y>?d_U1O zc{@tWadSNUmkDk|HTB?sOk@YyPcy&I}ByTtnBpA_#(h66#E1sbsg8&3yRWoxqJHrOxCIh)y@==LOfci0 zMgu0TYrovQ+V=VGqqP@XEr71B|0`hG{o<{0J);1Cnp(@~qR@z54>+rL^-SKX6KUgT z&?KB58Tc7i2ny=f-%mIP?s6g^yIz5m!JbovI&$x|Q#pJiJtyi}_0D5?zbiP^C7qs7 zsrw|O0EY8d>?!90DT%*hInd2o$%YcEQlX(U?7CMF|M}t1u6UI(uabw^bq%BV(M(FB zP~n(9sh9g0vy`y_87MoaZa8u)wxKOnQ0)dK8ak2fF}{ega*54*y@3eTz=}Z5IYfMj zBqnIAnoAUM#puyz=vYl6;ze22wU|tfU$NQ_EeQ@F@+?kmGXdU+lr6n!vYIz$T7f-S z7PXGwymvjH;1=u6(k}5|4{vTz`9#c`Hxp!4yD!o7Vg5v_Yuk-QJQNvzYBP^kGjJ$J zSVKYORy`FUFXi6ukJtlXv2)AUn20jPfTX70ld!}(%FPApdM8yk*JCIBtwL^D9+N$X z!hw=_`o5q=HWn@zX0cU#GOCvDQNt?cQY8*%O7Z%C82faTM}F~=NorDcf5Dh8zsvtx zJ=kK+wNdKX1~)yy8)ftetudy>B9a6P-$1LRctsAZDo7}L5q_Gen~zP(1IADA2OK>6 z)>Ov7%2PFVuqad#C%~bJ8$4%v5mPz&OH|vkbAg%K5FRmBuT9EzDzYTp(M;gq!4h;pY0!P4k<}n2ni472VCDJ zQJ77HzKUst5OIaOtcuR&i~?RYqrtr;DiiN|QRw-H53~7TV^l>XfpuHg5qSzbNM*XC zVw{a`k7e|mcZ~zk#s>W&E{1@1gHqaPM2{m5X#W;<2N*gksB1^hK>G$-k>~=NenxP7 z>k(^Fd6nVRo62hg4z|MtMYvo}js%tD?3S+cwlM5D)O^?hz@dtUGgmJAt5$Ge3?rAl z5!}iC`gv^YZzp=rVGiO~x5hBUDys3M1m8tkGM#b{o?#t^BWA}c(v5M+lyR!1-9DIS zs^%(oVIK%9rEX2_xw-j9x|i0O{!1RWlynN1;MMxRRkOf%Ns5b}`7nV?cK$unUywT+yD zQ~o3eB47S7^wqEQ*=3``pc64b4#}V zQSHXE4hj%)-G!50QP>DxU#J4(&4~6<)lb#5;#KQIO>#a_T;so>!WNPH!kW6_Rd!V* zq}(go<~I`$cr|2<)o=Cxhp~X5l~E@<^?(mFAjKEq^?^!-4e!Y!?cI?49dU4-3Vq66 zMUco>w2;Q#E-t9gNs1>9fyp>I=e4qh(ofBnX;crnDyuz zxuQXK3~4qTN4Ll0E1eQ0vg}*XQ4k{?_Cn{fPU*Ycy)ZD|37foKW->4B1rFpNi6K9_j0|UXk$M zMKj=z^_QkTMuLH$4=_k$4CnI*pt?@$;E2UHIyn;N0(Ezj-n2?WXK%omUbW80;U|jg zqE)H;2oi*7R5RwJx7?5+8VEl5Lt;o5&sa2;c};BaJJgkkTjwVFbe4`{(wy zO%nVoe&%(Uu3ck{z?Ma^oh7;*&Li6ot*qL>5Jt6ZI#!2g<>y&ryL$-Gk&y#Z^Q7c621mEdA_Wv=&yWsg+ zH~Vv|)?*BdYR-b3ph;Ms+Ot3|94$Vy>5|xGuKNW;H5oK{&SbbKdVRdqT%fR!L^+L| zKJh3?*?GTS_ZG_iBK}8|tJywAAnl0@GiNJQHb#;4&&LR#smCj)6P5@MUMLcvz#RG-cuf$&bvKcMKP zbfJgMtEXxjU1Q4wnVe8W3J2k(HBfef176?x5%{5-A{hMhb%Xj*LI#P9-xzzJ@!6adnSyjMAp?$%27e`f7 zAF2lW`+8o;@5m-3@&D*F0*DB*c%K!HX5lZd1bF4uH*omPo%8yAg-v2%X*=8}8ecw@ zEvps>Yj;4`-qeCI_o&4FShdLLv|VHmnj9e;-}rwM&bTtwL|3~j7od3$L%z}y_Y%_&3I<73v~RG`jWCCS#I{#R2}d3LD7Ubm6tReO9!&Q#;S5!H(;QVv^*JC zJb`zc(_~ZkUEO5GKXvUBsE*dC>SMnll`Y&P&;_tyrfOMIz(`;^JN%+D2)`3eLg`Y7 zO1pk$lqqEf)*I&BAzU9D`P?6%Z7VpOOf;4_(qGb4vHEmPlhihE(39bSPAy5iNoyBd z`9EMsJS@5(EyA*tbplRKy}`=K6qioLav&XTz^CY7aho0Pv!gd$U{O^m+UlNIZfJV9 ziS_Af$uazOym-EHczBKAkf&ajo|f4P@LL`*15D27UG2~sXRlL*e#WVeL%ccUIpg2aV5U!Xi=HLL zTQ=sZ0Nyw!M8RLFM-Lu@@%bYUZU4ej`jzBZ%r4udY>^QWGFad6KR(FZI?}Opxh7D0 z2j}zcX&}vFjN@RZd3{LWM4SKKx|Zv2f9-rF>ilTrYRNUq!@ zyxEhraXLyS(NHzAs|{%I%WoFlnqD@AXmzG$_1SYE7(yoHGC8-DOdvy3ijs8Vf`&$!74DI!DSiWcg$W2c1mL{1TAV5oSrh zO}E^RVe@oEl#1B*(LUbEOEB|`^9=>mN$^c@15QuJ^Tb!|+r5(;K31)ZW?mj}5v zXnz$$^nA^W53efzi`vknVX~w8o}nivBcUZjtSpmMX!f!ezvekbC{x$bG8o!BdE{`(T^ENIC%Jl(%ZiP5hF)>n-KVHsCt4V0Xs;OG9;e&Ukytgi|=5za)q$A_1LX z*ciZ<+bY=BmkOBQPzg`nz$d48kGM{YTx$FW;cEQLc;oAW&-F|K3w>^Q8ML{I)ilMbo4?piEykb*{=0B z;~ii%)xD>^XN1^^(klNJL(UqXfG`57!q_4+Ji=Aww#XCP=IlfFF?Fv{GHI@Zc#rcH z1JzL=(DKX0ric;C63Kg`7LXepmFmts$#0UFT$(G1q?qo2c!CK8LjusX$E>{1IqZIu z6reM-$Xm3+n9M8391~WkCr8K5>Iq|IIXoKyZc~T^1M-FkcIlcaGM4-^5x44M=Lzre zSD)p$Z^`P`7=I?(hGC&LVt7L^F;gjDpdbz@Puncf>!db5*S0Xe_CU0L8L!_t!qGLP z1RS05F77);YJ{G;mhgzs-(}#LTtWoBb^@)kK8Zf;L$_-2yv$JR>gh_!KPxU~w`NU5 z)5+(whc)2sk)eQXCPN3o9@FBL9DBn190;7QWE`X2_Z zOx&$WnMzLA>AvdW-I9g1#yYSJd=GGy?gMME=9gd{`r%A%fBM&c_Mc4@cw1wqM2>Wc zk)pAnE#cA8do>91@wVYpQCR;g5sT>^`@kTMjrEx~#QQLnH*PF`(birjX(Kdg-vRvL znWYulc&i?rPd)k`h?XCi9fm=>5hMQ8X-xc!EenEY0z=h0#rc|8ww6zo&t#2{xfWOA z!a4NX%HJG8>awrkPZ3N`LYR_<<3{fmbf**60M!q1cQ2kuHW}VXFjbuACW9Z=9zN9r z=%tgZ=I4HVr6wnBXq^A^Vdv#=f4Bm2Y30ouN`>hgio%45V+8cr1VHk9<)1cS$nSYeeyO+>}3gCn{&G+6&+EYqQ?$ ze2Um@pV~86j>@@h?JEIV#gQ2sQe^Ns(VDe0MZ2}*Up$I0U5%j?h3zrgKv|0_nK>w{>H1p+8my}!yu5&);{CH8J{Y(BHr%jsWSG)y{Jx71CGE5 zta^Mct|WVY3PMvP4$Fw9gLzTqOkE{mw;be1**>FFUbR=N!2$c)+5Uh&hYwm#3m&)d zpK-wfG#k@^F6cm{=!8C_F|`V!VMW6fQIZro0^IU6Td|u!X4o{5P2s&zyLzuDU8*HN zD{HZQhAxRrtiRD2GsfS|Nffh*#Z!i?9zzDJ(uvRXzy zU^)|~1)x+(1SwAKN0Nx9mExc*eva79<18Y%ut4eXW}P@c!zG@inic5q`$I&+JY^Qs zekjUQ#|-R?_mW19dtqO`%83)=GQve^A&_fDJJ_42K`S8b^40)|$2|AI&FK2&>0SJi z-&@By;l@&uCimhS7P0+-1}b&*`xp2;+{kVs7RUIfsgra!yMTFE_P0#hg)?*bU#Rv3g`D> zMYI+m*7Fq|DJ2m-t@}3sFIsZL_bV6y^1zxM`r?`e4GlTH3g4(PAfo5qttVV!w&kdN z;JKi@L_*!!ogrwbI(v+--P=&}6!t54(!0}D7~v~Cc{cja4T`iQy`(S|L)<6=N|jTr zS^lk?%A8RX*{wMjf9IMe)kdoNv2@h?#Cm~fR$k~5cY;}6b5oq}A`{pHKP|D4{yIS? z;SyU{Th_+{OAyltv3A6fg79x14?!N!C_SRGi@c8IHxEQS*&CzMD+*PWB8>`2&*qBh z>AQ)`$E$=y!@5MdUo>{y{aYr(v;fwiDXG!A%G(ozCY(pfjQ}$C#KG+t7hx50x?!*i ztH`Dc`dn$7#Z*8gHAWf|jiz=6hgna6J%z-*o<7h}GO8nX14&gH+aJOM{}!=F;JU7; zETx=V1XM*s!=T)Kqx}&jaB@lkV(kuzS~Yx#~}Sq}8Mejte|W zmJ*R$@2ypf{Pt;>$v`muLPtZN2SO%49nhxOK$M!!%+`lrxeKKQmH?5q1WA+EEGisr6 z{muNbs_ob@Hip`*khTtA2;`;`(yS~)ZeptQ;w;pQ`L}}xe;>g zaX5qr{k8{668L<{39Xt_KF#wLRYqz*8*Uj!q`8(-ksLPGp-~7nf=`aQTt|u%CTr%5 zB1jM~%Fy}C*8mX{7Io1{cW5H00!37QN>UE`|2jbs)6kKHs;wP^BoKZ}`=3faxLv;h z;Q$<}rneL&UjNV|0SxCWwd;jC58A0tMv%y(M9S&zKjXX@l(l)<~VLp z))nEU+EA|xre8>V+C=hP!;3ij;RGttunxd0h90ty-M`eERJCah=}hCNuM&t`ovG0v!S4xK`B_w_&W z*1GuiD*fjKP+G@=zbLN`_uFTz0FJ5J={Cf3fR3iP3@GIcLutHZDYX?@!k{~y?UR(P37Msa0{+f}BChu*A6J>LB4v;j8@J}}S~ z|3fk#zhHm2+~2p3VF6dGDLS*=`%ft{|1s0)=XM20-`kVHO6}n@1}yvi&xh1Z^jmrP zAk2B?))e7Hum0603PJY>P{~I`YNP+PysOf!prA zk}K9nH10jD1xOQ2VN7?P)qX37WkCA7AdavN`AdvDzF1{EmF#n*E^eNH9v5Fr-YFtGcYzPM7kkn1-Yy@pkM|O=5)Tok zvC&%mTIxP^tpSfq+0~4hzhc-;keH}yY>#Zsxa`dVXFcv&1CPaVwzQ{}u^i%~FSI`#x1~@z~-VQ7;PAw)LRrAE-!h5cmI2^6SP| z7f~MjdY~)Z9D(|$4HR6sU_U6{xQV}w%^#}?o@hpbik&3N@4xU8j)q1+~>yKeaogO~{s}qH!7}ragTIFg8V}no+2iAVs z3IY7?l|?+&m+Ma=q$>nCshQlM#(2^5Gbaj$OPr@)2%cY1qG)ecf2`MEuD@LTOd=3t z+26hyU32jy`PX%Skqlg>g(mH|_yRo}gSM)A(J=v_x}!T!MQ=^8~}I`#VF_YL-DOL5_%=w_(P@|AYgu}?P0)6PDR zV3KYZ5jXH@3`_{)nWWqIUcr)wa?PPVgo`RQ6ZP=qBP5Xv#TQIG9VsE4UqZ>vV$gqO z_J?t_1v>XPR#99xW1j3uEvFRwt-pE}L7@R*5}J$>1Mu=)<<=6C^=c)R z`4}(lrTbw!N%?4&fao@=D4tnr?MH742X-go4IYd@&H;+}6lud@6?4_S`5IU~wK~*| z94DMD;o-m9aVlkMD{coaQIKnP{~F~FV(qfr$vUB#mjlG3j8=6W+W~ZUvd-6}L?^eA zhnLk;IZIY=)G#X_vik#XRkrNx9e3q9ChR-ugl!W^dw^vM@`fnG?eRHs^3Jikae9P+ zeq*9^mu~7#Y;E4b7hdYHpGeh|zOxFG-%W*OszSZG%%5i_+rYhve_J!4yXF^L@BB}M zwKnC$wJ)8nUiQ~7&KI!SYJeo1njch_;3m2O^@k~{Mcw+@v|ovPsXhMgxf_)WXvlme zy`W;3z59_gP8u(*?;gPu(DLPf7-#7S3|$n%-HEQrMzT`g{7dbI-b|`mZ}67-FB0X1 zw$oA12W99vEnQQoHIB4PNszSkwGd2NKDQp|AQxbh!~cbtT3Qol>6j=tpu_0hy!@dR zC$_)3N!o8YwiTuG^?w^f;?ntJ&QM>*D7-)28;-YeR}PV_io(WCH>^|ca>T8OzV)sW zthF=?T;GZj!V){)h>L+#>Om1eDC^aLn~DU?vZugABt+(?VR_;8RhGX>`nT{ z%zjd;g6rl1=`X1FOo+cxP5$5vQpCOWHWO$aL1OPq9ply$Vv3Gi zal7(pzbVjJ#Xh8ZKDKeQ+6qT+x`8`+HN`U@P&fM|8Lk5Vk^1o3H!dK~8hR7*25Znv z^{fx@!nDtZ0(<8_-I_V?OhneC)_v5acmBQO3ZVJ0*7srsf|;j1Z?M+4tiHH5MAhwx zXe7QrOo96>Z8EdHQy;@93Hl_RG@moMQ*CV+^n}*8fe?Fk%ynG<;6~` zujn6F@K({uZ%6(`e5)S9K{m2SS42`i#1HRe?zg65Q4zutSJGx|YzDG`=Mdk)-eI2< zl-vS4q9i*LsrALhCtHx0{ejSgG5_-qLf!Fv@UWWL@L;)_N4U9)Q6BweOEj6YACVBL zW21t=>G9xM^+$}=ze}$oQUks38wMZYYC;&TPEI;UgqBUJylsfNYF{b})~~7RO9sUr zjP)if7rrL~tx3Fm=MX7PS$8rhk9V)j(cCHd*f+;8Cr$A02y?1o&Dz{rEU!1%aPbur#C zi*d!)L`vCI&NzgjkW!JLrI#YC%31^8JADsOk9I5X64Llu2V8hB|MY96jA7(|fMgJk z^Ug^Ogg;qp>P1Su`Ujgj?H?pEyd09^WiW6vIxppu07fcUhG?+i$|hecZ0~cweMn97 z!4&MiU^p4{P9MzmlL7&;AonS1IZU`zH2+0pT6jKCUyL7~jU(lFQfP+(CEhWe$5F1w^QLkfU-c+ZZ!=RyMyjsEj=Hn9loi%@;e_p}t z0nmC{n~6k8;0hL)>d0VErT6R6l66>{4N;SDPt0Uin7f;d%p1~=^6Esv2=w*GN&{UuSIP;V4`!rn9~yU?))5V$ zb{$fmatSh3_m4fXm6*yhd}^CtDhp?%*{h-UiMoQ58w65{wVrf9_tFYn8rBN#6EC z3~*muTLF!SQGH+7TKHcrEclHqMk+CT zNQM}f-z+F6=Xf8`yUainoXA{OLMxodsR={hj}uA=wPf&29xt8?2F3t`-Bz3~@Jf`X z5>!iG-7{4-TG(3&w@~APU`)wJxcc)3#2Z^84@iz zD;{I!)cXFi_H#fpz7Lc2d)fsf8h@DiRJKcx#TdM261f>^0A+$n`JDCoMemK#*{}xp z`kvGynjVOw6U2f$LRvu_HKvGI*VZMV$@?Cl+J_j&ZKd}U(b)>iq#@NNBhWc#A58Ns z70P)QZD|>&B+?LKFU}2Xz&qR1)zXn+esduSHwE6`(z&X)|GUc4kZ~V$g7$mwNSyj& zS2zZj>@cEBvIvs!RmL1?98+0i}?sihzqUZJ+jt8 zyWsR|`F_G?#(ir)XGl9ec zi(fvmUS~0_dG1zETVmq@tlde}uhbJ3&*<_|%>0gd?n+L6XH3fpdO)`E;&ue!#3g%L zzUN3%wNfpxk;zP~$9+!IY;vkxHgmsI8T?~d;5fSrx{oR>)6b?U>}o|D>Vo0zZJl(! zds&6w35C4$gn1EO&26x^MPzVfq-vOonEQPHrUupbL1Bz}v8j5T@=bO%u|f{%coMM_ ze*)(>gQd#Np_U-C2_ALg5LO*g2#&?<=UJZE#usBX({MXgsQg~av(h-#9XKyU$8p=T zV6Fj$OUB<0UN295x~SgkxV4`9?#41griLx37R1utDTUZ7E%18M(u#y_>uPPPxfq?O z^t{q)vTsk$)A#h?+2&>~vN53r28F%7wnqW{tCmilvob|kvsWhvWUi}8SExTdRgLim z;)A*Dfzfl=9CkonZfP-&&L>A*010uadhu%pq8LMF=z$$DO{e_VGuS#OrvLXubmKU7 zMWld9EP_ySK^Cq?!KOc31`ZaEmzfA_AR!2$sM)A^a$rm`ZL20W2Q`-D7_`T*fwH3= zR?<=x{e(t4&w;(}>K5jHSG`|RH)DetSBizc!yNkXqhMK#K`qmA-dH$ zIfwOnNBy4*@SxZ<%9g~VSb~6AC?}pvbnZ6457H$3D4BC5E`KPDxbrY zVDWenm5tm=A)JYbvta*NW5aqImoaGu9_%f(Ski-$U5RrZ#Tf-E)2DpPmlQzx>ST zTEY)VHu3vbfC}eXK>~cPvL=7*bQSdQTKgsVYMWj97d%z7!cnJ}78Uc2JzV*-_M&q_t=_b@0IF)M-cFo7bLO{*;KM(ehtjmKGlXq4vBUXp)GR zb`mv=4nHbr3*Pl`_3K|r=OgmLDdYkTthSpK5;a^dZ-2VhKdMm>d(IthWwF_7K& zA_RG+)B3A^&z-J<2rtf~{Y$G0IVCA!+x_MGS*J)exoqXdb~6M^OVJRy@PQpNuix1@ z7#-mI&)vG;ll#$ADAB*9$vprm@wy80fr0xSV6TGBEK6ht!Cb)WagW?qEK)n&GUchO zRZF0GJw5sI+(mPKI@zjb4cjkP#$Yf(H+B2hC^U;uC%04=H-N~;Y-6bzx76%Q5NOt| zB2}w^h&c+hP%m1T^MgCDBw~R$@hI&LH^-l$vf{d|D!qxc(kA+G6j5X`b~sug`xRVG zimK9}pHlgPMI>}RlIrg_!i>``IiqiL zQQED4O&`)9rjS*zaL)=abcr z+l~-oVS@(yOkk~&8C}0fr(`cLb*@g6&7!8V zx-m zk@GR|bh@-Vz8FJ>H=fNEG`0`H<$?RF}=qZ~Ri&!_S>v-83u|3-#>%ZG5p@$ zsu6H}B2Jw{QS>?X4V4ZT#WcV9E?DmI492_?csAx4U>BC79HQC)Epp)OB%nVn3j^I! zu3BZDEpZtOwABlfn?0*0tN{jPQ%E56KWX!_(AbOS_3ON%xOWkCLphcqh7V(R|au^LE@e(Mv4{*%%OQgXgUZlq6|6 zi^umXNt0v9CkSxZeoPchi>WR?*8iY}O~hX?3?@*@Wj*}^CYUua+MS>LagQFyaW={E z%7BKZkU%dEfzJt4>^F$k{=EuUP%(RVzyeNS%$sG1ygXm=&EKMW0^M33&jz42j)HeW zp@}NV!K)Dl&?UG6L^8zn#}Lf97u`tOP^@gBu4R>S(664iQZ0Gdn{oh*r+Vp5{vFD3 zHtn0FvgXcRrT`|8rM%W^gp)CH2y=q5letn$F=1PR&M`~>!$72vF|^O|xJh@AJO6%o zbd3Ggp`&kT@Z|8#(Rt`?aBnf~vS?*z=5D|~+M+m}p4xV2(_1{g0nI46d9UZ8;xuFC zO{(O{RLiD&zw1jwAt3LoNc(6i?byDTHpn6dgxvfOV~0D0KTF)#dg&>&^3xVkOFTYB z_X`k%W7(Ebe-Pqeu`P(eNBNyq6Phg*1Krx`8>OXk3Rzce&n+r6b|WnoD}B>dKmU@N z{uNaw%lZ2KbL!}JN%Bf&U>LLugp13yKwdu4tP#2wNC#jl0yJZO8GX&SFWU{!&WMdiguEnTr zx%L_!qg=e7^;WixAv=Y$4=2!g0ed{5VuJZp$m2SP(k9jh>@Z~L{xe0llqrvg>A=0X zh2%v{Q~xld0+~NwEv{*Iem=ybT6?$%45Lgu8e4RkD<0-sCQbScI&4i`IX$i8-U+Xt z3Ez&R;Y5hsr3)gFtOh)$g`gp%MfwBE$X7a+^E{Ryk3XyD z8!!Pm9i6Uo{Qm&`KmxzJh+tj*qLYl%@(?&gEGNLXZjTqm3YTLq0M{weQtI_-X#V(e;S)Ue8`ZrGXcF@xja4TI3pu3~IK zBUP82Doedz=0xRzC)A%!Ok;-{uYX=(DLeL_U%-`^E1)A0#?%VPzLY2eGf zjzF#Gx5K8neZ?32(U7b1JwDDXoT0pvU4gS@qYx`pizG~z2`%yhQq9uG2>MDnJ5;Z- z?DLMf%tr1Z%@ zz!34wq&$qem7u&_NgASTV|E@-igxi&WPg-wIm|L_u3MLgV=E0&&K0AL ze;LWLzpHQ7nR_UZtdpf-n3HW&du)D!c%vD%LO-l@L`U79PvstdZ7g;XRytVT`$njL zr`Ao%HaoCnPzK*5(lT?OT_|+8=*8<~w12DVO}Mre^NKOyPmtMkFHDWdDH>O(R82`q zHc6XvhC+cavFjsjGf)IbcE{Fau+WF{?A`2J@k9op`#sJ;N{2yhO4yo31E?gVY3BBW z7c53X(MP1Ds0yg+Fp0=oAu%q^$E+=XgP7Q1XOi0l4XhQpd*NjTZe2dqT|=4ByK?8s zE}+8;7H&M^Si2eQ$~@vaf~qgGl5&OAi~EIcP@K*q3_aQV;wcP_zZ?h+I~Upi0QSLD zDwrvIL#70pWZ9g?gob)rQa)=r~h)_jSLq{*dXgEcN< z1e1&t1SQjvCSUPKAD)yn@t3B5r@#+L3@&&JY#4%+qiyQS|=+;l4+!!BfTl zR~Mz7l&Vt3>XRy-+DCbge6c$?KlqcLcSR(CD%PgZ>- zB3<$4xqNyt)WVljZeiKwXNd~YjL6ku=5$5v6KaoRY+y3#_pPUbG1bMVIP_de+KHa! zM;9_xCfpYY%YD>cS1evu*iqQ#&7@(p!T8LTe^WSLhd9eUMbkreTtW>vhB3?yiO}{` z)KpOt?`r6Z8o$`G?Ee6N7EIwhOD{PtTeqdn3A;+$GJ{9Z*vq5pKEZyKY7D;0Bma5s>NTiwGGGOU;z zFHu3QY}c`#LfGAZ_G2XB*lU@gjUVYbv$MUFa!g!h;ZIp_2(PmN2)`pQCy}N#%?}Hf z6Q`3hgi_!`ZwC#rp;m-X$Z1ISYt`yaNKk4|kh8_aS4MV?v9}LBz9p`E(&A%gGQ&4K z$xllVr$DXEEFLA>8}W1TIdj=>ru7GDhA4F@d3TG1LDEKl+#k7PQ=>vzjVP;=z}>hu zy)B7ovrfxTOSp36g=msoN$!al+E&6{bX};}z$Yoao^9t!(Dh#m{{WOW`x5bElYWpI z%#9XWRmGUx%=UyEZneQwT>k*!j<4da`jx4~=O&ce9*H=?%H|1}c4oqRacLK|pvTv8 zC-RRmrh}J%K9Y(}K>|i>S-PCzkdwCEX{EY-@#6VRv6raTg_?039gP_t%c*M!e`Bx5 z_fuYBd{r;Ay{pn|58c_M<`CZy>a&vm=nmy&_G4CUqHad9v|6;qEyHp`Ve>W!hSJGb zG|~$i(9TtvlP3&#V3>rRHix`1;WD}eC0&<1KY><%ci0E z9~5G4Nax|yx?#PMq?w6s^&XgR9IYm1<7nBK4*;WX3LAY{6m@v`w#H|D8y&row^5Vg zWNsn;DD*gZTT`8zkfj?RdOU>FE-16bYKZW{`Ja#1iOh~l*wJ{i(f($hWT?%ymld(W z&4G}A=wambc$CV9%;qIiGGzFFlw!HjiLn;;3vhY2Z#b6@5KDMP#>`P$BD=*F&cXU( z{{XTkN2_E%h9|1UDvCTF;RZ-00XYYHK}Q9;LHfVN0@(#B4q3qGetW;Q19=XrI2;Q1 zC2l3;a*so2?|-@e7z>rqXZ@bMe3?7Fs3>rMwX)N*`fTPKsWzzD*`#da^0Z=n7dp>k z`%871W?D2cw<~zAk3+=fY{2~uLwV8mh_e!7nMYDN8zx3s6)}nqVM~e_n9go&`tc%W zrxnYW!yOz*bLH$bdf`0!qf!{Mg5G@MGT2-wEz)}{1{v#Z(P9F~1;XPb>KIvV(l;)D z{NUntSTxTv%Piq~g4&`BK1=w%=o#(>j4qt`p+N|6yW$YVw{rV2QyU@e*%Ik>u(|AA zL6Gjw*r|e*MQ-g(tKPVMPdk4M1&U;0V=H!$xt$A9O|y28y};OkMJsK{kUNpF z8VbucIiHFyB&v@YZFxYqxJsj@)55=h!w7gTV?o;sK~f|sLhTUn!JxrHJbw6K6|hFc zOGNuo0-)+$6oVz=`%to!F7NQ`3@XBdF)>c4gzjc&1yACOj^R83Ec@ZBSTn=yUAb%1 zZ&VykhOG<`+@BFcC%GHk5eXKn24|s%8}42h4anA&fuoKVTEoOV*bxd5!~_$6{m|4H zB)T$D>Wg3|b|FPe;fvG;WQ(aGs-o?nZV_`*>4;kO0xQ#0>4vJ6L&8~Fw{-D+@pAzx zDT3?P5?wSPhV3oV7%In$_+kPI&ozot>|m`OVlj3xyCK#9E3642-e|D_yo0xPc?iL( zY&x0UTDdPisFI>+!ZJloc|}-%y0L<5^8WxR5Ser)-Z)__c!JJcz8HYC>_`c6PYf(2 z6ha}Pf9-;(Y9LE@$|@se1X3bDhs74i3b}!BMioHO#JuI&Ck#z251CN}A|bC=#S~Ih z6NAUE6hfj5I6k|>YzE|T7m!NsAByN1QEhR#2UV&TTmIg)hS!wa6lLkK4H8$|fMsG5UHi4g?VOF@eS86U&p;eudl zVY`c*2;ibNJVd@P*^7n)axJS(b5CX{n~<#WaVN9<;8?YBU_J051rfN$3c?$xQa669 z5+t@=7M5sWc8$JK`R;jtCs9y(#TM>f*X>08Q-r?X5$wGP3C%}KzSJsn33?Jz)C3E1 z7%0oIDuagTcIj*uSJvj}m55G=j$O29T!K53P+{apoM(bh=$hDdo0uxTNG44##^^)4Hr?hS^o zN?HY!Xc^dzdxxvV;rGJ^tw#BEB1VJ_mtwc6%ZrDq7)27~$C((yYFzdiPd#&*6gDG3 zDvTzkEdoI=v`|qS9o&zYm{r#A5A8&#x{T(gJzjTR!^H*=(aE99Sh(&BF2?<;(L5hsbC7p`BZwK6ND!1o!J~9>Rl=`+8R`u_KxWvw+1GFYpp)Z$aXU(%^VSEw-LaN{J#wKxpYm;T-kAnfUA66Fqm`h|T zQGO+sM-70p`mrbm%3Q$xsu&Uqpz%UmDH3cU>@&fjj&Yse>`ug8O9>%sZN(DcGwZ~x zSFnykOS`p8h@wN5tWzqOV|MrI?&dJxRF@*&!ze?Nwh+YRj;8!Z6Ig3<1cETdv0H*>QdGCP#z)E}iD`Ci3H&#TQkv*Cii%hUpG%TGe_L=|FUmoi*2 zOr)2wJtsNAlEsN6nt#d`-oz#;nyye5sak@1MY}^agu85NNh>Y_L9e_20_Mm%)k_3j}*ZpM@rCCf4UGPQJGb49CAsAa+mJUO^U+6HnmM9rH z!LL_LFo0fryt+rGBYTh$aR8^a;uy|B0N;8;CTv3%bf@@wV3Zn;1+gbR@Vf+m2!`a% zEGu|=x}m_h2Zavg{4sE*eX!NS>d(Uk5u-}5+^rwQ7CBE{y9k9I5-w}Rkp-R+P~{MZ zi-hioLlFoNP)CtO1w{*p-Kdq+M_wjlw3?(TGQ_e3G$Ybb;e<9oh;h9Gkr1UybeEXt1YdSR%* zs3#jDEd7{jgfDl!f*^TzEeu83%e*(BPIStszr0%h2-Z`KxlBv3BEiYE6bZ%w5FTq%b*boP9IQ3$}r%e3i% zV(d4V9t(f?VQK>~2Ug7!mXO959>WQzEepG~wiQ60c0^Q!OOy>nb|ESm7bv}G5;5l2 zx*-IJ-FAPo1yE`v2In;J#Hc~#i@GX%g2TrB@HHa@X76WSm?`cGsPqRd7u~}YFmNr2 z0ICjy`%zG&9X8r?T;johsPb8%6ZoP8Ka(#102DO@WhG?zd|$H+h}fRv=6Je`T|Ovu zb`?@Vk-FGwKtqsiczj+i7Y`MTHyy8>_eFs~Y zRO*cig4?Js_=hjW7bsFDf=lwXJX}#K1Fpk1HvBPTQEDwE$oNx#hyEBF5!hRD;!0O^ zmkDy^6yE(J@#WJRV{O|PS;BgHRlXNEtr72z2S1HYr8^u<&yl>2e#N+6W`_N)w{6+S zH0oHY&Ji*N15)9}jLjUo!l~hlnAp`~Y2j|F$eIXW;*9a^X|>0cyf-a9EouI`>qLF1 zV>qf7N^ZiZtCD1Y<1+js?G2?0eolf+EaNBr9p`3@kz2ICtPkrHIpYy?o{hChQ?}S2 z*C8W_&{i>K%qH-=*1p+_{g}~*lW}=5GIkd8`Qr z;6NV|Y3QaAk8D>EM ztQ6uuX?)|Uhw_*yRWW9klVi~Kkkw*vp9h+7-W;jMNDiv0b;eeJdca*c%rIWiGyQWI4eiF-Ic$N>W}K7F#)UZMNo}vHt+~>C2RA>>mzx z#+#C48<`Oz@!JIobf>vDFvuac>Ji)}ZNBKbfWRs=^KqGn#j+&*s5LXY70G&G1mK;2 zwvNAMG~+Pl8cn#H_ZO(GQH^rPGNVBr47y`5Wy@sreaw|YqE?9(Og3N6F!JAhCjK&P z=+8zvDAV71qFgOWwRsi1LaWL!t9*^hnk+I;9xBB9)gBid`%%E;=Du2;oF2>6Z)`Cp zGO`S|o*bdRJpy0Iu}I5YHz(nZ2%>6#K9_FN_Y63>>^*F54B)I9jwSMnh8Dxgal20V zGFW!oY0Ii1JCYR2NzFN$4ujPI@<{0|G`cv-w~`MOA?CrX=#yoeZG=!O$nmka;e%34 zY%3`QFHjk_Rm)X6qMN9&jk2Q!5wZvDibdQfuBYxlq-1M?ETsqWGX4 z1}f+jxb|7WKW8ZBx|%V`XRBK|N)_yJTN!Ic_e72)DL zNLM?fozH`z!}mI_Mq=4(mCI0v_G5)n^BXigo;rF@Zq)_}%UYOye+tKcfZI`12IOXz z6>50oR^6Eovm2&13_}`!Q_@!nJ26Lb*2j)tjBWQuM~wutxfkUSr=Q*>#mIV`v{1r+O(A+8RUAKw~1_YFYI}Vkr?`sslQ7!X9)on<>?*#}O1_ z&6h@$T@;GNOMLae6mhcV9UUgY^hd&JTH0L1`3=EKX@3fo9~pkaGdi6oH!hu~BRvp7V5R*mJtFp^lNZ3ASi;;L(sPqqX?Tm)WloQtwQTGZxzL9=y}#4{4Z z!VH>$UFQUkV~+T!)hadY3N5_y2UppOY(aXp@ih+IDQQTg3r-E&!nfMeH1V(Mi#Pp44+HlaM!1or1eW}9+Q%?^M0%w z7RZDNY$HX`^5vhLdQaiJOF3+LkITDTtna@;nzs7{ytf%6jBt;#nMc+hKa_ceeYIGw z`f*U_aYn{}XPB3>DRMVT?{}BJJTK*yV=2+$`m>6ve7nXEBY@x+4Y~1nvxLlO*@@_e zpvU5_JVG!r8V%aaYx&7PTv?6LVuzG^T1*(HwGehB*>}UZL?(3pJs){LulZ72tWQ9OJW|Gx8z*qu&6eF-eNZ|vm5xx25C}iha}Sv3J-@U z^juWxJl{Vdv~at2vM8f;dk!>NPc@q^iHWQ>)blxR?z$fMR#e2fwpFnP$K|R`lA|HT z)HcVS=5ROScKBSwURV7do0u{!*ypc?I8XSulcV~AsWs@*+Pq7-3x3aZTz)a28Re(g zZa$5Fpy+<5r{RmA$AS9w3I71n-V373kL>k8&*Gcn8go`fx9HH_pQ#UoZG=DLS`BH- z-^e?DjsiT{_E+6_ux;rlRO?BKsrusmzg<*@Nuj2M&v&X}um6gfKaZ(J+{{W6Uc#z36prRx?q-umR-$F?X zGDQ4P)%GgjNNr69Aq)?l&{Zd@1;RqM1RDT8rHW!$7y|f;03icuKu>TO!3c@x17IS5 z0GAK=qT)0;R7i*A0_-w|O5Czv@WQ%-WE9a_kwq(LK@WUZ;5lP0fnTpoRLMy83fp_l zOPnNNu^hEQg@}&f1)H^tmKi7z5JyCS?pi2DTfN8@_6R7q)09B0IBZB@CNx!p<=t#9 z6^aysgt;$<8(`&ww#C|95iAOX7HvI$I-=B!J7!4~8hVP(30-_@LB($dMA~7pWw}pc#Dc9 zM+}`Dv|ksCsw$#fY)pxJR}2YIfOmQ5yd&YdAn<4E^zf8GnY0XBxH4FO2x8WDWdz&N zc%syo(4bW#&3VPtrEP?>bQz}?hwQ);(+D?mUkpN`HW&qO_F!0`&9Y0P!77PoB_!!* zG`U;hfuMf2%eyi>8KB4JmVKU$XgimaQVy7cauPCh;ULc6C~oA=>PU7J%R79cM{+2B z#y-zfG9|tt0x4f+9Iy6&1(#+%t-I{O+)U%p7pW!OH$XdttiyLHXe-mj_IscUmjbl% zUk~ubOz2I3l{k4I=@KVl_ZUu7(ZkIkqI4``^(`zfY8Jq34iHo#436W7=cX;We+*qkGYn?thRq9zqErEklh<63Lux8oV(mJj0?J6r zmh#ky3pph!-taO{{wOO7mnCFr_W=FTEDJWt7U=#%q*aRS9B4NwaNM61NpS8Ys0+(? z(*Vu`4C+{8e9lUL-lf7&F_;}eS*>i`WwB4Is8gBx$%O zvgm`T{4lF2Ya1<;8d@`L!~XybE!@(DmO+KLLF$ao;mVTI?82-`_a8GZoH+~z;3$m- zbUTxu!wAU2^#f_i8@w>Bz%YbN`BYf2!lTnK(m!=52fgY-iyoG zg?i{k=^e|`gg#wRnUJP-KW0aRP8EPj0`fYSlP5j6yXb5k2)&UNUn>C84sbX#~0ayhbvbjCOuur8d3HZ zszy{^LAnkcxSJ3ag3jegbU0F_W~?@bYa0+5>{DyQyh6|4`{QmV6;oVdnQ>|75enhf zI+$E?`xF}WH3-R9i9Rf&tB)vg8Y;C4aH;fv+)(s=jA_vp{HeFZoG|#EitaLatmnhU z;)@!wpe{LR3a1n>Q?-nXms-6y3;fQ6j7PS5-lET{Kh9)g=kvgsYs4;_< z+_u#4mQXbyu_P{WP{2oVFAl%)L55*}TjTa(6wDE$LDtcQl){bfJd}8#SRigVgn4{D zQCpG#QM>U$GZ4+ThF;7dho#F*{iwRssqA1`R!=qMuM9JCQ#OSXlIoF4#l+ac25F~? z3dN0_5gU>iqUI_!cvN}uK?I--N1uuyS1>$Ie}*9i30Zb=0b>N+ft}ZX3_|sP0z=kv zh*^TngD#beh}jQZJ<&k1ktMuVAuJPl<<$_1F67UvLWmHd$ga1hA!>W1q6FPRH62zU z1`v6yLK)%^6o?A8%x*1RNTLN3p$1njomWI4iGjC8;)q-*hQ`y)Viv-8ed`9GZVwYZ zDfpt|SSfIgjkcokdoaadqIMmBw#9vt`!H%VsBnY4@Wp^WlSJf$5D8Ldm_jY&Ux()q zHBw$h>w1gfgQH0FBw2Ff(jvx&jxHq+YSM`HAf+ozmfNlv08~!tY`WNm%;*_6ZmE0( zsHuFUV($L{3@t`7j3%uw#RMj2WIanTJd>weVPc4XN$#B@_aqn* z5c_;RQ9w^{L|W(N7pV>-XPF-BqUOYJV1u7@2?s5g4t~@^4yD}S*O#&}2s~u1;!pLQ zKx{f>x4t2W5aE&Y@j*n8cFNj(Jvv`#*d*RCXWA@Sxq{${eMR*>dA98-5sUb|Cs( zP?U3v5nw{82?*a2zFhUOw~LpD+3AQ| zI8kqSSu6y`ir3gvk}XXzLSu3lh5^xE$a+4CCO!0bk;Rh4tytTCmfIX`{0(@WTpZ8- z>CYQS^kJ`I_ns!@N#;bB(gC|j=43`sH99eoU&Ph*OdCB-e%F~c$a=1YSaf@Vmz91% z^H0SAVAt4`m8c&0Nj+JN2=twsZ)H1r`j>YlR`rsa4dc|*WzCCb)a0~uodJSzCKATx z$V*R3++GvNOE}|yX5(jrp^MOSV!5$8JIpeBuz4Kf;pzR+lG$K#;8;0h(_m<{x~p{Q zHrn4ZHyICy;g09xFy)@6Ilq!@Nz#WYRi?L(5+^TaJNuJ`s3XX&(glKs4M~$arA@XQ z;#UgTlHr?ytULXgqBaOTBbVnKT$wJ$GRuUBr#)x;xDLdBHVp?D&*Ax&-)-k^x7y*2 z7`fR|fOOfo@DA6Q#MDOB@)qWD}vnnqgNTOVddIBCj6*u zp1ln&BNk&M(kku+2Z%V>wU21lx_CCN~YTQ3LH3_l`yRMS3gt3M6SSFMybv zCg(9T?_KRYq&)Q=eo-S5{e`nJ=bqW@#DJ%N*HaHV*@)UZ%Skx1O*tlZ#FWH1X-TbM zYCQbWyS;mOHni5|Ul3ffRs8jd{I7^McJeu!(rMSG9u?m@Qq9deEC~`^D4y=<)2JK7 zj-yA2Z)YbsB$)CdkyRN|r^z^f zh0;0NQKb%ke-foJ(BUVDp{{f6Hx~Dr3@9UOP-yE(!xXts)ttwzKZ#X;_Qx}l%ItJ7 zOF|pp3C{)>*+Y#0+c#2lM17kD3qRi(xi!a#-Hn=9w(L%ZQxlu>9Yk*8k2Id_b`;%I z*;U~L{wUcUguD}yt=a-M<;#XNpfw+Vw%&T6#1~P+Ah*Hp0v$FYhp)2{Sgk&CH5^;+IY#@h&~uiqCk(p{>+z=~?z7mS0kaR8UlJ$E~45U^g zF&wHnOnG8QVyCgLW}CI*jf%hx_^-}1D%hw~!a8K0%nxGM*!8{Z62&l~Zs#)p02CCm zl~Q94;gYCM3aIdSiWb%_OOteew3TQ<2ZTq4Gc$dIs56nfTVYiQXEi70F+_>c9XTW4 z;)Js7WKD?q*?faLVV%J{F3rZMGlHA(k^>VYYBT^62yz&z}jkrA+B z$|~DbHSMzL?1%M=h|sc~)Vjf`0g+u5F`^03k|>9fGKfQN`X%iM@P<4YQ*(C2=MPn* zpLA#CrPR^K$)mh*{X}X~4XSkz-S%V7joR_JO>x%HScyeFzj1S3I=~2lKB~WE;cQpJ^Ear-!vS!yZPOl6o z6>#!11uFQc#2jtSl;gvaHp=cM$v+e+{f8^G9dok%qqB>ttbmn&u4q4s5Z>m5ix!%o zlY77-@T_KTlI(47MBGK&o|uweB5)ACEhDk`>DixIQO2LNil*X!^97ff>be(Y;msmW@z`-}+&`I1Y=ONZwg zslKB;M$J*_1A$qo)mtEV1)*&&3%W^Te*+)L=dd~-xxh+ zl{ayD4Gm*L`c73T7^~6~4$a9-4o(X)(^hd1o%t+!Z{c)ne$yxBb1b!#`V;75k7clF zxkC|kd!dyAC$xD zH%R1vPo()Pv`Kl2ElOfiV0UlJlA#aEFdF#p(rVmM>zvNphBJ>Ula$i#>WD%19hD}` zRsojAh0CL5;Soy*E@PBrd|NhQmjeBbdWiFsnQ%yC1^|{TNBh*pwS>f27#R=2Z9gR21_;2+sUHg1pTc93~xJ%o_eZM=v{KhYhr0(+m9?hx)_A zhQNoW-YCD&tayw60BAs!poc4Xp#K0yrT(>ql?VxR7`Wno(!W|I$~$AFu|G<(U+aM- zG#k-IE;!1h{cusIwP#4f6%<_ZwtI3ryrH^(QA+J1Zb8gqswvi>h_3L#3GM`kKZe+- zosQ@NLuur^MHUE$GcH`B5CI631hE8hFXD;PK@NzZ7F|(81X?1UTu`P3M4*Zv;o|wl zSRo`TT)of?K4z!4eqI=iE*$szSO!p7@-4@d4NI`L*}F)S10gIMa(VRK3YgG7iv<>c zy_1D9ja1OFZGQ|v%#e9N*r<&exkPOkBF7Dnf3*tjC~M?F`n2)LaKZ?8y% znb_)GyMGJ~NfjM3*q^m*34H^$+kt1;#17c&uT(8dRth?vU+}|N)jDFPbS;}^ z4ZZI^C>qo%Yzaz`rclV83-t6Sciw}44BU!k0`ocCs4;OQbsId+^N_}zKy^UEVJ+4) zl`b}OGSMR9bW7|3dfhJgt%leOIF%k8xVz$5Vta!=rG{9kQ6yY?#l-9gn;sG&bEk`^ z?}k`8_Am-5&MZ~r#_UV&K-88zBXnAoDr5%zB3K#hc!!TDDDE;RW(01#KBz5!!6r5& zfjgmc#i5PgzAOn-7AEcqL1Z&&7KzgmV48si!>zEADv3hgo@?QURBL+}$VZfAZ6`cBCHzD%(mF2+3|c{_z;;4iy}G?h6#v_5w`9;mxd}7yAD}@mEIWnmx=;l zt;r`NMRsWNLAnT0W1wJ_bF6x}xL{^XY{kNZfo%cFuI_u$Pqi0o z3UnGhBX@=gdPUR|l-s#~^M&pRqjxFxbN(2cgIOCjO|;2>!v|wDMu^#oaRbUUs+P(& za$HE_$g~FZsyGCe+XSS9A?ozeFzNhJttUas5EUvYxy*F_DAN)eAqh~V?$17?hQ#dE z6H+2+39}*bdr(R_T#cEV;3Yz}VUgS=VlsMfgD(_NEJDpqE0+|1Fa}YZ&)j}78+f4N zG!l)?%Zzwp=osouZe03H`arQ$Iwj;<&fXKx{4gTfNy=Ln9&7Bx5Z=Y2k30C@*tKN}v=VcuVN%4F5?FY0%GDBtSW7bq{>y`!DBa-6bbzPXfc-#7!RTKt4RNg~qN3v8~Do0KyyB;tvB zlEX@dAKnI|)%@XZ;6JeiP{XQ-N~5eUi5#7&YV? zEGV!mPbI737-VJzlYS;s0X8BjZl7iy9D>m_Bl{8zhwAO{!xK9W=^HtL(OJsgAL|WF z!6N8oF@r|(TYqW}c_euwX0S`X5$cAgC+s1NGlSg_;usvffPG>7jvKeAe}*$QMk8b_ zw0bfNqPxC-D8rIP>t%*klW+_oSQm?jZU4jfUBHxGhU~VxW&tc-pN-i8ZWd!9m?tEVqHo|RTw8b}e1;Y)D zqbx?vO63krpgRm+lj+JBt_gGjLL zC=%>XQ)_o~E3Sk)0e$%sk~J$-ZZ0Uw%xKb*Ow<9u$suyZ-A@$Po>8B09c%^3%Y#_5 zPy_DoG*A7}O2a|1E>0d<#gxiPNz02rh6RfyC5qQ0Gb)wXk(Ff^RO*RhD-RoH-cc+A zkL1aJMYsiWx`%@21UV$N_Ym;hy)baYm2^f<1*S;`t^?GBs(PLjha-hs5OoKw43ad% z2N1z7mtL4w)RsC4+(V(S@gvE1wH-VSp`|Xg^~6iT8?k$YrEYL| z%RV8Esq9LTvx9ID+i=5TbZR0e7_>-N#ST}0&@~u5J|7j(^=*mBplw%6Bq$U!fWPhl<9o!*-vU+7 zJuWTsxVf+02CO%70$^?d@RUtKLo#acB2N~7lvu%=!bElqFoPL}?}A1Hay5CpzI`!& za>b|{M1m^89^9>i62P+T$jgZ)u~9k!6F|yd;32p1LzRQwM2Op^uwB84*^zLQoC6@i zS8!*;6{!PqVX;Qe^@c+R9k(tFl)q{�k6>`SnF93=7wIdSJ*d^lTy!z&a$EK(PXK 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

Bdz9X>O3h~#_p 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

;p>-&N2_z1+l^5gCy6 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%0D_}?|se*5ph9L)y)w*-#5Qbihz&%O-hWX7 z8@%DUq(T>6j?zt<_WZ`*RJ<{Roc$*L9wvr1XZM_6iah@SW3|}ujjs$*K{!WWYAr}$ znFRt>=j}wX44{bGr^rk8B8p)KJHsTmq*mCvgsKU_vgGj$U5i!NpaoO3eb9T7Dihdl znB>WduTGs%hBaZp(q1GbdkmPWjeoav!sxPhINW^}>?K4whHkfp?h?`g{f>ezi~bpr zS$9N)tt+~GBM3Dqd!X4+cvGZX%I}H}`zagtms%Y`mdsh{E}|G3)N29SMm&_kcsg>b z``ez%!5G?HS)zZOF?xhgLGtrzTSlpRKy4Muy8A(?+Zx&XO6{DPXRhk#1;VT_y=%2C7SyU2t5WT#XWRro?yo<{% zV~hD;$Tl^rDe8ZR{{RvChV@&->G>HoqPM$gyLfUU@o63_Hch>crlPG%PS~rnG>SPJ zPF(Qt+wP3c-(k^Z@l%g>!GDRTag}(flH%yh7YPuR!co%WVO8p5i_YXT<@6WGvurP5 zDJE@{%R9$EygP2z7s=**P5ckUE+zfPM{Ms2wLy{=g`L^A!}xsTBSpMUigEPC=9cqP zAYR{BPZ2;uXpbs3sAK7whlzw|a?qy{nI+l5#~{k+YaOU^R!3paIds6u%s9-sT4d1NBlC^OrwP?##IVuuh)RY+KeY^q9SX2m zsq!f<9XwFjjeiNtB&@jS7AStm4WAT2fyI<9A{Dp@EvI&|3YV~UMj#Ty#un7gAk+Zc z%ixuWDCUclH5gNv?PU%SC}z0eSb>|K?(c(E0a-#hJQD2h9&x5y9-}h0EY!ON4#$o$oh6IS9X9e*~rik`$3NqH}E~ppDPaxtpY7AxR zO)|sQGd1f7mcSk&FNZF1&&~NA4a0)t%p-<&k+ug!*rCp|DCbkNS+%FRIprMh)@5=mFtKKNgz&*VB+q}UX_j?l1UJGlyLO^EQ^LWEH4 zSQK7N%rLD}d?Gfd?8f|6gPob$N^NqVyFAu9DN`L>tj#rPl3NgmG zeq2y-lqiarA>H#BVz$e$qHQ_&po$VaS}X%t8kw|jiPQ))8=Dk|pfS>TM@*NC#RA2{ zhks;l4Sx)0ajDc9vhGi{3t+4pcXqHh8LJ0mMkjJuc`n_ypb_vGQ+)*NSQlJ-FUS8BW zSOyw7Cw7YrxmFuI&jy3T1_KQq_d^9J&40%H${12W0%ytzTSPok<7leEONNLg#_wG^ zVy_@FXl_#JmUhFTz@~&bE8TBQ7T_@jEVq}ON$e?9aW2}7RLNSX?Y7AiSOyLlat-`2 z4Eh?Yu+5HVmvk+dC1J>lk^3;IQK~zYZr*X3iW~_2gn*VHLE`1b62vkqnT!Cy&42MZ z=?0bS{4gxL3L`_d%(*NgRI%G7!SO&1MGvQUC``b0aa--)mvuzz7>i#Z zZh5RuvHt+lHh8YlA&L&9X*a@oK?_tF&2)7`kdqoCrU!#q*NIE96*w`mRL=0kWY&CH z!%;-gVKP1OseQs#&^Fm3)*!!8M}Jb3^(d{uQslIaCg$}R7Gg5kou<5Q|K(?yb2R-5p}I|j@3BxPx$ejQ;&l5%vi7%LG}mXVOT zHH9`(nQBT))61W9FS$FCEXmriqN+J&PUwJ4Y5E#ZsinvpwtG$0H|Z1OkUtP zN|^eDdD|Cqr4$pCe(spNlYi1v=M_ESYzHy6?6{-jx`7W11l*kGoCQ==u_Y@wo%}F~ zP{*xIBL4ucCY1eRuT zEXorzp{nDyrmp`0+X{bhp~p7ic?H2Nk?j*rYClqF$!>l(#yYX>CSO{Q^7Z|UKDU>;Z>mBiRE`!F|xH5~W zbPO`KhOpIA*=%H;v@>29vgsg+;o>rIO3|nmk0^xu7GNUpo!~zP?vJ9Ew`?Ou*;5_bAkatmK`%S=hYXuD9>!WM2il# z6)N%*nZv3nmw!Xn3(JNz1Rc4ne$+67rf06^FiHmGblb{sYE=ge70VY;^#jJm2nHoW zsgh@^DWgRLdQ}OyTdu>I(GOB%c4d&pMqLsoWNV3O!mL;=<--bcrZ!-m6-<^8z@uyn zf?QChp`|g5-e)~+irfW3qtdZr-WmpOO69CwgiN$}fPX1ipi>~WW$xwSftWUM54xZTN1D35*z>hTZh=!>TY7%#wz1Y#9A&_n<*e9nv;%Y}MYza;vhs6_8X^0t5O>5P3 zO-O7pdF!-TWr3c=eALjV*@mfW?nG1`6$0YohJUJTst^;-DXCo=;VLyCjhOL6k+c$w zCOnoj!xt(w9v&o(5!@PyEe|#6hABYYwaq*+YRpDcR{Bm-du9@C^x@rT6n7MAh?AO8F;3_bPq{rvoa4*d&J8<@)Q?HxP|6(4T|n=?P^Dr_87D1VmKeU} z>VE~dFoJoPOchC}C>XFtUa;bb@ZE66T*0a7SA>H*xUG!fh7!6V1e2Z15E~x5)%&6n zB+H%A0uV)P6^pThF6a_i1ko*-tP+$nB0=Q@Fi4fnz_V-yE_(x!`on6}7MI01*?#y5 zodYpr>t!93my=`@hS5QeGqJxCXpxsIbbn721DZEc^sC56ulQj;lM=(m9v077O!f#{ zk0$pdiw+|}1@Ui49oV*0BRD-UMO5+4h@Zm(&7zsK7-qAI+?Cw&O^F^vTrovD3K_(_ zkvT*K5YKK76;BKgsAy%;fu9TVQtFGknV!P_vAsedoFjA>N0Qky3(4trch51iPH+Rr^+!+uOW$aAM^EoMS7YPgp zR1`C&yCEXk09Ygb2vZ>&Ab;R%Qg&pWFmZ%R0DxTx~U8m2f(L;utkT zLkk>S*s?y)hv0%Fu+jWzBhu$rOhUD=MUBu`H7lah0qg=}9B)9C3r}Vbu}b?HxsA6k zXK{PDpwdYz(o}ZAiwGnsvzLG6-{&1J54Ke@W}cIjw)IauMYNg8)_*!CvO9~0Jv}et za_m&4uxq4w5eMOdY%EE7k7aNvxagu4#S_zDmmyo)l^5!hZL;d>jM=g1a!ra^2`|nv zD9VW+R~0?N1onO9bOMz#pkf|y%8O){xtzySd zng(PtyT$zB>f(sUr+*UiM2nXVkS#u-#EYBfxUK0J%<>*8$B_*=Q2NTG&U0@H0FeXAoUF4HFSqCyAbo`?qBbd1V0H|G@`e*2? z=*K1K`-MhbmhE`D%fw3@oVk5ZBM&9i+U_}Sh)|QCU}clqI3nN{dJuo13_|@@g13ow zKZfyO5>HZPUSDf=f=~?KQ&t{!SShtnLo6RU*rQVO5^6mp(R8d&RgLTUjK5+cW6*X% zyOL75KF!@X5?UX3D4wetQ}fy5@%yCxA zr!A^dU9@~8gLP1EKdk(}NMT1D)^r*KtvTbrC&mPYc_vf_i8*&;$r6P$k_mwQ)xD-y<N zCw{jE+!l?p%i$jsE7U>S`moY&UevfcVQj+)_g>wd!RvnndcJTl;MiNT-J^*3ei*g` za7RrEgW3=5z(b81OjE_}<-9HyMfby$r|wp9hpCeyxN`lNwR4%84Lyk|`?>qroZYrw z&lD?id9F`G0hMlKrg9pE+swL4rzp6C12HczH9siWsyx%2guGWl6s}6i)TZVSGm~pQ zdPWIZgY|z2IXcvuzqy#En1#JU+K}h-gSXg5;Gd?;;b*Num3Camh`3aAf?AaCqMhjs z%$N3IORxxY}NF zLhHAiyoj>u7=2jkVa%`MY+N&m0NE1)0ut#W@W6l0G1n&NUkQNItHleL9GODOx4~x! zq)Lyz$1Ky@U<-$YxQYi_4VaCpNZV<&H+`6j#GxBnkQQXfh;A|-AHEG(2~v@Xu!|K* z2O2Tdg+U^tB|J>|u&zbx2k3vcDZPg6X_;q;+eTkNE-oCikD&e^o$BK1K41BcC!vJh>j~`Rjx{(Y_b`vB%Ulda&%(*>>LfE_)vR{T2_(i5o3+)!3OV%-&u^Mq^9kSrK zc_)f8vKpbmOi<;fj^*`h@bO%+QIU`ag_3_VdjhvGx9-|6wUlX2V?d_kkabO(%edvM zFKxadj)HU8%KSb2NIAWij_{G9w2Q(~p?T(M{COWWvw44xyyQ5hxv_`0}wa*lp%w>M(P zLQXS{7TDd)pjfL4;CkKgrt{AG2OS1^QJz84@3P4Uci}76& zgdS2wqIg#BIMauW&IhijUvN!4DieP>nFDOI#6(>>LGmyzGpG3MxpKGdERQnXVyHW@ z^-b^z50qibdR%z)UhurYQsl+48=P~5z8J*qvE{CpCa*uJ@LR+~3mCU9*nWaVE-KLI zJwFsGjV1ObDg+eMZ!-NPxQIld%1=I$zl*RtJ5r<0OVpe`%`ghppjMW#SJ!{@R1Dbn z(#HgCr)QG;3)>v!36d$Pd0R|KdW9FcW83~GnAz8;`H$zgE5^^$)nj1ny~<`TnU`Xn z+8&?Kzv8%FDREGj#*NW0F5W~3m-g=lEx%z6Q zYhwE)nQn9RtDJgcQX?C`C~qT~u9Pc>z*y>LIxhs^B$JR9(VIwpk?M>c&X+`&DqaRE zKscgYZgG1=F?Og5F~k!R*lZ>qD+F5A91ed%TmS{cQ0_iE znYkRbLsB_KtnGrTGfoSvu<@Y?4;91Rq-~eQkIdt`HzB`i&^s0MVV9&+TQHNlaI(3} zZg9KuNJHlJ4wfD+EDChC$^9SZYev{RCUWTBS8&`#^H>DKVYzuLUX!Hoj>W&Tbk^N* zmZj00fNm5Ul~X=l@v(m~q#UQ7qW6dDO=0>|jXW$FyhvA&-0wb+heOn=to0MKn8M>^ zcVNw=gd}6cUw4!xvZ+yvtd{ZNASWE_3wU8OQ(E--4Oy64rv$S*RT4$$|cClIdlyQY|1x3kj&iuoq66I@LS6Y$kc$s#m&wne9tY2`>AeqBb z&Kna2x|#(CTDIXKj--C$&AKpx8zqJ?2fO#g z1%;1D!~uf<*$e=1*$6}+qjJp>Apw)QMucCeml3`%d=-B{mIYF+%24IE)3Xgrg|`jm zX&1>Mkd_kitK?SR+OxHnyFCJ)?DNhQqU)lf*hbXgH^{*Jn}1={FvYlLbQtYes;~ls zpXi|H4xQN~Ys8C>#9QNO{?)(8)!5T*v|KLP1g)aeeI$KRrX0I0c$#-;9+grp83HdtRZzeSH8wI*r#nOyjvklYT8(SBp#JC37*+YLvCBCG6N>_MgoEe4Y#dfbFz+O;k zBH4CR>s0%$;kq7-WOP_5)#`c`$r|xFnbfINr0sB{b|)9|e>;tCQ;ZoSaRjh70>L*4t~V_}*P5TZa#>{~MlA-7)d@{6%7 zU~YfppN0gq_7)AO^ZpTt7fOp3`=YEx>N0Kg#ISTQO=kcDA%tW}Mjc{-Ztmp*Ham$X z__I&73?r$7ZtYefp2i4sz#^U@-*Dkxj^d7#8*2s16I zXt1{pAaO4!q@svA^o_8oV(2+~LDn=T%zl5++XydII+thoVRtP@47MZTgv^GaL~_v~Q#&rf0lRfbpoIt)L!3cc zfXz2+f{PV1p~={A(Au!3HYjF9M%;hmb(X934%NAo2T>F|ZX%vh6xoa>_={AEsKph$ zO!9%CbrTYWsDtWQfel_SY7P)jVT9p#Ly`*Ik?&_=XAePb)G(SKSD?d@giMjsO!>D95O{RH9cVUW8Qt5xBL6VFr zT?ZkJV=6yS4YX64s8i(T9J=W;f2bQap;woo-P$qgvxAK`d)clJvo1jRO1DSDOti4 zwR&MrgsA>q;SPN;nNqbRq>RQ|CpcWj%TiKSdeO~Gh7`yU{$8nh+jJ?@L?tEX5Rw+w z83;*8&Tl+Xo&AOql5dFHnMOtmV3L)Z@`_|Luw`)v=lfA!kzK*OTgbH*$+3*XdFvWc zMY0V_T@##XMFG~JdmVrJ_@Rh^GY#I6L0sciONCh*ILSAxU!|~>bTXJ-*}_{8LJ7_f zbS0NTbO_#j;-N}o-J^p}sANXMh6pnm#ZVcI3hjCFhU!Md85xh+fT?Pt+?uos99Y@M zzhs~{BH4iNt%E5B?@ezPC58^L)%Pt@$Y5$v)yInbp>CuCQ(J$%7l^;gF*=06`(ZAN z;r*CHsa6i$!4k%7rK@OG_^;}Wts4l(R*JEkFG(Brgx%a3Ms{WiMKf?)V|PZ_rEQYKauN{9Akqq7FnQd*KgAmqH<3L$ zpu^1W+Z5ZD{QZBJJwO3_RlNDaswGDa&kuAmBnDy3tnoPJQKcV3?np5+c8d&Lqb?Mt zJmRM)O*=3yuQ*dU#Sy&x;2FiR*~hH$MK)4Y0ZnoH;Z2m43f$G&MO+n8$juf|rEz7EmQKOl19$j2e zT99H%NfEl$x)_pLu=&0nA+-uvaLbDfWMC18jHSVsOa%fG<1w9-x{=i>K_k1oU|n`m z0X;9fILOPPgnN!cUI|~YA$zOZjM-o~RXi^dYZ)0si8Avy>(vVwz?^JMb%qrxGT*W~-CZZ6<#2)qJ1f@&ahN5U{`+;S2P zTBKCS69kPmT$@~>EWnl-6Yu^Q8wa=@jN-Nyz%zdqW4EWakhx;fwhy&kr+h9aW|k0B z9m+AOXF{fSG?YxI;- zM zo^XB0CC-gX0D~6`jI-*76%%97ELnc^V=?~#ZTNQXp4dOm668LsNL7L3%?t4t4>*71 zvgqj9pnvs{2wa8L0xs4THGpjMPOGAwi3%BOgh#1(V;)BtT8kV;BDxjU!W-Pyk|oSZ zbPePyo-9NHd3Z(wUBM7ougcCCyMs|b+JO#b47ehP76~_+6b*`4W5tV5ZpelwSpLAr z7V+ked{974=)>5^M8_i6#9quRPzQf-Ym9|Cx%)90ik`+E#})=?;6W&ACA?@Z295+B z#kztVW`Y@SASw`o$4zCLIn$I%g3Y5Rv=kKmWJ-drvI1it1^FRGRmn2K3mySzu5|oR zH3~X}e{U0}?ij7eaKkDVHc5PQIh&j>Fh9CBVos8w-Wam~0Brz3i3$@lprL>E!z!h6 zPPE8`&k%_r55lo&BjLr#)}+}CkcWps7&-`cuz3ttPdzZ&H^SQ5KPWlr34OuLV#XO< zj6jR+K}Z_#mqjB7VXyl^-UXLi z{wQSu8##@)XZpxG*k^Em?E`-`$sF;0msf+@N?lvI}&1~m0GORl48?*Hva$=RIvz+*&*DWP@%A-#Wa6Ta0C>siMUlCy zEaU)E3S6K-V3eFT?ELtRwuiM9YL=_BbZkFm68MK3Q)L)ud~X=%!|xbGyHAlhPPo^l zdXKETDZx^hCkM&PH7?U`h$X}$Zjs7%*~!bvdHEJ6kM^N-FfD*wqkJ^77W{c*8c#diwIumf6~BDatU#&^`XaMlEk<{XI7r~E7_|M5Y zH&Yax35kWnwFvL_eeuyf&l8t(Vo~ZyhjWGaV$|Dq_>+HB+LvsZ5d!sBaYXJhWxR>J zU5})&ei$QoK&A_zd_=x~DCqF;dZtc)?dFP(E+$HKL^tf=YC90n{ z{EsoYjb`dKmMX?wr-JKuWppj`d_<%g_Z8y9m@*r#_@kYZKg8CU_0(Sd8GRONJ7}%9 zKUNpzaQlB|mz}&%{2`z7jwdsoxcXPf?EX9PO~~u~L9 z3{*H;kTMl7t7k-R{wSB(qXumknrrg)31M@oOT~W~*$r`@WA`^@A!_~@H8evsmHEjO zX5;p_t@py&bTEr{px~QhZx4vQn5c11p(S&eGT!uSY%RKr6vU}EWT|zRFNPk0%;>7) zYGYH3lPSWM+*XWR88RuUF_fIX9T9J`Z>}z@ZYT>LYnr`XT|{d&Dt_g>Q5$hsvi@cTC> z@`0;B8-e3BN`#s3ey^2yNYF@Oonbr@vbE=N1!3vMpvvD#mr^1kv_(JA!_k;bDNM&$ z`K)Oy+Y|Ku%P(r$LfwLWJV}~rk#mq~M|*!~D9Cn-B=I=uac;C7B1S-=nLI_v?JA>% z9BrDEtE{7bAs!t*aNg%n7vz`ZY+Nt2rm+;Y%{&5@@o_?#baXM~SMV(K8qv$LO6hNa zi-z9}RZUR)EfTZB^sy0NX%8350sV%m zavqXf2cE#SR{`mk9e;v!25J-0Hvj8>`Ou`^=IaA%}u$7VK1wW`$uahFA8UA3bw zms{>6kDl{5{*zSqe~rw!SIWktu1J5QAW3i8jFH&Z@UT}Uu{YH-BkKTD#Tuy6a-EhK zveIi3IDiERtupB$;?^_2A7#dGsO8FQ(u;Wdz%OLHF^%x((vPtX&sw<<^zg<%NOHD= zH#m-T^*}NVjDwE=_=KQBw!t|nj~J4O+CI?5?n-Kn-;-odBWxj%!qqkmsj`38Wx0X% zTq*-9e6{zhB1H_l2kD>}B<07HxQ)C}YRoOsU?UC6SSWJimx|aWxTYLR(`AJ3ZIKf2 z@bNT)VN~x1&n{{7>Zh!&Ca|Rmq?FPXihsgF6y2{Be==w$FghHhw(>4 z4*_Gy=JGl1@h)3G!}ApfnYmM>KJb)H>ABIKN-YsOLcggJ%9-?ZI*fmtK##I zJ(n|NW9VFNBH&u7a>4Ze&d8Cs#6!j6x-z>PDqN}T?(WELyNWXI0#$!5xL%TdfwEB@ z*zoan99UK?6PT5%3nCyuFKJ-KOXzCix#}#(Yd0R4E1r9X;_`||*i(&QdpS~Sip!G( zToT}?ZPt#7oC^DuKHf(LV>8Z*)}Z4ww`4>-o~9DVG>1WSmOVABo7=6d*=i)0%oo5htet4+K@ZW3Aq zD`_M-=8MuR1|9V=)R+$gu`4He5Qz@}@WusAofcM3lK%jQ=wp8(;Q|%QZNfq@Xnd#-YDHQEdnDOl6??_)sYJ+v@%4HqC(5@2kiCiNN<8l2?rZaN+exFMD z75$0q7NL(GDve}amOXc1-6Ta4i)+A#2`8S(uiWQ)Gyo1Sd%NNLR@sZ_ zotyHDfojcTV(jZ;OyPtRa?+X8h=$ne@pwy&^8Wyl{M%;fQ)9@Ru|M6%})fj5@v1z}?M{^%kYCv%8G zKD~&gaO}4Bc44V->eRT47LV@mGOrn@#1rMlvHrX3!ii4&A`(}%9C zry+m&jQ;@Gbq=QX3oV7&Dy}@l0!ATt%ZVX4OXUW+)jDx0O~z^dZgw^H(Y-Ken>~|!{);||TMF3RhWf9Gt1}Y?kt?S#w&(XE$7eU>dTWa$9B2L=<2!#_ zJ4+YLVT5)Uh-O|&Za99MGrA#YiR(u^v_;W0RgOQcmiDw7u>LoPkEvL??5*UfY`Ts> z=V(UQRyV)zZRAnDUuK-jvZLwAJFdVa zN_}!ifS2bFiP4U_DK)8G+3C(Y2>pK|$SJ1n(jKe?_aUkhy=xlP%&eqa#ckQcMfVa# z-mw6olqHBmIo#O97%MIf1_g<&iQTSQz=^4W&2&hqof5&D9R3&vVX5vfazdhu#85U4 zVD-0nV)q179ZT%Rhf@~YqQNK(48)2UqCsxRp!X%Pq8<$f0>N~NCi7ij_auK**pXi4 zi+eubQ{yW6DK5?5Yn-PA) z!KwvXTX#8mJl=LY)u*!xe_&aZ6Ev4x>14lUZGdHYc!&e63 zDAI!11Dsi;Xw-x>m~X3CIGqq^LEY?>61K=8)^64x9mqC$lo+)UF`**KD(3_m`-|L{ zC?4SYW;;HeDV$!Qxj|i@T%u24p$`nGj*9}UTA-4bwDf}95~GIOum^uAT(NQ3G^=~V z$^oB5p2L>q?oWteEZZWeR@u#XU`ePBES6fpOzb5J+aK$u24_XoUhm%k5t|YTND;B! z#ngJbjvL|)d|wOz(O{5F5`{#{0y_*;2-}xxkUEj^8y1caC^5*Q?%~P?qeSm5T2{9AY`4QpBV>7X$BSg%co!UTH zH72R@pA1!kCS>Gw6#b}Fxd}O0G;MQOs~||EgsiGBIL=<+la+sFAXObN3W`JmWK(V5 z5FX=(M$^x_A$pO}u1gff1#GlWFEWAbFu<1{kWuU`UESHibd8SZx0fDQ zK(y!>W6FQ#YF`WlxONhAK2X^1R^$uMf9>Vk30Lry4# z>ITm^qpW6dlxX28E>WG>(LKzz-MXn;8QsjmSP~P<+5RZZ>=K95?u3FKT`yddq_|@~ zMHrvBKJUW6wHe3v3okF-8Cbn&l&3S0bb!QF*eriZ%d^t3=!%Dn_X}~J`*Vmk!zs4N zd{Afz+?nq7q3Fe^5>DieG1wWM2ywMg_+v__w_zkU!6=xW9&mFDu8A-V&W6JB+$e>W z(OQseVZ^-AHhC;uNU4gFQnd?C9KPTv@wvn;Vh&ac+`y_roGD`@_Ffh0{c&n>K_tBVefTis)@Z2-$bV z$71D2vgK@9K}j=622GV6hLcV2E{xCYSq*vT&22!FLq%du4U}bpWh0s{biuG=yBLo|tnl6e*k;>@{+0 zk5~(i#3QzG=l4M)tc={48N{=yG{b{IwA97F@WDfp6o{%!TpC8SCrJl9_XdRyMGnLT zORuUls)1V(GZR~%!xK;)G$5xlxfFk(9~kul9!V8UpSLp1k85Q8_0-B+liLN3Bk z4votWRnQ}1>L_eZz?jfxz>GBi01N|_*cDZa*I|d#_&r^ zq7=$N;>I_OW}Q(BE3#|5J5#0>R-lt-)-I6>aNV59GC_cd*lK&RxtHqB=qiiUi~7C8 z94y*~OMxwkc5h zuW58wI(KtT{{T2!mAF2VNaM!alxv36JgN+}_qIRJLk^b7%@Dk~zZ$M9Ky2q8ToQ(-6070X?E|k|A3a z5uilO?HPJe&YrJo03WpL{`5e_s8C?SJ^#nUW26#!%KjDE?*l{2~ z+7T1Po6ZvcP|r*Wc07OX&^yt{6;LK@H!Y%C=%)LGznon|Sy%?UNWArPi&!;K8#mMw zn@Y9KUgG{3DS@!!KiW0-F7u1ofNVtdP+<1aot4G1uL)X?G(dko9HIEIg3a|Oi)uzd zD2{wlO@uhGV%m7JTD|)v7w8O1NrrZl#w3nic3oUN;fk6Ct3Q9Z*!wTVGIdH*&bv6n zXLm^w31td+FAtGY$Jo)DeR^TJd4O@TE)pLUTBgFx`eL|yL=w3P;?MlzdSEtDv27$< zwF%B23>5`4SR0!}(?>d@;x;OyVcG@8Dy6T(1qd~AXdvwig5C}&x)aqJOk=B4FAdQs z4%h?O;@fU6p9z0JG8b?TZ3r(R0`SDBARNa=A=Sh8!9oxNnDFn*yIZ)T{Uk0UVBE$> zVJ*SK@p;91N>b2B<7Gy8i}qld3ckcMjXYb#`!RJcDxnu04;QQ-_@d|oxred6&t!#Z zc#0z+U0)*yjg_5LwD9poLReck-OOyI#I6w(9AX(31m=GE{qGJ1fY#vSNT^g>+6_R`?V3JNh7v;qC9Au-L**@K{_4CW>pgqzGe zVFQ4n)9!vK%64(!xpeka+QRF2;lx*ZZu$G6?kM6j_Va3CkSEE%vX zGFYPQW*~n+ALuV?AT}b=!b8p~!r~8a%i3SgEV>F8h_$`>az|g+1jh0_@C4v^u6`4Sg zcHa%Cu(n{j0*ikPF5sAWZPo?U1`wPf@kK0b6e_saY}9NGEJragH;;xpyg$IUex@|1 z7U^a+9x?Q?q+?1X^*I@InVJO)t^O$MC-C+xeM9h78>HsBrbiFeNZGp5JHF^oBacJd z@Hu~XQuT-HNl=qzM7WEm?u^o>p}_l6#VuEB49Gb!??HbQGY%HW)lu0qt|hY?(;Q=& zj70N}Jm!PYKFUX`;$U1$Q|8~7=l-WHm&DbWqZb2=*o0R$U0&2)T*o~Tpw_sf5j!+_ zFHuGpV`3XO(I3%9b8eOItZGfWFNfuFA~JvBDV0wGM=P1jTzw~?{8!?elGbk%zbQC6 zn}=xa%1Fw!F2uMX007%=QRnhS`j2ai3S(NYiRPODb1OhlVJI?GH1wRiz$&&8+rxA; zOQB1~vSTiJwbWfu4H^Y;=&Z9mL>4bs#S|0b(86)zymRUj@k7v6OJ)tN6D&5+{i1)1 zpm?W6#g@v>D?O-0snLl6J?jH5k%YA9xa>x~EZK3};ajbSp=`qem_RWEzTT)Ffb2PD zaBQ(*wXmszyNV_o21$2!cuL&v7^GVn*>-BoWZTn|E_QbTvmE;Rd!pSmaJl~8Bpj@r zML=)tPZ*WFIS|68PCL%WEy_$f=J9{oFeZoD9w;sB3WFh*ac?43>1?iJJ&3hUkZyMkiOhdXwjw+p zCHF*}JMkA?DN~xQOtKD1;u?POp*_)-?}?@Pm>!Ix5>j;So- zEHgPO`*#GusPPX^kwN3hLkA-tsI07OFGQWTPkKG=rcKT9j^+Kt#}80P(SHrjz9Z&8 zmErnLW$8Ny=`Y!Qw$`>YBec9cJ+fv^gelMg;wJmo?&lo;0L<{N)=_`IisbqS%iL97 za&vf=+P8R0JmTZiRPN=^vZXl4xXr`W;<_ASZ*X#R&r-}yqC18lQ1vaCe`&=gVvk*y zPs0ji9qYHqv&-TMD!_iQMO=*ec*7Ggsft>NUdaJ|E-1wKO)Ho~a}G(KOX2DmL*g8> zu!UIO25ZCNg?eFbPc?r~M>)W@HW?XoXMBW(&_9$0q&p#mCW_S_FKg!))(qq@;{xT% zoIE^U&Tw$xjZu;2k!s+vjrV0SUuHBbY+XlE^)M?@M~9{ykS?RuAb2kMLC}l>V$I*P z?82D>NF(Md0LW$46;Rn{(qk}fo1?|-lxL2^*)&+|7>OugNF+Pr z=@Ib7Gia{lO)gvO3Bj9s@>1d`RL;g!x=4oP+aez;V!MZLsZ8{;#*N?|jLrjP{{V

f0*U^9A;)Y`)0c`Yqy=QHHK(23vUpD+wBx`QEt^c z4%+HDw*}xA_MwReu~Bi?g8PAuOa9T&d0cB%GhkMBvmGaf=t)PDZdzVVqI88n0(&vc zXxY{_UJHN2uN;8y$EWs1V)e0ZN>!^L+2JkQjc*$sE28aR>LM=K4*d~9N+lOPsaQwb`x?o|IpFEhp?8A$r5I(sb!*g>IJ1(#(ys6IR?pzRq#46j;fZAt=cU z&kC_Gxki0$*4#vc-333=E$E}NVx@}@DPnf-4~Su&mRz1De4MnzT()Vlk~1T$ye>?a zNSA*|$48CB*Y>9a@;{olhq1J2bQx+ZLf+?c--Sb_I%um0na!SGu^@Le=IzcVT(<=z*Y=s#&7!3vAL^@29K-wLEc9ul!o+a3}wG4@s=2%J?*R~5II>hSwH z!l)Xyi3&XNojg_UCqz>3iW&^}3o`d;5)gkKe~KYTrCTy*Blfti>WDzylmk%_5{O>p zBD9OlMPd|SS`i$OM<|38BWywgDR6T;giAk$AqeW04W-o^Bz94PyWxlo#>ze`*efof zIZZ9m^;^<0aXJB!z9zoJxGtrIrbHf>G6MeaVuD<{@**zX=v+T)4iqa`Z&k0#P~?Bg zVs>GvcE^h2;k)JM2UhR}@~U+)uj-p}p2U;IBZTD1%eXsZt=q4;BYrLi%PGx&$X}-d z*qXw72`TE6bNHrl`h)h~L2?l1{TJN?-;=?E`!Lgj&()ocsQqYZG~UC9#i~rP3ILe5 zacl36W>3voz0IGe-%9kF_ZoUQ9@2jRo1d>uJmZ{1?a6TbwT#(6FP3~sAB|b^YZ|(_ z^i$Ju0lZTf!lL6ZaEA&Fzb@#e{Ki=#@Dt)g?C0o*z!)aB^xal!V-!sb=uI^`S2WUl zljj4AopJvFrc_g}ur9`E_#Y0+%QYf0AmAX{#|VmYO+2BE%F#-s4%9syQ)+*hZeoc! zFbv9*>;2uvCMm)0)-m#Vit9pSMn|%mhD+$2!;@3vJvE3c(+^BAuCxT@q9!ZGI!7w3 zol0zePFLw>v2CV(9{3)YEa15GqUtj^vi%px@$5n9%#P!+dPE=cxxU+yp9*I;t6f=Q0BKp(m!!<@Gp92%^`oS z#J?jm@swd&*#0CVa8`=>Dv(-lE7x3F-66g7OzQoo0)&5&$41l7VcEF z_G3zO@lT1HFB3+)ty0L%&sJiio_UImvA+)~WahR8DwPRE$d)=-*<=R9L(emC5GdTg z;f-lJGvz`WYF^XUG(_mj3eYwow@Y|o<(?t76)XavQ42Xh1nzlATk0U<;U9_uT#$@U z&~{mgu}j1h>J3tOgfD+023F8dn)gNw-;%+P$&E?G%Kj@XY52_KGWT;dz1*@eY6G6u+P22ma2_9}*(1==qJP_ja5w zR}>uhg}a6vpPu2*$WQ3A{Yu4a4Nu|o6rYowlTzXx>ncCeHR1f044y0t@tHi7WHUvVq`y~wpZ6g#BG57kgP4S-SQZp)E=ar!*xKHv>YL9F1qV({n4K;EsV#TL2AP+Q{+;mylA+D zgl&YZJb4a!gp;T|0ceri3Uw+TqoQ=449Koto1qQtGGu=wt16+KXLqovjSgFHGla8f zrMT_3`g4R!4u}jL(7BC{GwxndCN?Y?70aAhzz*?g-4Lja0dcUSx$D}*~yN&Q@IztMMz=$v9;)~psQz*)UKv=%U0lz3BM`ITE3|{1u z*!J&|uQ-2<#MBuf$#fW^#Wvhr_aJ6v$hA&T79dG>)x8&n;*9PUM?e?FZb?vr-e;5r ziBZE+`S@W{;RJ1xS0b5+ELphq@W5QSof+Ku_+VQpD^nZ1oYoE`ksAuzcG|rw1ZxV8 zU82QLV?rRSvs}_FD+&_NRN)vZj#e{uy2N3sEL?x@ftavj+m|`Vgd1#}G9(cZ^NQN2 zRW6jhg3r%LT{~2wQRW;p5DAH-xRN>CkI7DpguadR%yidNw#wqZj5|<N}2j+5AKFRO0KQxDI~1Dzx-t+__e_LmwyMmOVUpNx(w8xO^lqxINksMt5uTf4P^ zPt0+?Ob_Yu{bXo3Zf2EoP0GwmsP%7KR{wthoN8WvC-GwaHjg3fjW+95I$-Z5>twu) zji>EJSTv0jbopG9F6cwZOB*I+Wa3z2CW)CDQH3zgNi{1iZk{5AGmK&MyuN(wh0N>= z4_RS3MHi?avfZ%)VasjZtU`hdcI#|H>JW49hyx3~1$F6&3bBxK)z%=Xoeo);hz5W5 z9A3C}fn!QejBhUG0#(FWe<+3SL^#i!Le+LOd7kKCD#JIWZh>HxwlZ==hf83A0_2Jp zIDkEdQxueQMTjPYC#d%LVAb>=rZ+lAn892&1XO)`VqB<(5OlwX^NO<(l=$+@x)<8H zBj*mLI_!NW-GC~cP@q{fNK1+l>k5BnGem59C6=mCn+Fq6->V;HDrUt`xOsW2M=n0- zL#TpARJWGr(+K{Ml$@osMQXyd;A#oE8C$hTRm&c-((tScwToEUCn^@|k(HQoMu~YB z5hFQd#XpMJB`Q>vm1lX*HBbUTWyN_$gh66lm%BvihHGWm8dJp~sD??#45WWiIgX_a zh}bQQKqe*a4B^P2pvH1kNHT)0OWY9i(l*5f3`5iDx zZOen*3mUMXp5Z*!F2sZoPJa|#i(mxxuIUG1RvJAu-N$e7MwKmBV^W&$PYe(s+<1ue z;fOvv3KO(Fs7%RL0$QGF&Juq%Ct_T0FSQx+wMH{o%T0>&jNlm|-Rl|VY)TmtQVffn zDb$dIsWRgjTfoYw{g#Be^?Om4dxzL}D!})~1|vBl$ddAdj^&Tp;Y^do4w=y8QDy5Q z*W6UF2PIL+B@I*`V?qA_Tv4q^1=%qWx*DJ?Y-H!F=Nf=vzVhSBAd!C#IgmsxhN4$| z;s#{gQ884A10d~}c*ia%YKu|qsawnTqS%cMBgSqWVHsq#Mu4POLIMW!iff!I6oWT7 zUigM7lVlVwTcir8N-KTr^utzEk2VJT!^nyWSQ-t<>;5E%DF;#$YF6jN4n`HIEHv}F z--5(pcQPDu^L)0GQB=KDjj=>P~L%g;$pj45Y zw|HQsEHPI=ie!ZK8=)I;d`-e|Zr{g^0F)rLg3rlo&x@j=V(N+HwGN9dn2R`lqg{bUM~2X*kBy2 z6P)^D3mdsmCv|{sqSyn|1mI=e7O`Ih6~dPM1QnR$Yw4< zdO%zs`$ji8Ir~r^kvoVTps8uT{g|GS6QsktL|z=+x&?6t*+#G9>yI@pL}9CpjOq*f zd1`yI*XPh@g| z^bjkJp2u$UM>u{Gf?OnqX$0mpHw;xvPqh>D=2fgQ{iD&8JfTNeqSr?8a8 zTpUGoGf)dCp~aTygkus@VAw%oths+VtXHNQLdXa4Vz#3v22Z0{3 zCkb~Rh7)1AVlMH5^ht8PFxmsemiV}1H1-G^gKye}SSpAvAS6hPN|7%L@1j(d5xHFQ zor`6ETS;;mAvE8a4|FR(HDPL|Y`)zm)18^@?o(#C zR#u%T$Tkdu4YtQ3J=(`FneWo1v%_yLm^+;20_T#!t1uG!65!ZcLzFc$nj&g2mu-Y* z0D*+MRsvL5<(G@@g#^u^$nkQ8yn%5q6}oj>0--U@K9pXV@|!!In5NU_8Yz~4^K7`d zg=5ry7mBm|)*g}dV~nx)q(w^{Rf*Y&qIgKWUKsTalSbvlvL6lYDOp^|h?eBxfQQ*@ zk6tA8OK6QuvdOt<+(-mN;rOEuF&2DIrvophT5$bHrCx1By;)@(k4tBy<76D-j8s)l z4k2Zpk@mhQ^uLbcdTVAp-{wDmC3f_&c$(82VkX=>S#ZYO?)allOsgF#anU|Tw#3+r zcw`6F?ME{s?AeWWT74RJ2KLe4~K#v*`GEi-~HaiDRalxkbPsfuC}J=L(TEjipLj z;_n1Y?M8M%f2CrrF}DU~K!At@hdW53r}h@yMkyGRY==EKDi3f8dr%VW3ggfgww*0H zt*SZewCRQ;8J%=nY40skOif4jeVEc@n9%Z@Hsp-UXgr|jurWI}42?*peV=2>wE5o-e8=PRFwq^C;KY2BNK zdG^lle<;l?d3ugz`$WC1N>l;Hi`(IbB9g=2&sJ_u@)=iyrr&CRH6sFIn^c~dHxwvf z^7N6HNWrg|mo~o0@Z7yZNwj`U8+fyDkHXO=k;FxQK zviYQC%^Mz%uOpf9?hSI2SaF&olNBjuM&qzn@%iNmg(beD{1dk6{2*cvZ+9)_Ye(VB z-2|B^IbC0+jk|?^cBzH7nIPkHa;-Nw;h|c6E}H52#-)E#$S+IatWdFr$7?uNye>&N za_%^4Kmb;VqVkN*kLm>DDcGuM$;Y0Rt;>lUIt%1HUuhyOqsUA}O6V0=2mX~vnC%kU zku}x(p~|Wj9PNy{V7(+3`T8EsSR=xbq${Cm=!`P7hG~DG!c6$mZwIL!b7IH1yp_Yb*}# z(y;C7_bhfl9h1`OJcs2!ns=Acz3CArv^JiE`P z-X|FPYkDq!zv%C`8R~Hj)S7{m#W?NZCe?X;*M3ptIh=1zj_((fb?P*=Qw*G|*^7Jy z(-`k2%8@M6cQXXrjp?rxT+WJ!*v4|3X^<>gY=EVA&7GiIvKYSNZ=y<{j;G`;8?Hg= zx&`p8k6f%xN$n9F4_qajDX=V#jJh4Sh@|H^6~AhKGCv&9x%3dGf!#p3d@-BTVX2p9 z3ShXE2tFteL~ZtEx{|l-#m*AN4NX;YTM7r$A%dw8fcRD}g(xzp5$gu16!$PS0&Q|m zE4qds2v)W%YVQDW#Z|(LN1d8|%}(wpSmnUNW#mogJVa2z>Bl6caEeV!5@n>v@V(suja+*69H#kS?w|l_1}L z3SFrc-(?^NRM4K36)@UH*QvBgcL1bl;-dKviN&>b`=i$X01WZ(m(@Hc@s4Jy;$Qol z2V;0VF$OB9$)McJB!*V`4~1jZ{!@|ZU}ii|;rQOaB~<7ve~B8xv_}qLfwtva=^teh zK4Sj>%xfQL!m5i_Ij*T*t!=kxO^{fB-6g&oONfaezq9_MBSU9zs+-#Y?Cx6%(aAxZ zW;`VT{e4ErRWJKJp``SIIHU1j;)}-tw6qq=yWCgPIB~{^n@BX zOh{6k-%ri~00{9GesF|OP|=l_0`++syV3X2)oWOhy5(?Pn4G(MMnm7uHu}ncoh#B| z#l+w9^7$Q^vwR8_qim3EWO5?Uh5}KXb2}oNmkiRzN3zLD%Y!ohCwxS{_$OtC=`gA@ zv3|oiSF=xEt^pTOTkeY2RhV-^!t|GU8W+}v+waT^a_#Vm5cqx3qtm~Uiq$75bIi!8 zN-*$EGDWCc!b|dvSbe%oJeOL3+_zzUnN%un-t5o}lZ*O>%F@f<9Sl5O^&B2&<@B{L zcxM62VNelims@CjT|*mj;aq2( zFlE{n!X4Um4bcc;rbVAtd^dEE^u!=SWQi;+g?uKF3yIL;%=KPz3NQ|fin((bgfihM z@`yoE2znt8L?P&eJrIOuBYvGFOmXU)lI+ViD|3#L2#S2xE+B^@?d#GCh;`>FxFT2U zDY9E!n%8zE?v4Cj23($hT`F?3a}`_pT})DGJ13~urbefW>K&D#;~{*l9KFdMym;|r z@iP9MvUTTT`LbC#I;7KwMgIWne5u+l?*9NJbIa4y#3 zQX+~X;8FXSahZ8+fJjiYyNU{=R9GC~j0Y<_dMY9RX3-K*) zPc2GtOxxhzESU*#=v@mLvt+vi2sD@^dRp7PbB!q%isu?%@{G*Is)Cwv>5HCW&JR~O z7WE1ihXZDi=qP1>QjYUtwzzWh3c`_e?%hp4d=LtyUmsz4?0-pSLAiis!UtrXA|4#W zP%obhS~xbTL2uDry3)BdQma2ShOiq1+9`OgNqY03|4jn-^A+ z9Cn@AKy#Hg+Ty1=>1S~sS+-du8gc%{h2yXpoOe~7mTqOab_;zjla`R&Lh~z;qWNZ! z<-?$(2^m(UN!O*O9-O7nTxG*($ag7r?qisu`J%%sQol^UBUj?TL>wzvGHy=k8G@X4 zPkO_bHYSOGm>0=ob`Qv9$Koyj03C_VRfxR?Yo#pXw0e}CNQI?*8X~pVQ5PfmWU`Vfm=+*9aNcGNDI4zDaWnd zue~&mUT@+qHa{MtPCvzRXZD*v1N{|sS;cJe9wKU2cq$^)n}6OXfYp5amN_~90E%1U zr$-0i+vLXS*~g%6&bD>g=3AuwZBQ(Yhi6G#;}qj$`?KF4VYMuE~&!4ah&NmOp=#b03n=VoBsA$ zed*m1Tn6?e8?778w$!Jt2HIEHFZb;D+h2_!i`kzQQ4Y5g&ll%iOT1$gm2i35G?R>Ih^}IzOOu{zWPKjS>0oH`o{aZzeuJ=u@f`*@|1j)ZDDptG>&usF&|Z+l!xrCyN~C z_y{cmgrK=i#;9jw$JE;Q>##4 zW}TW2{5ZsZeo@a)&vBiqZ2tge=T54XJ>91rRC@?vVm7b34#0OE%Z~`ml4-5mVVF;l0cbuP`k#xpzUC|;4!vZe7C!wF{@3MkrtTmA41 zT|#uOa4e-%_1rncD9-NV&MId^1nyIx!vzzdC4fB1Dt)LZVpMERU7|*aostv-f-uDx zpqy*?VaX-BjrV>~@gPJ=Pe1rzY!yY5YrDyR3<+}^1mhlY1?mLuNPi4mM#8O(Ztdp= z4xsgk?}%Axz`Gp8c=73Vrc!5rTw-9YNrP_Ii!OTi&1Byyo31SZ0V%p-a4PBZ5kQyol~p)l|8xZQ=3S z8>xl2CpCmWXMdkjcw-^qa`h z$EPQ!#g4v{sY}mE%go9UOiN8=#wB*?SHiwa8Kx=HV}3rmL(LP-O79GJj1sN4@p6k? zuLf67u^IJoO-ZH##njuBh)31T89Hg&XV(O&x(zwU=FEB^pD0|zL%3a&Er zJEG1!GvY2CAUdRMRC=PXcPL1H372MS!Nn}2FFM{!!vi+6#n zF5Fe`c@=LHFKSQcH5S0HL$vq)2}%CQkJJbCvy6A|;1N^tb3k(2Kz>G4CXH*zQSekdE0 zawuHCRO76F0oVat2r6xiwZ z@)9bAszTf)9I_(W!WoHTMoqhiC_}I;#EiRq5slQOosv>A{ai6>IY+EYlrmDoSSrcd z19U`+i2(C`=sH7|A!socah=*0-yZB(mLIWp% zuK7VwbU-%ARP#u>5r+)7y?R}Tye31-mTip!NCL$EF7qntpZGy-AaU-i=tQJpdfOTBzqLy}1-JdrC9 za{d@}NR^!yDm$o3IrPUvj#HKiGV+ao$pI&FUQLx-Ry3$gqu?&(^0i z-xHCYL7?frD3uLWL)@I8UP{z~c)w;cu(^#5G$e(q=qORV)UDd2Ey-W098!FFBw))S zq2iafRE+c3Y{tb)%iRl>1`sc067ay-Wki&|aMdZ`K}h)wI88PTfLlD02BD&VXhApC z13{HNyStn*AfZBn+3GQ%RQ3!!L7lL`3=?RYcf!aeW$n9$7#wyR%rv*^FqU-!VTQ#j zCK)KgpD)yu8NAsyqChL?E`hATs`7%D1!y&S?a*bx2%g|SVVlOTa`(nZn-m0!-Y)p9 z$k^@;!&fz4P) zKM3_@8#q~Ylx{dV262^b`&47nMM+?6t)@tjsgl))vb|X>Q!q?CURnE5b_Y-ZSLL#fmB&i`ts*eff?8Q{J8cVsl zdo<@3z%rYw2;(C9l2vZGL=rG&O`5T~s*?Wz+X8Um={{U%?z!(Y;Na80s zDIfeWG}z0YEJS_Ic%rC9_Xb#i$;{M_=sF7!Scr>;?x>U@kL>}UiX{TDpRb6ylvk!) zIFGNFGFyrdQb}P2S}gNI>WWnW@YV=zR4$k*koVUme^KWWq!snjj*3@B>Ih@&sFW@z zLYu7)T~?HTRR(&34z#?`w244v%oTGp&h2f9Km?6)(9Yv==czc5&Qm1#uhXR{j8qS*0QCA+MKbeGf*`$;S- zoL@m!67DKww7MnGT|j?n8clAB^-vLC+NO7UMMhzNvE0izsUG!WJyC6&WRB!JgS~u_ zBO|cz4$I6uc>`{j;aE#Bsmicyos#+{V{NWdoSLOk=A2+k+sITzzFu*UH#Y_k?O00k zHpKG%9{BCL!*Wj3OkQ}rT`-UKz5$Hp%ELcH?m4YWi>|#Y&EJTIGC%PKDDhEmELv#4 zLRKMvoB>y>Qmv1&BgLF4{{V<>@&5p+SD&;N+%xnbizl5zz4$;<5dQ$2%kfwS+xY|A zhtZ2n*sjfHDvZ>oCLU~nKo23iKe{guC=mi-xYI)(|R%}!2DZ=PsO z#^fGp70Kmi^-9Id>GCf)f9U1HR9T^McdrP4@nq>-F`hEzpEe|N{{V5e@LtO4J3WvV zY4U9{4fNXGu*EXv%+umdIyr`L;{eU!(>3tHC@eR4u00@u6L)v;#3^qEZA&5qrdkx| z?Lf0=FwCH@I0j+_6Ax+FhHc1P#=szUp@_e-xB{(F{{ZY+cMN_7$}b#klq`6E zt>k=U+Lq9!@is1=otSyootSk@h`d_Ix#KpJ>*2zvK*-_(Bh-pCdV~9x89Y#|fRMRw zC^g8UX(PvRvAoTf6}o}`FJVyT+W7|v&hc2)`dNe9N^t*kl!03y+fs!3xE zN!VoVWVqv^@1u>28&8?5@g^wkP7;uR+EL2N`#KAUULamADYAueJgJa5#9l*s7!z zb80~*WR1CL^<4?1nru}nTX>ml^qBrAagp7AL>t>;ovJ)Y4mv~dz*I!?6$sgX>`_#x zsVeVyplX7D+L)k+pRZIU&->9)_OpgAJ(%+TOAs1PHmTF3Br-!H7h^!KTakDFu-kjr2 zL{DNadYn!Mg=&u#zSInME&a+)(AjuQdply|qs3`-!LiBb;pr$ZRwkM<8LBwht=v3A z5mRTDck1yH-9nEv895^CtCxF+NH~p%X4vM3LH^TEw<{w!{`uoeM26jeA*qXC?`G7ay&BO2!%5M8?c#=a{b2-6?5;v|kP)1o=jcd6EpSyDu1a zy)BC+hGLCU8OHUxRnkr$xnZ5bM$p!cfaPd4xrxbk`waB)dhxYHNmc%I7vaIV(3X@l6D|q{j&GlA`92PSo)U zTVwifrA1b1u;aA?2bpos8;AKqqamUyUYVoe?W&6*Yh9#4Z4)jcRoD5(k5Oby#Z7UH zwo_Wbp!1ANmt!6qgpjUCOX6S}cE+;A7(L+vs5V0{GCW0Teeo54{>1ee8>E!kzS16? zsW&L|mdyKQ!Z{>4k!Qr|arwGlqhVDs#Ng0!Vq)VGw*IS91#tzJ@=acnp;B3y#3z-* zb9vQtFy+2he=&;`!I+kLw@1{tHju@nh-KK@V0%$}Vmn-9H^j?Z)El~QPdN1d0E*`5 z9gp-%=DnN_9mgWkIK8DI7!V%&n31r{!feQFREd~k&`N_pE{~k zQVD0IrX|U_$x=`tA+&{fk#qaw#!6K5DEs*pnxzFwOtTwYZ{hBN-iJbrR|t(lnl6pk zkA~ZH`N3F?8zWxAd!Jd;?F<#bWY`I;I)Z}ZJXaSK6^mYfk(pt`Z0=FHF7U*>mZIFl z#0PN2Lz1R`t9i5PyYq@_7u5kqUS?c0;)OCO=yAlHyUEsEI>1o1&7s3`a}GQpJ}xMg zP^Xa6I%f~m9C%tmHwDiZvXC@#l49N1@WTB-*N7@(4vTEsq5@Eno{oh|(8ZUv8&tj%6yy-Bb3 zse#<3&JA8zOhA_>?H-km@8Y?C6UqL2fxqUdbJ%WwIC`Hx$%B@gd1ZI+k7LGt^qw1$ zhh0Yh0INx+B$IA+o)K_S!V&2e_Fz9F!(8~PGx1#3<7ZZ|N^Mn~GbS!MXTm=C`KK$7 z=rvhSv+;QepBJj28FTAN&ergeZ#rY0m5!Drf028`vTc9~fZVq6d@zfTCW@d1CO`x0 zT;U&oW(M*rs2yR(@V*J2tG+-jQ`l2myhq$WoN2>{)M}_1vwBL*$2{!5MC+3?m8rfb zryWxwFKJ`yKaJsjpN(IU@qf&INA-EzDxD#)8hdzdoiSCJv`y|7i^M3A=Rb&fRsK=y zzk~k(9sH)wCl)<3wJMX%^-|j;zyAPLe7;eCfO)}eTj_sRL!SNIWWmGHCY4SGS7oy9IoVgH{zAJs0 zHLQq~*X=DWlfq=fGT=6qm+C*9G83rI?p*e7f@(N`9%0tNid=I6>bWzH&I~s^{{S6=pm?CPILHp$BhWU{+@-M0Pl(8Ga$Q1cwG$u>_C<`l7~y_3>lEcI_%>4(H@;{j?ykB3!lUP07!%nyb%%2ZbIa-2xLo+-v0pU5Q0q6vlX}Xg=)kh znYTA=lBYO?Fxy<>h4$7IR=2RAq5u_sFPH~kJ#+RvnIe`?cC zOyZiyWv4bvY?BUd!!Ic8VBllL{YD3z`FgUqPxnV*zQA^mH?>OJ)NP;AWksnnwrZ^Z z01Hyy>-*Q-kvW{qSiFrqK07z|Oa5louIw5^6;vhItYS*dYHIA=U<-7A!?|-$JfqBI z{JTGi-o^OLQ`MEMZCVR7>?=oZLQYhq+h;2P1#Qcf$@j-AHg8bhMMbKLcD!=ol8d4w zNF(t=2E;6qwA@7N5_Xp8jgd=$vK`ZY-^n1DT;mzFvow#l+^3pXv6*=Vj> zGqHrlK;Ai`>KmmX0)T4bEQ z?0Dtm-(W}uO3+i!jWx-CbR0VY7}iULFKPzZ424aWrkR+3DIpPbQrkbu9ny07JTl*c zrm+00&Oc9;la(3cniH{<0dNCU<>at08#%pL$ziS&a$;`U@Hj28G(*L`LrlQ_+D10V zL9vryO03J%gbq1z59qT*CDMF5w~{lvniuwqm2KB$oMva$nLD_A?)_B2{;kya6fISx zwrY?yig}ZN+p>Aq(xmzerbGV#k)H+NUvL~V6lhwgH!4Eg`={)ao0xTOFzJz~hj-X= z89v1d!??S*7^shDn4UG5m*KQnn++akKI@Z`MXLuLiwv$RmU*dBlX0fPa_NauB0;p% z^|H(F@<=L_%EN|sk%!1Km#Q>EP48ObxQP>(eX(AD?s-F_j?z}7aY@Eh`nm`M!)t;h z(GaL#lINyzn2sgeJ*aNvb;AiZT^xnsL=@VbgoGqPFt%gCql&lb*q3v|3YJ1_)j*7U~K#eRNX`1ia&%wWfX zY{!eu#Lqc;R>hK!=!1wy6*Eez)F_nq+$%^%X$`^4yDvB={v*NhH7DWuHtNxJ{{ZO= z#S${kZkwje6Jn$!Qwwn?$}u(2Fngj7|C0Ve9yuUt-rz`aQ7Cjik*|sl+N=IW}3Ra{J>m zep4)ro(3X*m*_i#q<3W5PCQMO!*L;9=l8-Np3)j{aX|-M^d-Z$%x-5UCC$N7V1##t z`FBMB0BJN4_!ykhn_2W_#5*lzxwqSY38@FOwbrFpB}46=~7EZ-Y{H#e=8$E;PS zN|Q6xzRCiO*iAja(H9nx$jgW;mFejaMbgn0{{Z}|#y3EbszOdn+VLV4%hW~er?QZy z#iko9Fr2>eQMg2l)ih&m{{R+$a1rdpS=k?425Xl!SCwjdTv0&GxXHZzh4YPEer8;4 zYYT2ZC-PHTZ4c4anROYwI7ahs4c(RAvHRoJ@IQ$))0E_Le-Zxx+BKz94bISB-OftP zOPH0sVz%cVqmRwSm&J}gZZl)^QJFKroNI{{>6ljdH0p-fil$=M+-Ma7F zKZ*v!tblXl&Kf#9gEP16!6?-b^x{ggP8Sk4#R%oLu``-5c^c+x4AQX&rFBA?h}-QF zZ&8wdi6(9+u`=h4JF`?m9V5Xf_O@>$v6?zgL-gy~WX?35oWe60b=*JL%b@csyp2EU z7;EZfvk6&eU#Z4BOvKfH$}Fs%CVH_yHBD@i)``U-5k>Nj{{R`)`jl2nTd9wuzK#{j zXQgkW#XAVj*9Mt^?xoBXk%Ugcazw$y~UOm-I#Sl*j&u`2Cy} zwPGr?omdy@y17hT+~6XQ4ycHRoYD`a`di5Aarr-~U*`0&^qP-ZGbNeheyP6qNOKPm*8|wIbWAFjE#-IwZ-I54x$@n`(ng2EJDr(+I+ak*2Hb4 z)yMS}R8Cp1Bvp%lvgh*YZ|)eGmnZEuY}CQi%$ZAs{6o@ErGHPPZeQA?NymBXITNRwS7# z@?I78ZRw8E=Gc`b<=MALgi*+~XND<>P=VBuRw(xf!2?r&J+9tm5Fl<(5^3KMQ^v%M z{ihI1MuoRfyIZx04ZnJ*vF<@>; z`E^B;P<&N?tV!-BGf~irJPv3$RA?KrJ>%kxvkW+YDnwc@x&l;#)?BvaUXhHz)F73? zsQxGh2qy4VGVhGe4M&nQ7V&#MFqQ$a6Lk8;r=7*q6zWC7SY&G)1E0DxH?dE6CM9YD zMku8M&9W-6@wbGW;e|h9I}#FZLghoAy(Nu$d~08QMVXO zOk8T}{-p}zuHvp;Na@`geDiFO1 zAOVRy9VrN&=7oKj+@UWNDW5=|7Dpk3vurhW4BoddbVF5O-eD+ zi3*t5#EO!#BXR4+7C9h%Esx&?QHP90V3k0Bn9$x~o#N(@Xr;33VKUm;kj9lzCN@V* z6{qn*$6#ef6E`O>3~5yXmt!}DEXg3@3D^bKCcQAweN+_7eK|x4=A*U%uhJCPlH;){ zF5T9uM2WFXyB@U9AKB`PnE_)#g`Af(gt#5UD=W=9qc}<-#&?{cYzrvPVYp(0*o~Nf z$PUznv2i;X%s4`J-3(H}IOX4J2$vE^lIy~-NTtl|c~5ftV_G1K78*Roy!<&v+&;u* z*=VJ@HfbFkQ?S))PD=C&-5N`<^hFgii?O04A~R-*6e*03LU|--WehuRDC+?+qME{m z?7777ZR`|4ryJmZ%f1#qBV#87Z;b9rh;gx9V>oJ}nHx{qhz&!w z!nnCY!ZIx6xuC-ZiE*2GK__yibTo2bjtC2ZG4_@iGA71tQEyl!7T}!ohYD5u85gZw zmLSGZtd+r^i|oMErELchLO*m>OH~Cj-*hJ|1$_<9UtH6sGtXe6G&gyhu1KbTZ35W# zMbAs3U6%|5uvMuWFv^H@-E3#v3uPLZSqfkq_Kn;X;#i6jfFj`2s9~rO(rNpHHH|ow z9oUSVdftBc-GxhByCLVC%T@5k#ETAy*%vV8^Ht*Ej)He3M$S2Pv85Clje&SXu2hX^ zgH=e$!L@ThEJjU@iLUNE{iuq%E+b%xyPPHB{{WgJ6gribF7M)j zAmte$OxN~cl(iJHz9s#bQ$Cn6jv}GAUZPG(xzjKNKYmeovT1@tgtnc3gkHkYegp+#d5QcXoHvBONc!#-S z5Q)T6JIQO)pTiJ@4LRX%X4QhI62+cQM|c+`2}8!|efGl3QA0@~Oznef5s!UVxe6fp z-4Q{cuc__TMl33Ua+;xk;fP34v(jcElt9QT#EOaBur?tYDx<1Ju?vm~t6NPbl(?HO z-5K3TO9fKj6b__29&)Xib!z~?2y(3jLIGN}zh)%}E9-BI_M+hkU~0vDMnVcIwcp{2 zRSLk0scH2=R1*$WmvNGKU}!*TRJio&h{_d@eP~D-tV#kQ%BdEAs8JO{vF9q5pS2dq z2$HL0y-{%l6176nE{MoOA6yXWbWcozeznh^wHHDN`qg~m=nag0WpCMuLRd?cB`qRQ zAudlTYZp=&ZgjuVMbwPJ7I|IQ!x2HSw&#>XU)qUKjFj;nh%qV)2^r&8!8eX40F>$ubqyyD%a5Z2oBxI(h z3{V+&KMxAQMmCaIl#$uZZld7sw|G7~dLkTUC^2#wITeHH2~5)}L!Rj3e)6#3T(xI{&> zBK}ah%(3LqU#`Dnd+Z13e{;QU^g~_j0Me@!$T(8XF#ruRdeg6Ptw*LUJ z*4~T1M4dBd^^dG^tix4NXusC+Se%_Tv&^+%Cxx zZ>9!+Ve$9{bGACXB+xvWSX!H^jY&#vDqdW8cOZg4bSpD$fwI77+qaZjH&HiXvy9!! z93_-y@b@fQmaL;UlI~FWoeOyz6J*?6I1?fw>_uiW1-s|_Q7jsZ9w-Uy7)IPuieMOB zw=P^B)G8-MAVn)zPH>{%*rxhIO1nL=oIucjb15>keW<)Hu*zOH+aDNlw1LZ1o&_Eu zE-3dHTWWSO_?5QYS#!<|bVZ37T89QmQ47?QSW1^Miz{;Sa^gfz7Z<>tlXxejTB>FFrblMYQiVh-_t zTrL>JmCcBjZj*HEzFnP};etaTDapAEk}tdOYMZTN?zqZ^&Kr1*-Fc%A~k ztEMU^XE!&n{+i;*%KQ*RvUaopf%+ zBUaf*3{*X+fy~qsjrAHAR6EtMvO00WZ%a>!7x%?8grzSW2)qP?Yi3+Gd!d!F%;tEt zN>R{eCBo*b0WhA)WoWEE5f2v3z-kn_kh~;qu|2|^R`)?r4&~crFEcLjJoJdlyx3YL z)LO%npk^asQ}(cOFK*00huVyPPW@dbwI!<5dR{)I6UZTXbzI*TajFvQ(jongJBl`1 z?i#6fifhiuz9n#*MQkmyP*0+=KBw)HtwVTOuF8d4THANw!sx@(kfP_?#J=IW)hirR zchVCQ?jYnvF78{w`<6AYP^Wzm`%lCDvklE7E&A&f1*30-qdsqOtC_5SXl^0c@N7tlu^CPmL&S`W!Riqnz~iNB`y-XMEAm~J)1@Q3i?Aj^DMPh z7M_$KvvyosK|WuwNUQNEJ5rcpxrycymi4l33Lj0PDdh@Q2QD?A5;BK|=}k_vG#0q+ zs3+{_4u@eA8!WSwvu#9w1if3Cei%E8j=}|>tJx+^z2)3c4CS>1u*u9x?!kZ6_ zi+m;Q^v4citaTHy2lWqQsS7|hvf%KKR07`Sx;QWl6w(krT{-G;q@Zgnj?H;cPz}&ZBPGKY+;W}2^2M@6V z>G(ZroDB)*;fd;h=w>WT<&zKWW@*M7a$@$#czvxQ8F@-}aQ-cejvd*8xBAvgy5EX1 zKOpRJ_}V7i!7Sb)O@>}5UCsW8*~})^`p|CQ;eqZ9Vi-(e!ew#9?dX5G^658=)YA4TNFb*max z=^4$L<2R)p;TF$;yd;XT?Y|Mtx}P=t-{YGGTKrCu*)B2%sjaZrjCdD5 zn2Jq3BQs8=3jjsE&`ih02yT;FHp@;A!>2DG+P*%xhlj;%RG{fqH9w^eD4U+CGlZoI z=G-)XYTh9)A&*J?H^6MU?B5f{{{R;LNpWEGKErH(CqqIyO(~J%dC?NIdU|vH(eL?r zy)`~%#=_b1t|NMF3SL^9N0p({1owgoy?5D9Kb(BOo0HYfeTVV5eGChXl-bq`3VgF9 zcUV0lUVhYKnQw{MR9_=$O3B*DwgqZvMG|C;wk#C-ZumEfT_XYn^pHDGuS(&pF)-ur zZpE;FQzE;BQU&J2@uu3xA=7Z=$D{_*Tc1}Qa+@kvuqB|#oIo->ooe2vsu z85xN&aA#R+Vq3yuJuq|bW897zQ>F#u>!h6S>ajn{3OYbx#Iq?N+Px;5$#*!Fr11QI zzse7=kn-R%!MtHEWzHfWXXXA-WDLNIO=cPacXZnjgnPAYu1QNA5$^V41fpb~N6Ex- z&C9L|aQiV8f)z1567CGV=#8b&ei&VpsDX0xy^D4a70tcUWd*-_k)UpMU#jbE$q`)4 z(5;^e`9kmUE)YzGRU4jqobeN!7YEsYgNFMS$O>MhWkr~7)4ojY__9t>A-{gt$ti7J>@An#8XugveZ)b93%(TF7TOJ-H{n4{4(94NdJXGc!UFjiL+om&##a56_+8Hc2-Nm|Al=S?w*03@h$>_mf3G;z$3}x2$+AklF3ZIEIcVm?>GIDg zxPP&ixM_zPt~}draRAIb#}^3=$(ljGSS+7mt5jKHqlzPzsYqI%5`pOmWYH%O%B112 z^T7SBw&d$3A|=IO?)Asi|ANRW;%%RgBFLw;)biYCYK< zx2uxmu(*wq8}@*mrh_y4-YHTVlYww`WT~7LYFtf-%9UFxJ0{<(HtEHGhe2~Vre+C( zxX9Kwo5zl6qMVg{NSo1J7&m1uxkl(>kHNK-tyXRdxw-&Wp7Pl)Q}eyHTs?PR3o$9d~sBO)t#cu^61 zzs@PLofSd6t`KGFZ)m6u+qpK#Pj;kksvjlS_x|Xe!-=SzMKw8As?Iqe-sPvJ5O+@4 z*fcqJXP!|yiXskwqAklZvkq^Ya!vBHc%&lcc>yk2WV!v&0(VrE6x(Vs|MtQ-p3haSZ#gUV5 zwuwEy7UgFMIIaRr+jr)WmPUcmn`B$l3^&^*=g6LxWRgyNhpE(G)l8y!qVt6(u+oo- zYa6M`kx!8baLUyPcuAxG0Os?F=AAjLQ$CCCY0abhNi&A&bqSN25B$?@PLoAmPNv@{FRU-y$OCdy&wH6eA>EP|Jn~otdFlqw$TgSS?|vknbyn z3`4`1_`fK`oQ9~z#FAH=XR`6PW!iL%HaU4Kk!4g+&$h@DBtgvtie0rL&3P_= za1z0>8!;zyg`VnM92E!mqK=V#lgVH&qHE@9ZK`?~)A3gNZx0btXB*6&8KSkP*W8@| zy+A_0(b3`lOD;18ZVJCroYjditw#yf3R0sdF)HxqZNj21{Fl8W(($>Am&D}18IXg1LIC8wf58V6p63Qgb#>D9HvXFNrOm6-0 z1BuuhJ!C;*3SYhoI)#`9Qz&t38z#1OQ*NJ+=vl4z$)dKD)R+S`0`GRCtBZ9@E`b?GTgg%%a^E^ zfBvMuIOe(rMO7q;i-UK>JR&yT_@f)9yv7x(qhQM}Z$tze;v(rHgfazToXHHv@e#(H zCdCHPByd_t7eI)Y83xZV8&jl2d`ssFu?I9}*?0$?m5e(1^Z%aD0ea%hF(0TUAe;zL%I>=(XuL5PeI6 zwvhDa9S#Hab&AdG#k0|eXnVXdD=c$P%e^ncE`eG?oL%Y4H|FvpT34j+%1+QL9+O^) zAK9sir)rhVGVf}qPF=j2X+w-Me_!1gxjwADhek>@j^X+t{Vt1Zm>HZoU7CJYenpMb z@~^kMaAro;Jr~`qXn*D{j`t_hpW=8yJqtZy>qSc2dh!KId-YTYY_4?*2)m4 z4;SK#W+EoZDooOkt?wQid@yRj&Mgvnw`WwHzF=o4b!Nn7+sx!9hxM{+ymQ2XN^^hbf+9-ws6Y}1IY;=9Ei9644%b=e<@HhWLRQ{yozLnAQpE%xWTC0}%P z_?+g)(xyh@-@vU_@NE7Ue}5^4cY)F_fZQb=tUSfYWzENIV;$#|ZHd{M!gs zdJ4oDy73OMK+FOu%aEYuKxL>t*>T00=dt0M{gQ|PcILa_RJiUsW@%e;F2&e=D>ZjA zfDRacF7S|R5YUaWWL@h4DqTg4$DC0tEMB@QI}+kImve)uEO~M*e;{URNU4J!_=Qh% z42{~XLPv0-9&r^$LY^sdUU5^{Ra`-`J<(ldZJs4S$-HSahE zQDuUR=MbbCKs78-6AOg6Pd4^GCPWS8_hlE z&J51XMpPyXrUgqN=UwVmg=HZJRG8{mLx@ruVrzE^G(!!_e|ap*ErS6uJOY_r3#m~5 z0AfC3tOOY+Od*9>YE_HRF7M%i9T8y;O@V5>MFgR&XbDVQdPAGoIFVxUZuNz;1QEBg zwO52j63eo;0Y+lz22#ZKj_(u!b_z3A=&+U)5xJo*aZ-aKbQ87!u8^5cl+Gk(_n!~7 z3v|&HhisL)fB0iPY&4aTE}Y?RfYM1hIh%EbOd2~9lCxQ9-3bmHd@~#wuZjsrcN8VA zesD^nWegE;dnFnY23n&Dk68{-8OYhj`143OP#k1ni>zqWfT}Z?Z;lCHKxBXovlnt= z9WyTm-9uP%+`X|%x+`)?%Co>J(+hIgcFGF#g;XKJe>ZAp5rnauR{W9?FbRSrCCk~1 z)E2-dn-d6fAy9-xPF~I=7zTxbbRp)2Z-EI{K+%2}4aoTJFL#g*eNfcy3&3e5-SY6p zg>4!21oa`ygjP0SeU?}$^A_9U{{XCXk@PUJ(`GxC9+R>XlJ9pZL#$j*f|LIMwF|L! zGkW`>e@w;q6i;LyW&&eHf)ZQwiWxw=#SqpNJ%N}J?oLscEC#g(Wsu{z}%NI zjP4pilP_r@EICCoBs}3HsO6r#)*!YFE4shyMUT z)#A4A3@TV1u*zU}jpUs$hkAt{xMcHorcjSm78uM+W_my~mocye+7umulp4%84M1nH z6E(xmHX&*5Ma{W)FBEh!z``zRrE=t0--QdUNcn~>;X&3lq!kjxe8AX#KZ-WvT?}7w zf2)-Z-##eXiZfap&rBo1nsoax)lig;n$tw_e>f;Wl|~bTcR&5GWEqPqz8`Angcd`= zltF_DrteR5GJw<#z4qI1^uRx0fwHcj$D231X~6!p*v*OAti# zuKAJ}ElCPxLP+Fi2^4%#Gwcf%>=!UK2|E*+O z=0a;fR8CrB^G|$qaegN*LndBchf@B8^Q2|Gt4ZWO$wr^{4pCpOVqY8j0nOmtFx^4Q zy|x~?1oPqVt&eQObbFP zWbB8awJCXP-5MrKTIe~o~a7%$wesZ4c zj7iHlV{?i+<)MAiPxyW^0-T>v=bT|hrqk+91iK(0B%>T$+-y+Ye+NMviI!$qOmb{U zuHk@;DidX6XJPC&Al)^Qa!y|I9}vfHiNnQT6Po#$o~Pl-0(ej)nio?HzQ-xJW;oXf16`IX3@AMq-L28(A#D*!dBg_vFTA(rWt0Dt}?53YHNgr)x(iS zeoIwC!n-vlII0m#HrZL7%hMbelcQ@D;hNyf+JHiIju~`Y4{8B}C^*`ZGIcAP9Se)6 zra5`jL(qWT*$A-H#LeDOObt&HeJ~{{ksF}YGbr2uv)i=pe~3fdzkFKsg;OdP@>rlJ zu%T-xfv8bla8W@tNxU(vq``a5k$>lv&&Aw7nx3W6l-cMPHTebMKAe^qO4qb_{j zpH+HJmBYI;f5H8knW+j5AW^}%Nw#i}KcCfxCHB24SleYKS>3YRO^31^BN^+`4^feG zU0?v8VSF)m274HBGS&ZDoo+}Ym;vkpvc@` z7Ez-VZ^U9p*w(un!t)r@9MmygM)KbhjHs7{KkFSge~(KQK#sPz#M|V~NvvX0)iwrU zp8o(BwG2YF^%}g&!2s48%`~f+ff<?Zw+}|4o z!av#EJXL9i{{Tsv8(R27DCzLiq{)@Ks!8H1(GV4`Zp*4PK2Tva#TLC%HLy;rB-wx{q^xDM`c? zQ)o*v*Qyx^V$I3Y6`6|V?`scKI9a(nHg?9yDjumvQiet|Gi5q!WOzk}S>6u`mRCl+ zTw^Xwn=cbmJ)M%e$k8~oDH`BubQdvYt%G*3Jzf%wj_U`e;Ql{dOl`aag5|L76E@o$ zf9>&jW18o2p4}j;loXi<9N@@&HuXZ;g6Jyq40@s_+qGLRD`93#$e`s0;1auty{`-v zM0O?c?D5LKeb%W$9}w(izG<|clq5@r5b0xjE{OR#QTsdQDktQqm~T zVlHcYyDNu=?x;0Qgj6v_I9oRzJ}a}~fA>R7JeKJ-tiaP;dB9SbmkYQ`)wUaB#O9T; z8!C8CkSRJ5Zc%h}-Q?Zsr`zgt&+A`9obaKNJy?c-s-k%!;_< zPX)>0v||JR07=OR~NZWzdMdnQx{AEoVoS7Jnf8S;Y zG#3-ITu)MMyD>86JVP1Fr`Up;!=)!PzLapKUb^dmlwpgPLZ2SXCKcM%MVVre#4Yy5 zOMqsFg~89y%_CywS@JTb8WZ%I1cl`{N^)Lsoa(czQ{M$P4kz~%yg{-lJWY#6da7Nq zmj3{{FE3^kt~089n5OOHXV`g}e{KCWzQZ?a2#D~6DgDQzDXdEeOyK!vCMMiz;F=Ft zXNMclyoTs%lCL}}a z8}Nw4u_`qdSrKgjcXs8|27r!cs9@!$$nX|Kyggquh84IqCeSdu^gr~1qxws1mFz8F-8_>FZi{==E0%ZTWvry^d4 zirVJDXEsNS=P1+Tykg}kSIhhG?k{|J{!R}~j^7)Rd{!v47)%!AR>tw-67fa{T^dzv zGa4yQv%4m3baUb_6d;o1e@xW_eu5&|lfqWr?u)4*!p%_amJ_$uj|p1^5u)K%PaYgm z0eM6TI!XpuuSkz_M4X)UERh?33`^*K9y3VxUuQ2?3`%X0XLrT+k`X+Qsn^5!rk^75 zhIW3$j~FxIw(EFdFO{dr?s2#)jIjMg60Qjl{gI69#>7Pq%$Tyqe~~4YMzGRiTpWKi zZYm={N|7#0=RebLvPBlY#Dy_XrKHuOqpVcUgHkYX%dP5H+$4}uIvY|e9ZT+6ZHD-F z+$C%nrd@%)US8ABV*Xuyh>ENSMeumpg4P8=yl( z&>TO7PF%@MbuZz8f9x_8pOU4RhC|3Ba1sI*C%wEX5Fo*m=U zj;o?Y;6?Dq)c*h##Qi=hpAY=cs=L(XLW zQl6{$Q}Orye|=4#h49I`Za()BxL1|K`=i8mK8K1v#qSEsqm1s=yNq(QqFB{cFBCd$ z(!>fkFNmOfivfwM>xLvRE)gFTE!mKE6}icNlG}B6XjsjhFHS-xTp>B9K)O@d)6OrC zhTqL#vbX#sHtxc9e1EWC^Ks$=U25Dvu5h3 z?lwL3WjBrw!i>;amzd$+xJknq)pXVl{{RzaVbuD|6=Y5JpdqwGb0N>TvyQJ7Jw~y| zSBQZ>$`7ys<=N~~ z#N@B7V7Rw_&&mmXi=N>n&|LM>xmsOqbnE>VEk&7{n{HgLxSBHV?kKhjqZ)(rGZOpO z$WTYDVoZb$wwj)3oR+j-5#jij7VI$aOr?H6f1BZMb)G9rt2nD|3YDnNLZ&#PUSgR# zJDI6};`ovV_X?jOn=?;UDDFwAT{9+1H~hImTGP@p6dgik>ee2Wn`??{T$1mY#rbs= z;sX#o<25N7i-o&OY$6hZm|{&-Yb#zhYO}{I_V^}=3yZ;$LwbN_Qa+=LjklRwxaE3B zf5J-xW&*=TadNH3L&V`-Zl!X=A;9i4#q~*M=|xaNYE8*8zkcZ>VoB`8p2SuA8eeE1 zNd_Lnbkdc>vaZ%Dqr-K+sipU?&N@6!4t)MyCLUiu9xooFo3^F5EMc}OuGNUE4$I2x zY@1G6(5Jtaao*#8FBTs@qmi4Fmp_+Ef0~;bZQ6~2WQZGip)u*=LLB2$=Tr$iYNt4D zqtjpr9BsMFSucvw`NuPn%^i=7&fP|u!Zcl^dR$_1mB9FJtz2x2BAv9`lxybd@P1jx zE0fRrojg2zR_QzA=+62hYMUq9O&1;GM*^)}q=`E(CIX=+BITWTuBLftnWkRof96jv z-X~)p8l6miN0D)ATjwSO+dXaYkGmv}WBd4$yXqSgCrg&SCJI~}xCqON)l}2E0aP_o zI)jNAcnMUUdDhC_Ci=5Zul3&rDpID&iFt9HnU`@}bcDDFM0cCq{olG2w<}dESepsV zy!5EGbi}u3P;sLxOL&eK58Vy0e|4#K*iI3hrL$I>s5!hzfN6jlw3xP_7qr<6w6>sMz>4(7c8X3hITY`6NmaQI2A$dO%k8fmm{A>l5<4e=R!GLzwC( zmpw91C}Luvsd(KsV%-CMXm6;dj*&lD9Hfa#I>@)a30sQfv~us# zDVC*mLV}dUFG|#$WQnfR+8!3Sd`)Db)t+CJ4S`k!_86ANH1^QT^sdg`q+LxaFTFW` ztm2^!7-i6Hctgf{KMTnKo%cyTq%!zO^TdyVabAUXVBbs-VDL6UR?ett+I*Dj_OWO z_MTL_J1@R2lP%I+yu?=9+;dN#S9Bee!I$ydQu~K-)Q%-L0!V~d?UIaKTqU4IRSs5e zHdOK+%~9B^1I@TQu$l?X%U@av%jc9ME;W%c42GlIcFXZpf1VW=sP6?yW#oZ}M6(lq zRp0Jc%~P5$$}`U$p}%P4<_f4M>nCMh$|?*&%(oEDdKL|^&oUw_6iCM*m=J1X%rEZc4R>w@+U@7K59 zEAL3Z6$E_sBkLxFg~Gf=2J!JC_rSg#g2i_eOU}x&^K&xu4of^JC~R>!Dx8-n=W;k~ zHs*lY?$MsvHX*2Cyg^H!Dip-hoi=o(%tecrQ7trwe{s0!mlvg%$wG%yX;mohOPHXx zZZhYsVqv1*Hs~y5f5)yPVm-(otHMrybS#!d#nW)WCN+W4>`O?s^?8XX@cci@G(?MT zqt)w4yG(7kx2*^dN^<837>lqhclhmS7k z;&aiZf4WXyWa>Fj(t?ML7iAR&Coru_V|+J}G|u(TA9Q4#O$*d#W8%l8`x3Gk-f`It z{XTwN)QzGQY1S9s3zDRFKN#I3l~?RfdLaEFc#~nc+Fh#MQTfUXgJ}z*t`i5IhMnt_ zc3%e9q(mpJRrywJXF1Arvb`Dfn>Wcrz(9SLVUG`aD&DpuwLaVw-Ku$C0TMVFkp zLAlolOF+kT-bvjNirk)>mo{E)1f^xneU3K?7sCL{^(N$FF-hLq;tu)I&evxO(bLI2 zf6%6i_>q{eZ3AbNeXdA7DOY9Mv!n`AjpR2NR`OtS_T+?DuL? z8%vOEKw4(8x>J~FauH)LXdZe;f0gF`DY1B))z(SZ2X^;Kc@KoXYaE4b2P|R9j?~F8 z=-qEgdrKDH{Y6-(?Gpk0eIVf>7`-pYc=27AbRQZ0HnJJHoMp@I;%Bhyes*b!}MvD4FFQ{Xg9A_iNO~gzfkPc;Q!kO~!Ord#VnND7o4leYH zksNV=CNm$>Bz<^zuJetYe|PFpS@B%wYy*y+!n1_Y7pQQ!uJexU#l&OC&~%TZPo_0{ zV7S4Wg)VD-Eb%93mongdAvmXAM%0y`GwCiM+r=U5JA!uUgQd=%v&1sBxpoN#*E1Bo z#N?`qIcMZhtp5PY%hTezv(SB+Osp#&C4F?#38$j!|=w$%d<-LmF!cpwr7l5N5tEC{r$hl^#mV_j_GY zQ#&Yp+`1@d;))>QmRG*`Tb8q;QbpW=bBxY}l_sfrfBoW|lE|``hztkwyF7b5b62+y1_N_H5 zqJ|3@fgtMgfnp06xF8vUs7>X`sPROg#X}A`Y30-Z031^xRS;7O*lF*J^pqVy-Rhw? zZhWFPAOcHN7nEjX2}w$ION>!}(L(1CbfiyT@U5t`g4L&jgMot<>A0WXmTyC?<5#lPQh8sS2!AyJB~!SG=XI# zfAwkKMRzzjiiYqroR{xNTc*tJ#64%Cin3Y4y#bpJgGBDe1yk8bZf#GBq^eOQTA52E??rLgmsl zLlIms&)lM%Fbp+Y>=~)6^tu+os;J>yf4MY@Vk>A^3UY|U5vnYW1`|DkWw_nPEKH$o z8v#H)F?NxcB4~90@06`b$thss0+CzJGrJPOC6e7gW)cWiO4x)Q3a0N)Q4HsdyP%Y~ zeFELx_M<{ETiN866z3WgQ^1EA;DP`Kqs}8KaT_yj-1$J%boMgPe^kvu zb{Wi-+_+&9;IFvW;pMCZuxP_~xVmFI!vyX(Ciu!S<&Bej@)^uG#|ecgzi83^{{X%u zAj@cDFz1vQ78p!BcE9MM*vhJrlZ(6p&NgEhM9c|al1D=f5NP9l>*0=uF@Q>5{ZM={ ztxBO4IV-_xM!X@yRED3C-JW*Ge?~iw892#|Jy?TB0eDi|VTGv5V~XTY;x6c>O&3uu zx4hJchA9-K7E^~?H3mG-ySSt`vNtZadVi35$4V2Ra>tn_}Q77Dm zYKKvBhd;bdL)0;A>?UN{fB1hNNz5`Jx8L@A;!H7qs+O4<#^)Im8}j=-QB15B)<-J) zEQc2l&MKwY2~IHIvC|Tc5Fv4?gS0(i!Nw+#S+pu+4!(Jd7RTiKYVGA!R25Znxu9n*ooou$;q#X zp&7pwPmt8lvb+6~U{hC**^J+%3gq4j*^H(no)(SAY_~E|@{JgO9JvMM_V-;pcQf{*O#%M1iVW>i+<_%^sK4 zr>eCoOv!b)f6-jGUEVoIEAlh7I$Uh6D`qs+F6oK(M7UAIK_&K6IO6{RNui%dSkPuL z^oumvAMFIYa_z7pN7ZhgUUBO%2> zzqpBkMQxg(&3da+2{PYH3rf_vCS)dvnIvX*x|uXVbl*q~o^}o6vy+89B&A@so4mep)o;mS z`y9vO{{YN=h<5#h@fOaeB&g1AXWq|xMpoXQ%qjVckY^hL^I8M}H_J@z=aIuq;E)ynPBtz8) z8R_CF(`3l)>ktf?G?gdpbEpKkO<}+79HN&Bf00tVPMs;7-y)a~WQk!@%X#w{xK;`I z8Suuk8^UZGCU|WgC~iELBOzXxaAhWlI&CEw7GhFI60-^_B%DbSX2n907|>hn1V`6$ zTNyK_NvRm6TgRQTg1sY83rJC5*v9AO(*YBpjN9Fm84L*93>GLu@`aT>g>L?F2Al=UU?Y zV+rIZ@|exZUX$$u*iug&U^ZnZ+i01XVLG_2T%+e#%9(mhte3^&Yb-O2rgjO6o5S_V zi>b2(`W(R*4qi*|j$FafgVyL<-`Io*r211JcrdK`e?i0I zE+zGHfY~;+#B2;dL1u$ZW?hj12E_(3XU}%AA+)_C!k2|a=r~|*GNlF>-RXO+%zqRO zfMu}OVogO$E+K~Q0A0+mt+P&Lx*;^lFgW z=?M7$0MN&K@ei-%9ga3-l{o%#OrF(a(i`d*0^pZ1HY%5wLG@y2ZvBMSe{>!UcLAqH z_^VC@Hbhutw`ku8jlI#;Z(}ujiY0oePL~kczR>uipO-14(<^fh6w}t-e{^GWPAJgk zlZhJ!dq3*TW1q;z(t6*<^D#oyZBeNY7ZJuEOzXENWm6DOZ;vD>d?|;Mn1GlYPdm7y zN*qkmZB2vK1+y1CLP#YJRWeKH>b;fXVP(e21e^y3+kKeSnU^1tvj+z+BgDO=yv=FR zZAh%T+ef5QFz4no_@0x8fB1#P{YJ>a_+_e0u^>0pDa9P;%d@XS!y<~n5+6^8!vG|0 zIWPitxyZO+Y-phYRFy$77RJynE*KCS9?0LTid_dNd}TSgQ$pp-cQQ%dIc4m^UJJtd zEmT^uJ;8TR4-bkmw@JSok9!@*fBygsWp1JO(H&ZUa`x?cl6asPe-b`sT-=t;dPt)} zMTQj&T-&Tn4qQS!R{Q;!emF34+og&{V!1gk^V|AJlYSdp{wR6jGdEX>2|ZAhngKTG z?zV%F@QRUh%J1Tb;$5+xau(@*nM;)%rW$1UMNDqtBGNTIU@Si63lG=jYn3M(5#5}* z+G1W6PdMY|Pm-N1e_4Jd-)Fl&{@h~*TLQEt%uL97hb4jIM#-9Y(sfNc4a+k}ZKDt_ z3x3RW*%m~VtW~mboB}y-wQaG9@cl=0Nv?4Q3z($u1;*hP0#K_@Q`an&zlSS(psk?2 zHG)1}x9D>s^*!R1b+WqPD{j^_V&sZ`GRbMCV@Z1Cu*%hCfBQVhmlWV|-SbbJHLBvJ z8a&Dt*d*F=q*k~y%!u44;V4}~NHu$mgy9&+W z>XgK+g2zTae>Nf4kSIarlH_Vft!1AVbu~TAUJT zu@E8R_OppIVU-%!Dd8i>DPx9NaF-Cmy-gIDnfwM4^jD{hTK!)S7ldIMb8PJKzZcVN zrNa;4S!!b>g=udEIJ$-s&USI}))%BN)S)o_w%zeSOPg^W~94D zjkkC3f52`Fh#_aGuCYI17NrY!8%Z%WAWF!h7Z<|@j7p50qnx|iiQA+dfQV3zN{Ks~ z@p85!17S5OP-T*K+d|kd)^t@ymUYU38jijjp!*A+U=J~Lm1w33Ew`6Qf50;ij7&+()7mMf!M+u@iV(&6VsRku z<-0g>I^tWy1!|EoY~~%bY|3+HIEA{bu#m~k9oG*_OYFz1{2%0Z{H8n?@i*bw8`o9o zE*mVzM^F~r-ds)Pf66`97e05HDdcVWDv?OM-AY@CG$RjmW&D>7c^Hn5<3A?cOzO&R ze~b@2#def4O%Pk55qlBxu3rJLWvd@={9ojklY`YY{?x07wCq$gc~ZKUO)px05v%h!WhJ!qPoAGnlxR$B5u@8$MOUT(*CM z=1Aqq1a}Se{3xC%`KLdU*>c$Xf8)5gf4KOS6x(#VcKA0yR)b^g$2zWVscFOV;Uc7{ zJY|rL!wlAgY&$Jd^QkXQBwIxKu7bXSj5;~X(-Ut6gz&*jv2jh!5{M6oiFrnAT;7Vf z*2P27U(wcQ5K^0|RL6NeHS6DW^1D2_e4|H9^qH1v)W+zacpU7s*<0>xRSVe^e~Ns% zQjr3j>wd)RG&`7Sf)Q*o2h}j#c;LO;xQff@q4{E*2BBIJ2N7AhY`{@S;((7x({{ToZK|4?ad0GM7%#S-tFQh8T6ZN#r zrCWDVX68$TP6Be55EgOCtS+SPlN9nS$V7OBAx$iw(i+D6KvXJHY|9Nwe@B1oIF{a$ zfM!_n3sSygYTa-2CTj8`8MnlK68DP4X6h`6u1=uV>9xj+>1Q&$N5h|-1SegFD-snd z)Q%m*{i&w~=4OF+;68oO-vLuPD$|(hWoH~_P`4oQseqSI=NA*8UC4|}iULG0n0n&& zF7JZskQsi&T#g`maguO-e^?htkCaJ$5ffz>XghE$7Vw*{0JJ2dbGZv;_rg;vPfbJG z!J)DDr@_;nahgWhU?epUNV5^)aRPo#|1Cc9C)7I^(4% zIom7_TF|GyJ9xhxg9nky%gE24`(6J4$_-&z^qQ8WT+Jp^FsgkBhvBu>7ZD*xe+5XSVK13yF z=&$sp$Inb0#u=s2+GLsEoc&-eX&tu0R1{yyBafH!3q3YEylw+WivIwq)4quR0Q{d^ zC4$_ZuI;A0?l7rRf8S+~s$|P|PZy5dhn{X_=DK^MGyX+n*Bl);QQp0m4EjCb3FdJ9 z9zu^rqRur-Vpd(0XiJ0Knq?TLld^gmZjsi-YEziPj#bqroBF_%SYUD?7W404KQvfs zrj*rY`5PVP)?QC?c3$Ci4^D84fUB{kVvgrayROYN0%$jSf6MbA*WHu^&4$zwQRSuW zM(q~7HuV%sfD-P(Cgjk}JNg(S`>`(uVwytkLONilE@WGkYq7ZL9sQvQno>;HMb&d% zb@#*5tOjxeZyQ9M*{;uNGE~PqJ!$9Lm?JKm49odOyH}(PXwJs1!v2~Vo#td^mRu(e zUB~3X^x+~kf4LPj&2syaHSSGK!&x;R*?lLm)k28lRQj{BFHK6II@Fk_5`mIhwyJj* zHAPsF+^E7S{k0hYM9b+UXiELry7;r z%9Om(y2@PsPG@R;&`~>Jud`~(Rh_IzH!CMD`(`t(e-X_X+x_0T`{GZKPDoTJ@)eo- zepLaNSy?wkzuNgzu)Zuo4-WPUXf1Z5f8{yH8RiolcxmIe0EZgI{cpQ~%L`!%# zasL3Xa{S__q-kEn^qFjM%=6b1b0SY0M{`KargCNTf z-bryepEN{GK3~o+6zKFgUu$61DR4VUsfXD&e~tN_{l{q`ycV z8;L*C87U^+nwZ@5t@@^8nts$VXKXBURB&bA?JU^bqjOgUxpy`>=Wa@w{GqO(AZ#}) zEiplIc6Zh}FDl1puu!!pGxsmmQO5?8+GV>YUH)yMpATO3tchSzt!7P;BurpG7vAZhmK45}SEi${j zSNGkVPcIV&P8%U^r_`ib#Qn2)V)0Hse+Fg57G=<>?^rbU5U$WEnxwmO?YsW~ml<}u zRIR*2yCulkUvj`)CFCgS#@TH9Jds+gmiA1w^#;9i>+W9YHDxLJDoRk>mYAlqX?9j# z&S9@=O~mG(VOBK7LX4XyEEGb83_oEKSO(FxG%SI;h+?oa!X(IxgVI8LBtH~$TF`W12PD?-cL_A3ZQe`Y7`ZBQd5fYd zmso#)xmJ5XklG?6!YK<@SLtIGCwV!;(`)FW*{7yd%&l^En;}WB)89euQofYd&54SdtO6+C9e`;A}P^s)9gV7{AJdui0lcur3> zOLgY0xJ3kAJ{+_1Sj6bAf88Q8VD!z@^N&8==h^p83LWP4*CP34`NH4W$5A7c(%?O!iuQw7{r3z=}HSsJdGe(W3tV{!^WHfA#2!a}u*vtRpWm z*`+~BVTq>@NR>a`=dUR3@INZp-mT(bSB_rm~hU|c-l>JHc$Ta;LBVHSC;IYPPP9{9E{ zBEx#a3jHbOgu1w+e}(y;Gi1QkAI6$6O`7AEDD_!(Z5Jj4n+b6;wpom>&iwhLeEW{{ z={-WvNx9p`lRHRCEhr|cQ-;X4C1ZgpEW#$XUcA#jOC5eSBdiElfBlK9J6%~>KF3gb zhUOe13yz>}69}vB(S6GuRds%#*=SDFY3)r&GQ+c5<#?@=f8ycYjo0(ZbIfA<3TD|> z#Mnq`!E+6?Hj>$Bt)iK8SX|?98d#vbQ#cm9F0k>oY?=_5jiUPh0F-2Pze|qA_BU3` z_PWM6lNni(7H8QN-;oudT&sT9jL6}&=(R*y=@ ztitqbDd%&uVrr3-l8V{nvFZ5Sg?|y~<05Y*@{N_Tdy^t6spkxY9ko4e#cVZHHFXm( z**M83!mu2KS;RjTtZTiaQCh8RWN^&HzV3GrO$@tEfAsHHi@U!_`L<+7%UYhQGHVg9 zy*99dVSEoo!%6FkOa$LE>S*%mjXZ_B9DKR7#}Zu8h{!?>PuScJ!R>W@!$anr$;_>OKsSZ5TSIp*7NxGqb67}4Atw0NIJK8~DR8)(@T zV`lSHf56F?t(5~V7Z9$`_rkMq+b>tt+OM@=+IMf5;#&zW2vDui)oBf~01bv((yq0V?$O2u{A3&a{=oJ_tt(KqL6wB->#f)QiRx8Ei=*yLFJfN32JSIBp#69cB(s4(rltJXM9} zaNNlu2@^gOxB17S;&YQHmyyJLES^LWId-wqQ!@JjdSdNhh!1&pD1dbYZ0<;=406kx z&3sWzT8>$A-4wWDSf*gimE8JZh#eOwe_1P_?96fH5TDjMW@6|j7{20x#pePSVO!2P zg4tM5kzEiL#u1$U=&KTe&{aG!EElH3hWTp|f>jLP@WQG}1RZqlie)2v2v*{PR{I|i zUYNC=c#HZ+lQXc!d-Q`WxphT3mCV!gyEOJ=7Fj7h$w@-A$3AhCZ3&4PbuN8Tf1TT6 z*-2SBAKHaaVRRoeFQB4zbwCUqvoC6Y%>cqDxaM9|c??i9>=;`V!m1|QC#mIV51d+(Vx;_`S{cT~VJ!;-w&oGeLmFasMQx57bvdjOgdyiG z0*0vw*@t?P7@fgp(H%DO4cYXUe-s@=vXK*$3(aEcFM1#2BE)2^E-`U;H6;Rbo z_Qe>LDMQQ7kOsX_ET)BR4x60GGrNSmF;N-|rbD+hr>elpg;3qACo}eVf1*N%3>0Qi zgF8+Pp*^fUrGcn6JmbPg?7?;jXEkNmzYjhf5Quv8jL%acYCUJKu~{i%Wo)#a1y0VY+B6N8v9XVa0yQya^28)7R7e|H+pCSjj^YRB#rNEaKlGgxH;GcGoy1#Alvk}dBKrWx1~ z_hq|Pf>D7RX5Av{DQtL$;kGKZExnA5&A{pg3Uh=qh^j0USq~9+tU|3|Q(OxW7wkn( zHY66pT}mR&n{@ENp#eBO>u(G&8v}96LoV@xLVn|THX!N<7a zx1gDe-i4FbXq`}&G$f7Y2^`UUP!hynaigx6cT8r=`xvSzoewC;@vx=>65P6Tx)SJC zq+4V{yilD%L4>&SfAGaj7d^tZxrT5;I|TmnAye)xzAa&?*a`#@*CmZA15S?+B8N;d z6bp?WXTLb6BQPe1D5BzY6nMG7OA=>d9v_Al>_+Mu7O_Vq2&u|h->3fQYDsD_DsM=r!S2DCmyr`YP-2-1gcRjJr4V|c%3vQ>fgYm}J;)KK zpoVtqtWv2pe;|sJbJ?$oEDIj`zTbQ=dJcP@ZjWw@#r{wh7Q~$E@L0lD8(+eq15h6<3TxW!Os zpPXv<+7YyvUnpKr#bw3j6moCkQyFEJ&mtp)uNZf!qF)Tr*{)CSt^lGyUBBV#BKBdt zeg)W0UQ3~7Z+`(xA3Ni)zk|eN{c{VL!Lm{`*B^Jo{NYddd{8?dC8VI$rvP3gw}v_S zc*_Q>kz-4fn0GD6ddR56g4vCgh4y7lm!`7m%Yi&Ycf%dNCj%9Fl;*!8#g0!7%Q9j+ zu@o%4F{yaiJ}L*S(pC19+Y(bdrmeU~irB%+#If-}(#{ZK-v(^v9w^Q<-5EiNKShJv4=B4+xAaGbAaa8g&|@ z6jc^tXFnCaQRnmdohXWoDJ~Sm`yuTb4;M@#CqTb*aj{mHlap<hh04k2=B6 zd~CzIU4KcZC)*Vv;xri3`m`(2Xehf?o|vA*Q&x%Y`a_>6wf=8t-FQlcgQ0Dr879Ow5mCvrGxtx;x0$EKyrc2{tf z*FaXG64N_lmLvU5%Wbi(e<`Ck>6Apa+!9soCBxa`h~JZG4lwA9sAAbFp}6t5Vs3ml zYZ;%CvOD_7#%mJN^E<}Kt`fLP5ZRQ3^*3%+Tr$hUPMGZA;aoyFbBCufqr|DlvPPTf zS$~YFY>>-VCK(bdh(jUb{GdPDIaIGhL;4@3jv$knamtIuR7X56lEagnz zB@j^8ro{6{3qSIUY?=nE1oXDru`G`58Gouyx4Y@J#TYNwWCc`*pT$%2*DJFQkvYU) zI8^3o9TG+>&NzDUEK~L)s7648+s*>QMcnD0mk;L?Y!a@_Ilnc}jEW`Xl@r+BcU2RM z2=LR~&m|IzvyfXfx4WB(*B~%osip}!V3k<4GUX0oSEd;~iC|?#bDRZOjAbZp@_&lz zE)+X>Y|0v{S!KcWS803061Z*Yhha?tGqM`I*!^nqbd|{jWJxaisPis53Dx z^{%o=U|!0kUiJ&6jncnJw$&SuZdt5NG|O#*HX=E4jMKot{{Ry|>6`5(cM$qoW4)jo zBUZ!{lR++bfSGixHy;xgKNSPf_*zY?f_*b*E7V5lSc0)XCf&9*G{G&y^M8gu3{T`= zrnA!CBwo<4ZaLhP{Rb6J*Qum8ZyK6XG5M!BU+D2i#9m*;Vy)MxKcoKJF_z8owJvU< zJya=qd4b=9&_-_GCJ0#T$mLXqon%ff^jQ&uL?=evR4DmA%1C}{897V_XWj;(-a;ohTWLssoC29U5EX0aC zJZ>H?KUQ9*Gi1qs?2Gmr!!q<4VQH55yiuE%%*&Y1i8M=$xpxm2#TborX+637Jzmm7 z7Z7WWfxOd9uC6uz0N)Y{F}Qw+(dnsXZn|NC{{ZfZ2E2AB>D+GRLVr_|%}U$<0MQa0 zoE)-7!rxJp283N$PBrwjnWXiRlLjx@~w0=+>Wz-VLXq==f&it%f@8#Ag0@lf?Q(v3TC4hsS^If zM~QFEB7xi<7dy%#sedQ1p*!KQfF78g1{ozXwFku&=(M#-5o1w$S4-jjplq(8OrCkJ z(PEn%NpPwme^~5rzZI7DZj+(L$3x}iQcJPbo|iA~O+)gI<0g)#CN@6Es7XnqIXk>Cm35;WxVUYI z%4UwltsY}@X!yOT&y`Im^(+j|3xSo;i9zt|ASPMuKz~Z3#6QIelpVPv-kKgu!|h!c ziHn#jIJwkvWVpC(v`RH%_whOWpLHuQU|NLZqOz5yo@_^@!QvluYfMLq<*ry_>9c*7 zy(=s(Pwt6Fith-;K213=@tU?{9*`C39hvO9dY}`M?m1$JfRbIB#(B-QHTpVRF~a*X z+C#JZLw|Y4NdEv%%!o(Cv90j*oaM^1;&!&g>F0s)f7UKt+bUT*X>|x9Xg=u1&FP!d z+}Rj!YrwX*F~h~hcMLBVCH_OF9?R7?5X)46R0uQsQK>UrN2jATiX^paScS&MBc{dQ zMw#1HI!^`q-S9RA?G?I5FCcxWbP-^M7FRa2LuOjsD}6OFNr8lf+U4auZv_ zA^S0*SLzKmP%9V)p@u?Xska?@lFWwe?G%Cao8%#|=+Kzk5PxU!IZDf1NF{LlIYW{5 zWy|b6k*+I`H!*dnSzJCCy}&BP(pBcK5eEszULF$CBz7QKOPMldON6?8oZy&SlXC0< zQ-6sK&LRFN8SGOI8JKQjOi((AN0V$&Eg6h(&Jy3Yf%zUUWl3H6Myobja-ZB;!%8j%$`RqSDp+fQks^VWSf|m zJ=35=X^NzI46xYSsP6F>)$t%kP3qg*=5HByI&O^5$)}0h<9jigrb?Y&C=%2Y(x}j6+5>*&$7#gF29oT}2CTa!lxPI5PCsieN}B z9-oQuBqD)g~tn6p01xPA{ifQ%yMI6#sWHyd zWQILdHP-BS_xABDczbD8d@^rH(#RZ2fT#*7UAk4$Gwe;tdE)?>T)-_K{^t#KPC z4bs@zo)6y5j}w7$7mGEArWM4sGe0jcA*{eQUxKDFWlQThhRL~0l!D>nFDUT;0K^}W zY3NhwKf<4j^w-yA{{WG?Xn&F;Cd*JbbesKw8g!I8Uj@~DAU3}zJuxRV zREEmgbGOwxx%vFy@S{T52gt78M^@rGI~yC>)0E)hogM%YE-TkdV1M4wV0UbdVw18e zJlw@dif~L!iswlBvyODTN!a76#w#5?v0aK*wt-ufazacTXHp^}v~o!4@IM!qBYLcP z-^zYp76q-2so72yq*7%5qp8sQ3&dPAR`8Pi`=i)#uwsQiXU*rfR=Ihl)2jFk%P-R@ zUJ))LX(9I4k;Qhf{eP+qwNhlNG#3h1WV*+sNqm(&;zBpXmLywstVb%`rBSnypB{qg z*V;vc@6ra~eALUdwuVZgJ*kHV&~!(u=kJF8&dPkB~8GmZTgOthXfNr4PVR3b5 z?uxC{8jR_z<6z8(;l?{060>$x>zY)jHs&q3dHVG-?8Zl;<z9IrH zFS$9aXzGxEsEDt6P*(9&n{;dgg6dTXnZZ(Gc4$?miqzDRS4|*e4lnqvQ8{xr`~|oD z4M#obqqLo`Zf9{`BBjX6&P%;GEu-7r(l)&|Xqo%tLVxq{VDVcRTZf-7_N`y=Cg~o7 zxH^+3FEdocRZ6zGatadpxX&Hr1t&nB{6Nd2>I1evtgk2U%qb!;ehr$$F9KA?iun=Z0D z<9~UFWzC`DwD?pD-{%Nt({zkhF)KwH^Ahqeylu`sG}w`c4aww)ns~p8H*tN~%;vfD z!kClA8%kzLmd~?cwi*Jtz7oxIT=Vy79V!0QzAF5U3@;TPWyb}%R={d53r&ZsCcKF< z$v=EM)I+AdMGAS!jTqZ{Mo+XoJtZ{JkbjC1G^C;#qU)3~TB05o#fi!e1)P)r0K&+f znwWBFlM-}Ptubp+RmpSegN&d&uf(w7J(Sd)kjUI=mb)st`3ot$BS$RJRWn3^^}2}e zPPaptq1x*L)Z^tkHi;4u5u$Z3Ju>f#oQ+|NDhg_PPLJzd-8Vfj)0@*Ah(}(O|9=3a zUDifd>P6LDmBbe<5xG|DV(3~Hp>NSGMJSN0z9gd#u@9_}k&&o9xtT64(~YgeY5xBJ z=>He(NJ$Yns^_M?(OG?kDr_#!_VqEdS;>YTZ+RgwYIhkg z=*yQ>qRqA23VFZ)PmFIQv0>_3EoJnmTTXpu;}+XWh96tsjn=d8zb06On=KFwD+2- zWtLg{(N;9X)Efw*$+3uW2&F@M$V@vh{f0Zz;cHCOL(hkr@`fk4HPGRj(9KFq?^$)W zY4UQnkrJKUe3LXn$y3>au@y-dR41#=yz?T%GwRm}8V6@N7H&JC0i-CGtEi%$@a`Qn;Fv^a=T&g}Kd0gyhC-2%BU zUc=kAM|^C^ExcX(%-5GaG}kDV`xM2FRGX+A(l8=38~IRcdzo8e4q}|w{*V?1Q|wLQ zyhT4JRhP-LWXPX<`p92iE+kC3e4>?^%z3!0B&{zV9Y&SDK|G}C+kd0DRE5>QyEXn$ zcR8=Z(Q2pTsdsI0qG*-rrcaA2M=T^C2;U13|2)7Yt&9T`*?$Vy=Z zkhpkb6EV`n`x>)|zOOyZ(HnOS;CA%F$HI~oDao0MW?NT-bVYF$$#Db=Hi}g0A|>lx zo_=y&WlgqZ&cGrS5;W)E1ddiz>IqnD`$>l7=NRQKMZCKqwSO0uP^`(bnasn!k+Duo zO0zB!KC)mzF7}-@@=x6t32ze*PCk_^xNi=#gjp$ykr_|Sam&ky-Mab4+zaWi?ih=| zBH1TJs8*S6>6wkl`ys<``tpaoJE&0%e9-!9K7)#{rDasMi?g?ya}wGC2(&wAsFjex4YcWVn`2mx-n)CMgaT#AYjd2i$Qoah3X%+lwXqnELhThxDaO z*Cpwq^u&c~TShmc<;Ku*xl%AZ)2Ar+;%~(6TrZ6&SbzFiv!+ZHQO5xc$9ZUq{m_?B zsm*j{@+Cr|6!*A0D{hU}iw&uku(okbwFfaZIXKBGvp(WzT}aU77OS$sJ^Bmd!%e5! zDuiTP*K+2I^MQ5QO4S*ZyEBs{Xw21V{t|Sb&8CD`zA0#d!&~x$9B4v;k$0>x zIteZi(jgvn3{TDjkha9iQqNIPsy0;mtC^4J_kVE@w4KrNK5idFlFFy5<2~^f{3o;r zrtw7;hZ{PB(rh&=B_>6>lqS;d7gxV7?BiYx+f$*(#9buz^mwZu$kN7P@t>#VYgCz5 z$VIl$l>uB`5p(3L&O3Y@TN9ao`J(>TFmhw+`HG(XnThFfA#vJ+WaxpPu!*AO-49?P zNq>}_lp2%4DlWY);qIk(rExkLYHQ2B2+hgNxcr6{Yzj4CAU5tX`4y=dot92J{$COl zD&`-a!PQpgB^x_iFH}L)OXjH=th3U_-Z$hIC+#;5$nog830~I=W9E;7%MDT-KBE<_;Ad|v45@p)~Dx{o=JBggcR?^9KiBvFr1 zhc(v5`xyGXbI&~^TCmuWBTcl&A-V||l|4OAB9nOsgzQ^N2a({$tS!`0c7afmXMbW8 zoN(bvl+F#U%D(;Ci;jYx?ZL@>mtW34d7alDe^rlSim!lc>1~H(wylePxR&*jm5C?NWANFjPW!>nBDGQbZN(t;d)15 zugLoMP}(yaaM;cp+HC?=sT_HO?Hath8JBEeddnr|scvci09fX85mPU!r&EKOur-&* z{pWLfH~L0Wy(Bg#N3MX&m1dsPV?NP!I)<5{r}d6^CmO?9PLDGZ{{U*H%YVbRPh5mH zo!&3$3G!Phld)qbH!{N7ml0>gJXb*X6)HTG7g+*C6BTEN?}Le`r%<(uOpekpO;Ze3 zgk~iwjT1v~@U*I$FWm*1y-Zz3T)4Dz{=lnM8e27N2U#uUe+0U%F>xi&$}d4?yz>75 zKPctTy7hccmIk`nyp+E*B7fb=VixsV{{U#o6SCpQgE(Pn2-_NyDON00TIx&g-*Ajs z(WAGhvsl%KCdT96$g|AMHb6opaF0ntyNx01RJLAhTqecli-gC+_F;*WMovc^XQ zK*E1gPPqK2{)XXjhx@N|dLPGg@BF4X8Ti!dDo!2C9iu7c-nBG{7k@x@i#UbD4jOXP z_N%HtkBTD}qqK8BlrF=6q+axY(nYCJBHAmKEEZtwmD)rg?Y{U}+Xf!*Y9VSA zM(8S$LZWn{K#`Ab{eSQi+!NurfjH(%)ogiLjhV$iFKxr(_F+?FK2;MZE4$SSGG$Q- z>`6&Capmm)09eM#KIG**MZSM#0)SYXmtGMih1Wncj-~uLX9;j2$4$4#uQ&-(cMdj) z3Pgnx!9aWU7#1ob!>KX5RfJ5Dm7tlX+`PWbDT=L0nK9`^f`3Ua+a(#C-NVzk5}}!% z5z^?=tWfMHlXsHz!6{0|j%#`uCn$BvxERej$M&I^n`JWWjGlc5PxWkRRx{Lysy)H{ zQ3SIUO3dZPN$mds6a)4Gwkh?ggcpczj2QyCU7DIM_*}tp78`b@FNy`df|RI@<-DLK zG+L-P9u>I;1Ank|kPl1AgNEp>=U3P%eHL3YBnOu^i!8 zp__oR@1ki6W@5=%S3OANP&NkSjD*FZlJyKN;Jv{$CV%gdd@!kskyMhGD|G5euI7kB zLR_vmQ}-G>xNOcdF-*dGOE_las~A6b?u7u&U}QHYUGOO%U<}*Cly1fBIYButSC)J+ zr9u=e@#auqI}#M`{{Xros5d1UZs5&e;Y+x^-Ngk63Z5pii>k;#iF=hHY`Q4{71=`A zHWH+eC4WzI0z=&vrh5XKa>LFpp-kvwHlj*MzSLBx1Z3FcWS$rWL5ia{o4Z8k1wsv+ z{b4Hb@WWIF6)yJdg;b!)Qc^~tcQT9If!L6hp4T;p!-c()5;ZG4P)yqn)g+s#cR-gj zVf3v+lFD%ueg8< z;WEQiJ8UR~E4dgZ?>#%9CM*LP5VMpouptG$m74K@IWZAUsaQO+YHo|GoeLG5nUoO5@$wn zh8(C!?9y_vDkj{px{~JaNQ$aw9nZk zFDYQ$NJVj53(5H(P;M-2rAD_(TP{Z$x9ug-&dVmEJ(otDZMZ}QyCg0cNq-A$p5206 z1qGAgjf?pvgER3|@p?i!FA9q`m$h<{TNVD*X)QR1r}1SLxDGz_^P=;E)4S;RMt9`g za;}Qw(-d?&yHSySm`u8aB420+hhDav;RqI26}FU5)YrtPOL+pE+Eq?!yMr)D%qj6>mtPG*p(4Dm%oOmdiahlqHhdd&dDY;iTT3asOKIWT2! zwHNw_3KU{h>81YgqC>of$Ovi=7l@*7VnIv^xZy8!E#N-qnPuVWxPM@59KcQ7!6vuC z#f4P&VR_AX$chJSGCQ6cTcjEZluDEoXm5}TpqvaV5L*a(MyYEMCdDcWEZHQ=v|Da9&J2F z5pfsXU-o^m>KKiP1Ajl+$#el*VtOU0qNFm5`nZ%Oj}0x6Kh=aCB$GQ9+?z$N>_28N z(?VD~a;TmV&+NrgNn_oP-B4obVud))?~ufyb_C{k>M>N3f;TUAmcgnJTa4NfDpbya zsNHfWh5>@84c*$P#HmI`Sxa81l_2GbH#yrDszzdRhS~94JbyMX=M6zl#BB8ilbI(8 zz9Ql%WT_R5IYkZ?7zRS3O5V`r>;}brF`ttaQR-ATcJq#C)iJyG#16-|e)xqM7W1_t z5LV{nf~g1eTr)XdnHHqwW+ojjk!VzkCvaaPr;&4#^j^m`IX=?H6|B84?Tnd_{&{5# z)BQv*GZTD;mw%$(I1tbKH&HLL%9JgO{X!9y?S$gYEYUkVL8?o(DRCJ~ouZ$V2yw_+ zDb*=+mpe>MNPVb-6^87$c8W0ru`bONoo7oppyRPtsUBErZRf29T_X5JN)*KPRFS3N z8nnfz-8OyXE}Lb{zoc4$IT!3mIkO7$E!=b(xPbK$Nq@K_{G!R~r`QyZ6{xa&QZqt6 z(K5f~4TWh2%1X}RCft@AoHkt%=^;^rd_}COA+lPAGMB3Eu=A6?tdi@0SC_v%B4WhP z;-a#;r^xLOMp;>0L0D~e!1Gl48!g;xCnm1UzP`^2bB>k|$M49&o1D}9qqTN-=~3v^ z%!uL2K7S;{)G`sVm+CUEG$Y;2$0dNv{6B*yo{Z@DvHt*gO|)#!7f`V9;#@0=q_H*? zbFOMsQ+URFuSC!Dj$U8G;KA{NN^hxH)3SBi!B4j^{NcRgWP~@3&6mv}G_lfJru>@3 z2c&W)i*3!8}ho|pXV8WBcCozT}Eu2Wq-!rQg-~ni2+BFgck5Im~Vq5{4sF`d5a9m zvlGjv9~kymV*<&M`H z04UkXVc8WW}A5KeHW zYV{hpsM&I=pGYu118$QULwu5^Afj3zD1U=fxbc!Q z4KB+oplnt_rOu0Q>M2x-Gz0<;)?W!HgcnkDWc!0D~jx{ zha&t;ne%j!m|p@@Tb~)GTz{IfUm`oYOFs-T*)jDVU9hf3CNUl>dU@*`Ez3idq6Hkh z+^d?QE8=snruHZ7ct&1PUiFg)aSw~m8^cZIQ$H`qpw}LSdmDRb+emJ9WxxD3nkwtA zdrF$1$9E&qPBt7(IZn*4Po%-_hlCsH_+x>aCeHdTNUU?JxCzcil1V2xuTeoy)53dkoQ9hB&En^fw_b$ZZ1EZ9L|HN@xdB= z%Vfyt@W22|$jg|rB34-E;eJzZ3lPfWoSYQ#67<8Cup1tzQTs{Q1*v$T9z|mP#D96oyDAo^6}?wMr?B4Or78Wg zXMJtfm8lPj(2{An6nT-({Kuvxf<+%n({j_r;(?ja%8tkaRh}03?&=;bARWV15lxqB zYj{RF-T0s(!$I8DBs(bxaIf@$bq$<=DQBuPBGQe`xBDA zRX!r8>3=CyWI?wLyGaY}BrW~JUXs1Gt%I|*%ob9v2sb|}AECc*5UO~2a#(K{mfU={ zIp2`{ZZ2LgOD~bim_5qI8^xuE>JqMu+iIXgYL74JA5HvUdb8EItoyKKyjVgN{>yp+mw8}mya$yzs)}1_}rMY;Qmo1OQ(~M<^`!2 z55keke`B`3%4{kf3|KZ$NA;NuHC<|6wrnxLr!7D!=UXvgM$tQXNTL3lLfFYXo~oRG z>3>;{9xDc9v?#kQPkX_ok|KB&5raz_PRE0c~jyg5>~(-i9aUnr(;sc4%EEm4@m=Va8w zM8kYH7l}Ro=uNCL7i1&vRkMr_VFs1yKDzklWtMRgm)uu z_TC~`9)WLZD{?rFblxI4ecYh`0D5I<7s(>OnzXfo#bEE+!!HHDa48@U=Ts-R|luCitP;Y$cA+~a zwF+v}XBrk&(RJ|UZmXVtaE|^arBb|2l;`Ec@qsFrqanww|n)xvXoSoEKf z#Zz+ZJu6F>ZgJ4;`_XCgxwI1w;NibSb5XClPF-i#W{W z(h*Ds!7goW_+ohUlae*#`x%E2T_Hq8@62uRiqd=NH^+LVSrL z!?LvsQzrc9Zkd{CW(}urZ+d;mnrrS}OUs-}k@Dz~!TU`A0AvrmD^h5h6KLO(0y4F7 z&hA;{mw$KapD3D1@-k+?xed*-{{Tt3Jar)^%{D}tW?)=sNJ(wpD)p*piQ&_d{NWie z@5JZl;4&L1Xf0xDQ-4@qQzZH({h~9q1v|3m{v___7~cubSZrkqvdGfpJ5#dfy(w4i zCbAK9GbGhZntnwQJ&xBOk#k>Gs326Q9+j8vPk(Nu?gt9z2*}4o&mzm^3{y-_h^oa! z>ZM`HCfO!Qw_hS2tIh`K&hKtB07M2IR*kZWd+PY0)P)jyiYsA{Q24Eg)ia zTQL<-#x$%pWjTl@9~qX)X3Irg?#Txw(K7GXIASSOlU~kaimuTnTbT*1%!M_EfIx|E zbbl1{(O29&vq*Yl(Qvx+BuceZdQM^HTr!rLop-XZgrjN-YpUtXxfU5!FCUp>rq?EL zt_fsh+jd~wl4Kt-a^B2@=jWg22FL?C%NJZXv{}psD8ildH(1Z_5rY#Y&ne@q{+*nwq($> zqj2#MsV`FLrf_P;4o3@qCBjbDw1bH^MC{E*cx2lr((WFtyrN50Y!>uX z>ez@X*v-UffBh0djI}vcbAk6=;ccb& zd*f~t($wW+#$BoOQ8$WaY(dGJZ6hyh$~2;?k0zLx8#3^bMun9EdxjB4kAF|I_Mloe zR4Gr!yBEW_d6u6l@d`%GEr;+ueVm_5vdu$B0 z+_h3(mTj;%@SwQsnQA(~QD4N#mky1KF(T~ct_+;R%Wo0QOj;+Od}U;45UMx0Wlr>Z zlO|)uz&q0N$|`2%4cwP*Ve6IZwOGgtfJb!IzDw?kWiL_{XBL*p0e?(MQP~kfAeZ&8 zx}3ShnW=yO04ZO@do7}EIh!i9J@X@-)fu-pZWqhHDBr{6)5sRWt?{w0ad)7W9I633 zK&v|=aP*Me(S7mV!_RM#f%r@DC^b(;VA;qx?P}@ia^VAGh<|h*f6Mx6RxSd=e>H{; zOl-oa4auhUnVEhP6Mx80%}ZG3r{`K&-HV6Ab^ic(n)jnsI~P&hs^Kcr6$I)jdyq(1 z*^aLl^IbhcGyWfaSeyKfMT#(d{uG}nY58-PaC{^xHzaxvH!~JR={WA5pGy%B1*)kX z1Z{ybz)z)Nsv=)tjJu*blvlCDp6!vCOYI(^_xDH6{{V@<9Dm7|*F6v7^WUVSP1g=y zu;FirWtkKb5iTcJ-5fqUo|_6XEsFLJ+Lzaut?6fGA98bk+Y)ryGTb*Zh=uy+6D+^) z9=n9vQx;oG*y#MQUeaQXp)IQ6W+;CC_&4`6JeyLDv?Mi_c(QxsweT0KGL3Sc+Hf&>Z6B_%}9%_E)5dbLehco?A8 zI4*-qDwQ^^hH(^yN!fvPW~F8FF3i}jN=vUe#mHOYH-F+rPZMJ^F~QSvUCQgOSn^JW zhYGI^g`bNnzvYG08kOy&V35a@5@nV{w@Zqf1X*y3Cb6T9m(t5+7m+S$T2QRJ#V+mp z=1S5M>Lp0^X_;MYr;z9H=+;tGb`wEqj~RHQZy%V*!uZ^eivgRZl$qK?#U7)L$y|p+ z($|Qc$A7*yC{?J5rqCqZCE~PHxS@Q8)*#`2CaeC<%VMpJ;;Kv1?!47feUPx*QW8>g zt3KpQqvu?ntiF-zIM(zkJKC1cfBIkR)ciY8nB!(ivj8Ge5e4$kx;C?Es&gIMe7ubT zx6DmPnwEK^CX09~KNJva$2c?6-vpG!HOJW<<9{;ea232+Mw~w0LM1eE^u(zNmT|mM zKe77KVLhW#=L_{sz9N4m(O}_%Q|kAi9wsVY)Me>J;0-qr4&b-Hsw=e1@45l_{-T=E z_H{3z?4SPtN&f(*6=P?0!FFDaiL(l65n3CUJHtNVSmeUnr1TsI_3(OMB|E-ChH`H9 zVt=v1WO!e?>%H-k9LDHR(xu+BC2aG%@WqIfsH&99v#Qw@?UR3oAc|Z8^Mbg&kqfXbCe;?R1WIM4_$mgMVe?L&$iN2_Hbzl%>pEd$SP~_;W2`$vbkd z)LJN$1wSm^<#U#(x+URU{{X5+-saV9kK9JPFtk>V^Na6DDT^0A1QlwI)YP+r^Lxge z6}lOP=yUlvcBY}m*d_cgGEN|wNta1`ImfHvbJhA-^Z7XJgdB9IxPsq;_D5C4hktV0 zBQ0&$yVPF_Bw^%pabx)!@o@6FvM5^!p^8Wx+VsY_7 zGTW3IWVNix%C--22(Zd&rO}66pzlJ=GtRz86bfUn=VOb3J9t}E&C|kjmqd_F?Wilwt_vgFN)*85u+~2W*EtqG|>!25UX%v*C)U41=K?PwhZCNUS-N z;4$~cYK`2IlB6x$lq6d@Dn)66iVl*$6Yb z*ed`6YI@+zN;Jfl&PU8n27g>!U&RLw11`uqr0Au}pJ(odHcbOCrCPBu?D(TQ&wg1*d2QOKGPzJKA*SRFufCO71O zUGQwW0c|E0U`JR9>|LD4Z7cI$C+A#EEJ%=M>|!kGY> zsV5~?;Mb}GA&8lGmWzP$Do_771x`}Ub z)T=i`VhdCxrN^Z^po*BNXgKQ*?~x9JT?N8E>&4Fy+r@?7+uzFvy;^=zk_+RA%S!!xyNaV|H-S zxQ7hQD}11AU!}3^yx^9eA%Q2RKb4rtGX0W?aZE@{&SV)~5waIymOp?00+0?ZN)`({Cxhe(5}5xIdaVgxytE#gAlD-IbR=vgc-%!BPkunuLm zc;F~fhHaZyFMryAl@#p32yNCUP`U0T$g}pM=uno4N1x${sve+K+qhtsK|<;bFa>QG zC$4WUn6)lvLS9V5C?x=;+_15c+)wzRUMv)LF?GQiH3LTPd!od!!RJIzC_|vT4cwSd zh7ia;6a&`h6$o1{hBE{(U^5ZZ330$ra^Y4;$%%d1=YMYuJrHW3$cN$KjhNKBp4{7{ zZN~$Qc6*QEj*1k@DaKGjG$Ml?#?DELk*zxhG$x46T8w8BYv~5#*x`p5!uAvCbD@SVG8%u2#hY zE3kuY>wnG)#02Dkm%{|CEapE;&rW*&6`<6%dnC_eli%F=7gN30N2dH%M!>dES$-!b zO>Ii6IGL2=w-3d|9bfj2S#KgQ6=3x@Yg3YUE6yCi7leuz`o>=(Miqn8+g=&K-JFY7 zmlsmSKQn(3l#&vA8+M~57D2zW3;jTG8yQSusehLPR}SyB6xsGw0DXNnN1phuft-y6 z(`1~!(kiU5EsVvYBX6^ELB}U4DWRj-x^eL0bE~QWxeX-|(-Tfj6~}o%GqjD=gWZ`z z$}-6~C&Lg}qwNHlEyV@aT~7?0!=4g@tCSJCe8FqGC>oH@)i09j*+t4Q?bKM&W-`v$ zi+_!TivG^bmxrniu??Q#M+~@H!1`_j1ROf|C5h>{O2UT?x+&Gw3|vD8P2lq&>eeFS z6w{!CgiDfII}|iFQ%gb(P^*R-Gk<)ox0D*8E)lv&CW-qn&rG?Uh8kE48V#it z=%j&pJ@r~Zwmk!JP16K)x(WeCU+=eI^aAiw04wEm#3@g+;%$aAB3I6z4S5eb4ZV+h8rS?V(YAu*6RJnrC zCEqXp=CEqSxZ$i3SDznKOP_E{hlX6s`b4^svn&Uf*zPBuJei=?9Jl`fwa@&aEb15P z35g#^9CG6~jv1(k`I9O!Cn^B>zkg{rg8d41owW+U$kFhu{M5k`4Wc%PX1Qvs%kPFP z><`pv>1+f40Lky$W-kFL>Xr|p?*T~x`NMI+UuAMklklW`jh`3Tt!;DYo!ez%wtw4rduR2z#zcK#8;mwWzj-7lE=;Z(a&cG4Wru!)-$4;LclhQ?bL#M?T>UsP48Gfd80f`1|-aF@wbMTNd5 zgGKaoKEQsAtW&iqIa#^cd8t5}&ig{GmFe|$1y z1Pg5WfBRsu_n|vAyniHB%ofc_nUO1woi)NF3*@V+DtSjYk;GZ?DKWQ_Y}3_`L_MO{ zncTaD#mRery_f+Um&20A22BMRa`_zB=~eV9 zM?;P3&yk|=ztEGmi5yE${@Br4!Z>X0L4H$zaQ9;OUlyleJ2r5%c2ohYm9`<%Z8pLMa)Cwi4K&Z=QHyi+sN$gmHI5< zU97Z1-7gMP=PkHMq*`oAe&mF<1&}d2yAZgmWe2j_^VHYr^x-B-(Ke;qw3G6TF5aI> zGvR0fB__-&~dI3sWI5N(NOe&3IWBLVc zny<19b~JgbXE2>eRjJuCa_0AIqLL!%3TEGug_jQA%5(s!YU_^5HPsz@R`w-3jz|DVXA)NROgN5>H~+ zsyH53v`9@7nM$9lafR|s(0)q3-zuF*gtwA!LBwh z^##t8^gZo8nfBW{dDvbdWx}`eNIf(I>o?TdG;g7|XfJ;=Y4+Zfy2%#^f0R9KBIVov z0FXsvkD?Z>g5_o0#Pw(B?9FGpC*;&_%br>Dg*;oxJG6X?q?M5ct?+a&a3A3jG;)K2RHR`N^jPs{4>e z9H==n;-7zsyEaR(+#j(yk#?9{xuO2|!bud>kvWlEzs+qasdLDun#g^UjNhlh9}_Ea z$obv3QYFuddhQ<%Tq_Mk#UB#A8j#f=NUAim#H%!JTk;qsY(ueO-^7TR^s=Q$-D_@< zdsjjF>Vs#9qZ!Xl$w^Sj88S}^1E10wpOL8>632hUs^X7M0iI+*xmL33w1@e{W#_z< zn6>dJ_0LQR`KytpMU?n*a$VPta_8$lEW4veA11^-zQ&}k zRy#$?c@5FE*t!+jiOvlRrZMGXMn7T>r13NaNLHzTD8}qPO0`+Bn7g7B5f0qK)8<5n zhS+~HfmxMh>a@V|yl5g;@d`#v?y!2#;p{6P z&11AEGcA#dwjuaqoXxY;b}X`EybF|!ir%7tp2K$|nK{B`;a>@GloEp~OU^hgO9_pl zkTcj?xe-*J8Q>K8^+lGfLXYdWYs1A5(1U;RoC!t+7leqdwkuFm#>31r+vEg?!ck^7 zT^MAomLyRDD1x0#J?kl?3P2=2C}EY*jiU55kBGV&MsT_Q0>Nv;rOz1bE-LOg#q ztYhTl+Z?QLSKe;zd*c+x$Z^WPnUx1|M$_r^Sx;Q|2wEfKTT(ioicgWv{FBqAPM6q@ z%-Yzd%cSXnpKO=}Im7W0!+DujO5$SDlCutRl3U`sIVSIjo)YK#Q40qCy7*!Zhbg;S z#zBTK#KWKN$J**%=oMw2keRf$G!}myahBe4a(%!Pm@CMfB=3#xn(0#ycChxLi6kbt zTXG-2cqLQaFMuD^8({faTr~7-uN}b!6R$rRko9~xx7>xi??$3aa-et zCm&rvWz5s!LsMn4~WV zV#T_PJ=#IH&_i}ZX(2@eQeuabRRUqoCY~g>=LbE8DQcCK0+S{WhSK5B$_Ju%ei2GCHSS~t(VAu z;y*6jHm}6fII9dzVEOkYstlB@(`9iFw4Q$SkDhY)?SZPt)ObITUP^!2NQqYpYH(C~ zy_m;#lWtkjIVxg6N@K_2h^hs_O$aswq&FlSsCOge>jdWrjnWJD zm?O>!o?E+ai|$>V1hkm4^}a8LBp`R#j6qy#p5Ej{+;ze5#X5{c+X1JE%F2rP%fsOq z5%lULCMFJPn-qVB7B(WIntd!z+{?q$15ty*aU?>=2t-6~;x2#axJGuWiOlMYc50Pk zz_iR+ioUxkF%@KN7j5Eo5s9r{{Y%H@SQ7G6zbm-xqi2#ZE~6} zut3b{iWd%6m*syPzs!CCc`Ic-C*XgG8-HgrDdjYuI;SmoLeKGQmcLy#8s~=|I zep?446_nVtx;Rtigos3xM&kbfmN9lWs_lxl+C3qf!gvC4>9;#Pojsy<%eYBVI&y?J zIiLAPkJ2i2G0DD-j85$uesdSp$jI>(M0WwHZkW#`AIpDO=wZUBwKLz;YkW?I^px5o zTRXtrf=S|W_k>z#M=t#q4=St)OgqbV9{LXYHp=ab6O^-+A*cGJJIIpfT=o%uQMd7b zid_9na(_4ZcH=E^9nFETjPIj(OVYncnxHuEtaL>PnlEWmJw_ZWkEKtVbNTJaYcYPt zlsT7;x{iMuaGZI--nuOpt93u3`=Rp{IYizYsZue;9#%qTROISbTtMjB;F@z(SSDSD z#DyOa%+Sfn3e>B!Qtohs$5rX?ibmK3r5_KsPpEc^T;}W?>S(%4-IOsApci67a$)Lg z*UZbyywM8r2Uc)kDO!=*1gwp9#_BAbn-6qb(kp-O`NHm@(9SU26q!}&u3BTlR+13? z-ssNqIB$jVl$OvbuSiXewL4bf&_~^uo^WsOy(Fbdl`kAj&}y{`B-Zq#wH6&d8-5>} zLQ~(RhBU24(^!D;ttxn=O5mL0bKzfUA~srdl(W)RFy1Db#6aL9Znn=!I_^L>FPEGb zmQn|snMH8NpaSM;kt+RNLnA;yR47^iMTf3D>hJbS7MYR$$ z<>!4%OieyTPZ{lIT7&h2bn$C;AujtyZUC644f=cI6Xs_$qEOenV%z!3%+?<#CZ*At zrY8(V=Iya~bk(GzZoK0eyznj}@&f1}bxRW_M1 zR(a`X>=2kGao62$!fE90&$$*Sa`dz-vzWjHHo&u}_x?hQ?r=?M5>*Uadr$e0hKF^7BskWkg4~#&d2{k3ISeB`Yp2qu{_5&9LGo zTh-5+suUC>7T(@R(qEvShchFv%wdfxlsTr~#T7@6N~lgKw>>V{2sjodmLZ8K>8-kR zjgM%=8`f9I>@una?aR081+Y``+&b%;ThBdz6my#v%ySwXO4g(&ezK&1$vJ;MY z)g_!3MA>j<(A^Skwmf~w&BXVnu$x#2?ptZ5vkwvz#^DpUvm57rNw{E_8p1#bN_hsUO?j34O z;W>5Lcs)L((MT5V$pM&NvR@?b^G@ipo?9nLu<%aW>lQaU?8Ci~jn~3etK1PZO*2(g z&*#+?=yUUNek8M=kd)j-gq+Gv+Iy0+YBNSdcJv7Ky%RkC{FXCM7{we`QLRfCQYw?K zNlKSvYH;Ol5$b=)Q#-O%T)tO9`UA20vE)?o79^$byCAt}vh2gV@#*`KbVc_f!fCUj zp3GBrm5V>Jtg}BWF6(SIa;+Gad`e56c`kg@-x?zxgNbKiq}@Vqvv#?uhMNh7<|Z0o z^SRI^sxwW?$hghzposz@Wtyg1`JyBrU^Joj zZ%dn~1C#U56Ku?OxuH9hh_X^e(K#kv@Yanew|#VylJyyUU5N?0XoEDd?DFoqB)m(I zs;)_&f4v|fi>8Tfo3y*Rr7Px}DwDmAR^=_~B6+H&e|!$$w(%#axT>pzY7L~soVzj; zz=9;}fx~~A_j~^Ud?mVvCy!CIu+B1V=ZRasX(t|RO4kzH(K638O@4ozWO%3#1iYRb z`nG;vd0y>=B=e^0PZ<**HALs{{NW6m0^qAs(=#U9a-ssu%n5ZZ!b-V*Yvsx;io5bH zcvZ2(EV7fMyJ)93HhRtJDkgfO^4GiagNa{}i<5tVyuDHxx;(nexNMk-aF-CfaqFxL z96ZaS)q!e+^vuH>?gsiw?h{ z;+KCUbO=V`6-X&SlP6KC&FYmOAJ;oZ=w=+4yvFOaUQsyy=G$1=yDy|Miv!m=xi-j1 zh)al9y*~KTxOQdD;%t$mg*Hved2wrZ@c#gMo_{p+jY^+Wn_$%WLu|I8QYxb?-KiWZ@Wagi+s-^J4x zDPpX!bKj;HppF{!l*7&v$;->t$V=ASZud_3=Qic}x5+XGt5nrz-7UZ_xZg@ApJxSPD8u&P_?zrd)qD8m1t4bF+n7U~}<%wS`1|LbCk2gUi<@D1g}2 z<|(qW{UIwVJI6ESa^)Q!HgB*lw({vD-lND=q{v8kONd3mv`IcuvVGU6Ut^O!I=unv zTS%fkS;1sf+U&Tipez-l{{XpD-yB@r7Dn|~{RO^LhPGMhnT)*|c7ZvD@pXR=c8gIP zSTcN68g+Y&50b-ia{5giK1$C`dXCV*evlY{(u)d<7>Vf#dX$)?xXoxJIr3kWYvXg7 zAIROGikVmWx++^(*iBPr^8{i+P^R?+(;PrZyrbCgKPtI881mfC23#MxO?=>x>XGR& zH^}Ag01(=F!wVZ#DgOXiNH~Ajv@7|?C-VGnUjr$#hnLgJX-q`p)ViyTnEJ-X!1=0k zkCx-;YO(Kl&viNQYDr7PlZ_*j7K`LAoz{^qeajxZgPZwQFgKGMr(^qYdQj)qyy#m~Mq+)F!H-t>L2OV%WtlA4^8qt@KrurzLVwb-LUL0O)Sq(UHYMF?w?xu$WJzvO%Wf~Pip|n;UD{gKK5td3Sl`e)Z7sPk zIX>K!tEff-N4lsnn5uvM>Y7IxGBymplO6sBReqD}R>F4OBew0F(s3NSl5*)-l^4mT z9Jpn^51ey9CB)NNPKInX>QeGVW70Q!1Mc>gISuS2%PtB567&8dh%BXJotU>+oNl8j z>gPYb_DFxuH}Lt*fX|zc>M2gFQzj_$g4wcdpHO=x9gZh0a&>S?U#=gXJNrC(E?J=e07yeA%9kRhH>3nAF1d1{55kbJV!)=R3TEtx z#B{s|rXzoQg&xIo7E&9oVOq5%+q*SVRmpPU>A5stxpjfb;s#VTzQSmeVEaQB^c!9u9wvz8*b!1J^WZPfNZXt9%M0mz zj5u(rH;HKU6Z%rH->0Os^%6>#HHPth9i=0ZniXJ;Ij=oar<`7XGi1fR%IU z%vFD;=B}7Yb8H8QiKcG+SCnu&ICztNL^Wp|$k1LFCZ?`svbfFHx28Gypy?Mm3#x#n zL{M$%^)=ccPGbbsy<*z)gB>OQ6BV)4~K>uxfbtH zEy~=zJuq8=EW0x`O}H)*7+m{_XwjN`3eo1UmtByZkY@AEBRe271gkBPbMZnM1wVhV zcIC)dD}VPwrZg=_0?tk3g9w=&wt%nYKN~fT?^v-P%|1Fh&W}Q;`X6Klsjd+@oO5Q1BDMMGD-5f zB}phbxQU%P{4ll@$|b^5 zuo=ND{oLRgOQ<(%Jb5n^1qCt=C2Gl@eK9B|j7!sMmmuU{lmj4Liw!PwrEZ1FjYzRM zO{gG-OZ-ul(AbkwT26C$&)S4Cmb0>6UCX(`rZzHz)*j%^VQi$JMqaYq;%W-rMwad`|S z%BcQWltWJl!sUrk+`GlYoD5EZp&=&mZy?v)U9?O}xtivYpm%ZfyNW#Elr%x}Z(q-- zp_;LxJC0?_-hb;jl_-BM^{@{F9Jb}%FccthB_$l;Vp%j9w=Ot0?O6VNI)_VF4_I<^uZ^%PY~ zbz!5I29RiidWOyp^F)R!jfmJ1li|&HV_G2mQJ_vs#>`MNa3Ft%*xrGtrrAZ(HpgOA z8B2=ujR;$Gf_a`j2Zl7O7OXUSTyKWQa^f}t=`}t>4NNKeJv0pboQ^%LM|gfOzeL!$_IQ{xnQFv*&yd^5{9f` zrA4zZWMD`jn`v#xVheF#*jbW$F$NhqU=3HC9YtJ586>+~pLlQm>#cOPKPBYXuKrM$6ogP5@%Mifxya z{{U7|1+#ys;#QL~;(n#v{hMHwv=6MI+r#t4(Ayzq+p1f&9i7*i?HLdg z?W0;Ro_wGg^aS$$t3T*j<^G8syEtu%;PP(nE;)au#7F0>19B8EUo8Aa)z6~CYonvnh5in4GRW zTJVNL6%GVGFYyxhqrT1L&o*W1k06&2ktNV~De_|Y@BV~?J1N=ZoVCB%^O#==6U%V^ zPz{u*$fF?HJ{iAvFGB~7;z0l$%!yaI5h8!Cz+GV8J0JQj=eCb0y{HZx!m`sD5O&<( znalqGp^jO7>jccg{{T~pf4JDx-=TFRS;?$XL3lc^YLad%K>t&A?9LxO_wD~s0TE)(}Z%cnyv?KD*E>T{pg4weDh`fJM z=p{XX*|jDoT$B;GU27r{`Q(wmjO&mDmY?TQr|}qN_SCH$Q z6BLu4F}h?#4Yz!=RZSD$8xzt`A|iifj+J9%57gJvvMm+xjQMhdD)Lb@!vwDRN zv24A)nZe#PW76m^B1qu!Fl)pKhWMJlZCe?sW9^>JNxd%9z&u0ZA^p&m=@@^I-X{ZX zV`McfCo)W|o8`o!JSs&S7pTtN4boZt8}>6x!>cQZaRjJNPZ55HCg}RMHS09*UnGJ= zGIDtuI{O0SiHD{{Nyla7?M>JGC8+=98@`{<*U5G8H z>|8Nj73qm7IdPno6*h|AuKQQb78{E*V>DxZ9Bs8PM}~HZylp7*vU3fRkbyRr%DL`U z*DT|?i#F8JYY{jUDw_>CcKbu@zbZUi-0gSIPEgd#MijWVT<5(eYPEl$Bnrf=m~_~~ z!Nd8%WaZq@h|BUdr8jTbo|}kUnwG>J|UO_{!v)CZ6$weM2PxMY=W8srHv#n zANNd|kM2m-{?5zw1+uJsjZ?R3_R03MJk3uG*QI8$8-#(lIdcJhSDtyIBxz1ITz?a0 z4C(ecCM=WEguLAMHA==BD5mwr{W5NkVh)pZRb+b$&OHa8_>$%v0fzdS;)yw8b^`8c7(V{br=Q;Q{@1H}|U?Gmdl?d5IB+*u;7 zc|#YIuM;+3$!o-g*}tF;JK4=al{|TbrzIO65Oudn!TDvR3i9*}eo0Q0*<8ji**t*D zvy8CuG2&pQ-!aeRs}cE1{{SEQl)UC&9Tu0ity`MnI>vtxZQHuWilkq>;SHI7qyGTf z+W5dWai%%N(~QZ}vE>?8ZQ?;_N`e9<@oG!?!LK2Hup|AYmRhPo6%77-{ZN2z$Yq1eim6y)%fwW#H#9gZYu#N-`raQat^!*G-} z+=YH&EfN(crJbI7A7E&i}alBxgr(n5_Fodl$eJGcO~BxRQ43puoYxh-Xu}sFJ=aNiW^ie><)i> zHt^fk1s#Qzsd=OABkKvliz;Jetc_A(x2#e7F$8U3FyB_3BO$wU!^6{@L3y(&>5E%3 zCE?xY`NntYITGz8EXHm;OVxB?X4qs#Dy%bxDDzX(E^gao6wBF)rp88HDWp9XaIgDf z+Pza6RGAkf>F%#(BJm$~Ug+%NPeXqZ$A$HB+~`WhNoO7`@3!dV<$awvQJvQv(5dWM zg5kR;v4CbKw=9wSFfSGzU2Pb|*_3V1T19z$F|+afeYK3dr>9A&wqJnjTWQA9 z^yzYTV|Z6OUJ*|C?Xxa5vB*ScZj*iOt7CFFM**@jvf$j^JWxo4c%4rCXFxJ>3MSt#1Tk5{z{X4$jTRV)(JrD||hO5nGO?x-u+7)2dTQpQ;KjoFz- zd6plG>T2z|dNj*z5<$wHHpyDUVx`k)zMfw=+ra0xCOLnU{BAB!SIB?YI|1~X#GZ{f z&9)yW*=dtOb)xz6NcWr%$*ZaJj&}jEHCa!iTRGZp5A@8zErZWBD^(!in7J;$yExI# z{C+NGFWg@T@>%#zV^r-wWVMZu)ktUMeO=zJXfr0APVIZ6z;nMG+;-|y=s%A9&MsQ{ zRlm5bePrW+Np-7l2}gfEVCtlbmYF$;Y@9Bi7qqcgXc*o?sI<-G;VSeG@j{|lY~|9D zEJR*Ad>O#9kj}3?x<_%gnUMWzr%(D7Henq+?Pctpkm8r&I@~P3U3S z&6Y*Gf8~bi2d_|e>Gz9)q3SHZ>D?N*Ts*vttBF+2%>0fHCM)G=Q>X9_ zz;Fi5uw?Z^Xik4n@g^IpFyj*wQtY2@lbRJnMpPGxv|C9t?~kQ^Kk!&M_$`yEkB|Ou z_=_hknQ?Tf!@iLlWTv-?=BRiMRn}OVe1c9~b1lPb$u-v`_rY>=t}IqKTn;{dMq01P z*PBiBA(+Ov4Y`?l$6UmM3hTFitz$tL(oO zIVm_U$Ns50$hK3$eoOJWc=>AGCr^4h`dd_KZ%d~7?b#*5PH{)4!O6do z=J`0z>Da%dl!iKxY@B#TYs4{=@?V6(%wD6T_vX+ zCHy;|jq!i7pJDtb@i!Zmy-ij)_tOHkF|-ZLn{A(EdE{t=!b(~otZ{hUU-+!2r{r@t z{H$F@%h?_iZwTSKY`YDB$(m)>QkqfB zh(n1pOug~Z{C^EHw^xbI=kxsxRz910H|#$I(eQtfQ0Qa1s>_03zZ+d6Me7A#<&Rf~ zjf)pbpDp~4J0@JVX0dv#V`=@I%{NKKa$zwfM1s`7@e8P$WPbkud|bE1j%SxqJ2f#- z!}8MgYAY>vG8Dd4c!khiP?9fBSv@r;NXH8=G_H1fiz0>o?a^Pqnn6>3AX!Pec-2{! zu|y`B4&XxvMyUQG8!0 z5?fxLmz_ePF+DjjXwkG>O^W$xoJ_BY6R0>+3leRDUCzhr8X0MaU1N0CWK9^RpPXWT zZ`3G5T2?m9H3O!$E(E!~F7%VOOSs_?_j{FND_VSqR;In0+Mvu$Q(Agqfd`m+n)!c} zy4=%)$rm;A?}3iQwMyL9IGAi3c44_Pxq_t6+~ap=;85@T;gK2zout&8VA^M7kYwfx zYqUwUvJ*~(z!5qh-LJ_a7+4RUM6z4J&T*B zvdMO)zeD3_jIG{+-NB-2yjb^^90Ev*WbvK>_*G_ zOI3R@G*eEp7pb%*G5UjE{d8R5rvSuZ^P6I&*MX37a&WhTrsy;%;ct_Yhq@=E&>S+z z)!nB~lDOo(EuvY#K_f9ywNu6!c>VCTR*{_qJxZ@ooI6~a;d3IfqEZqsX>)(^!KyH- z09qX7Qe;V%XC@8ev`PHN<@QZ`prKYsjk64F*1J2qQQb@Lu4pXvge&Z2+>qr}3a6!Z zF*+8c)6-cHDH0ArYNv<(s|`c}ypOHEf$mA*KSW$7BS3DXXy;I+&;pw6j2XA>CAzZSz zB0*hLbJMt?kBnVM?bjWz(xw;@CT1l;C+z}JCXuxjQcUyLBK{c1m56_%5A9P<1kyZS zxQ$&mLBJ9v%UxG2*QI|r(u%UgXlXYt1*!y|6T@{9YBIVMC(R{|bDA(umm?Dy-GMa%X#VdN6xpd-Q)J_^?zifiBnHI{flpOMUTybxLb({O7b17!Z+(^Exq+5#PjxD8I@o{! zp#k~iXT%5YBl-bi%N2OVL&(a8+b<+VR+jEj?OYhH~Uo?M=*>SMKD${aUnwn>Y zvSm?)omePhoVO}YR z3)SB1q5|Wpw=I9X^iOB_qo9K&wcuixFvc#TR4MZF0L-J4+x^iCV&*^@Y2}@|kAH_% zi)0QEnr8;g;CwwXHBE9?aOAj%HNHY21zmK@m%FJT*VqPa`5wVr^y&e>XiRk`w#`sJ?9Pm*0? zH=2(Qndk7pOoqwQEwXvMF*!3lBe)hGoM`}-_foiS1;Ro8MtZ!}FZ z?@#FvzgDHzhqJ%!6-Z?2v(r-&!ub=0z&oXBy)w^F4=mzKYrqf`8ijilP+XFyJkdzW z(~4Z2d~9)s&CHi~pFg@IKE`Wiz{2!d8XYydQ$>Hf0(G%B`iPW6K5LSD@>KiaC)l}S z5s9gTWwvSJ7BX)aY%u}RSMSTK{FW4jtgH<2u$+rBboXWksLRCIJH2x2vt30M6PNFR zsi8W&4U4mPo5N=-AtO>5bC-Qcm=ZgM5i?D4^78xOpB}|dO`9H17(G@wbd}z`@i61B zu+x8rLh7obYnLqh*T23nx@@|Z*}51}&dQ_P$(DCJsZecO7LjdL9MyZ5?u0VvU5NUX zLB}Shrcs^DtIxV(B;sDu^4B$0_Ld_7s3a-4@|#I|R7{RymQrD0^q8J}*DriM7k(xz z+%5GNj5k$gtmu@C^G%-+}HXEM6oJGZNAM=4;%p6HqpCc&?S|8VNiFk#1#s_11M$bx34Aiy6JVn(N zVB941^EA~LCL1RkaD}cPHHmJJ5Y(GWY=7FNQ0}?Hx9sxi{Gc$$QMoK!c2|gohe3as zb=MjYw$l^gBFkHWeACJ|w@8OJO$QxHPfItH)VspqTrLkw=$y$t>E#+dQ=cfMo?f8O zX3ZQME>15ABJyAJg7pwf4%(b);nUTqmA?*u>y#8a$TgE7rd(ZK7?uho!m9SC!Nl}o_8*Y;`Hs*g_1>yeyye7m#tF}#sPf$%y&C^>STYV0GlqR-}%|3sRQ>jQ# z9Y};9sIcM@f%j+s09eY)wySi4_*=QErV*3zV+^0L;1?Mt1$6&;J04jNPpPBW_ja07W=Ov|*yRn5yXnki=Hl7bSv` zE$aTLq>GteVq5p}D4kK{Pal8UGL@+Ws21<*y3&y#lkSe~p-*dA)R}_P&+aGXYJB% zIe4_zBdIl~WggMbTFD`q`#&X)q)yC}J1=y7tQ@ShJQDU=m31sOa~YYFBObHoXY`GH zJa@{VK1PG=M$#~&doc7K!8GfQx!LX9!CLa&#+R>tx)8>H$-aN{)l4~^RZRoLN0M;E z)l}!N?I@m*-j6izNGu02Oqpe&pAk}(n9}B#+56+0`3?`NwqGIGvoadr=*PyB+ZDu> z&6VO&2|4*0q<&0Y&iRxpx-;%b;X*b{Rc|BL@c9Xs8Q!$ImuLcPoay1XdWn3a&ga8H zS#fBQBG9fITpoWOo|J^JrZ5g5+h;Flt6w;p`vWQ`5^U)4wi}q3X|r%;`kfKu70ajP z4#fZp=D*smUzGB);8B@ZfV)i z8&s$Ed|!V(!)?cK#;h&UbDXF=KC?9C4v%7#=np}hbaa~Zr-yzbUTMxP{nXQi zl=?mB7qkjguG(3p)ZPZ(sH%2 z++Ood$jnYG<{z3NU(=g#_%dEgl4%^=ZNKF=k=}pcWnM-vNxYQrrscwG)=RhA8xv3x z-f)Vhw`s~Vx3GufECQTsbOcMzZte?~ z(urMw{f%RdDS@-Sw9#v8X;W*!w2ATf*H(>w{Ue)|TOLrbKg6|?lV;*LCdr9fLS7Yw zH%Nc^TCjOHu4e0uM(cZ_sqRRflQikXR!OlP;rL70Sap^%lM#KOyc1H)%q8|X32olj zfA5W*jZ^Mn#|6i?YvL#+_b83VQ;$<=Ot~v2)ZyHsVnt~RdBQVgpp2`t=TFl0L&O!y zYF%EZNotoZD<(`Z`x6La>4A%=T6g3z$IX8_&a89s#d4aT(N3t=82U)?P9~Od**R=o zK^`_T7T-`jocXU5dd?nJOigd}J2=%{Sx=}Gd>bdUj7aj&)GAHV+#+^Vjm{zj}tYsjJmIBm<~sttcdgPu`c1Y_kJlJ|2M`Lbmxh`IR@&rTTFk|pXk zW=7YnNHI>s=4qo~=l&R$a6vgo5q|#w-3rU>1p5*)a^g0k4ZjQk5!6Ua%Zp)}aYE=* zuE!0H+W!FUjLv|1VvC3D!kH8%P-XhqPE{8WrZ4aNp%o`#%M%w{lrM5y4`hGl3I^ip zIVp~qXe^_7r>zXrsxG9C!xv}BPA{=zSEsJgImEGb7)?)G_+W%&7(G(pB8#xENM$ur zxFFSp$`I8Gmmde>@Wo3K5Uy4y6s}c@Mh2t{^I4fHK@UXO0-Rg&Sb;tjfCBxtuMt41 zut=)G+i6bXPvVHH_6ySr%PN07x?(Wo9I^#vz3?;%gLZt6UleJDB8F^7n)LqwSfgAh zcGkcjgkrS?J`rvh`&vZQ37lg|qTL8CUVmhQn&o$EWm#bidm_31+a32 zvV)2AP037MZ^Z$Y4T{Q6(%$|k#_(uTYEEj<7|b1qVLUlRlqYe^GYIMB2%g|F{fB~g z27++>c(zJ}Wt**ysDTazwEQulF%-%Xw#SmgYy`zaN|24WK!IWOtATt*P@zZcyqDyq z#Sj5*IF@AtQV_L5{{VkDkK9=kGUd8PR$-SLc8%MbLRrgK6WX}_rHrmN!*?9JNI0F9 zsSx2^$_mBQ1A>d9aYF1$fg$RGh|wYe6KGl|C~5=;aooNb&_f-cyu48vh-78+w<7-l zv@EIYI%T~w?uun5pmZt0_MtT@V0$Q6zo&)?0F@@>Qs5O>(vW`?cO@BTgC*w|ozZGQMBV4@eW+|H1!g7AB%uXBP#YLh zM2eW%ECt5@0E&MXF<^$z4Zms-=rtH@<2Pv;!x>G={4lV4j0u}k)rC)B)EPCDSmup@ zS$*N<-wj9D6bIOK%f$|k#9U$?l_6f3elYt1?4nvI#-(8F1<8If%flTGGwfo6CUg5S z&_ImtbNFLYu83fz?r$h^5N$Ch>dVU5&=p4ektn5M_6C3CuK{q~7jkz3+?d&)cT5$8 z*1{Z@5%B*2?ulaNDhy>rkK%$OFyLj;El9QM0gQHgwD86M07*h-C2ejCs!Ooco8!#} z37bOfXb+@fNB;oCyKbP}yO>YHkOyF<-0Zzx&;=BwV`q$n~9xSEhfSwIs^#5or)#tNTZd`aRd2>X*0i zM6v~aj^+It>uw%?s8ix7mQp+De@!S^bvE>O?Y2hk(>})CEqKWCGO{%{`eKh z&|jf~f3i5cYTk;ndk@5~QEPZ^Rk?=vn=;PVQ`$xEgN(ZumRTCCrEZ^t=v4WZ+zL#V zVn|#=-x4x{*}gR|c$*wj@ic_1En%7^KeK{~8%?Fo{7V;ciOO~Oz{ZD$SgTWJ7Fd5} zdrKyspj!dBJmQw`;v8}sO_Q??i=ANGdZ|x#*98O&Te-1MX!1=Xb}lCTgnps#Q=sr} z(JOw6SX4}Kof4@)Y?FUmr#HVjYnM&xCa8($8*t&ooh}8vePni z4@u4n8&1o%t^uc-Dnqj-A#KXbCfa|&xSDEVmdUXv7$LY!hlo?(96m-hf$@>a3eb&7 z2wiL(yQoNRx-dDS8KC9gOTR%8+GQ$Yw}vFzm##CJ69JHn%6t62ad9SIMl1OF9LFQ{ zcWr{Cd8Qwbmmd!Hc|W2oU#g~WOF!5Cp=uvS&e$mOr7-0tknj(IiNz-}`6_=!dV|vr zS@K`{n#&bzuMyyh!k4U3=A>K_=^?mDesI{00?N=uOW~R4c+rv}>f(c_62iv{&lf3k z&-O|LsGS5HBQ|H6w{QG02obywH#FH5w|$)WVhWdEu!AI6BvxS7Jf$h+1@mrm^Q)LIj0DViP3F%p>O14RBxA2 z*uIQ4sS#kCpQO7`ZNakAZ@qk4z`|JH$4xhh3A79&F)NE^yEL1u%Mn}xL(-9xHZ2-* zJ6izLKrFw+V=sf|rklC3yV@99fUL>W zV`FXmXph&nU5cpPvH8lr4K}BLD3cbMmgAk7rhK_&+o?)dB&ldQ@5O;PMb-P0oHa5wKNCXx`2PUdscj$A1JUXX3ELL)yk>65AN&Be_kHgDWF6KmrPBA2)Af?@iU)9h?+ zU61fdgmemcp-IGfXfCp2dPwKxJraZXjHEhW#I0xpRY^_fLK8B^p| z=e;p%PD>YN!1zc{sCTWnf4r4ozgXfYj-*9H(!&wXYStBh!E&o_OH}Y8AH0!bo0XKF zn*D+Q0QA^nS$Sec8N>3tNz+Sch*3PWfu37HF~?u97QHR@lFp-C21f3HlW0T}_F|)w z#B^BdHus}1reyXnuT5dxYfF`_OwBN-PN@SR+pHKVDv6>dN{X65zGj?WLR}xpPNLYJ z(4;4^3zSTMLoVphs*E@cIy^-p{$8z9+^fwaCl8X&)nXzn`5gZMx2>T}Y@Zy?Qf5ep za$F_hB<@(~OLbMl&BisQOVWUwJ{Js7dU775i!e_Zi*wRw-E%@NE-o$@Wt3`h{=*5+ zNUDp^+zuBJdsjlko-%@O(kiChvw}P~c%pR4X@OvWnc3FfDy50#-QE@Lk_h~uT6CoJ znpoaV%sY(gPfyI1&b?`H{G)@(owrbGyKbWu3_j3Yw-}sdpvspWh{~*}RT&}y4_1F^ zc%x1c$Xi~M7qwnR2i(w$p$$_(x2;Yu6bh)rRr*a}$tuJnqc)qg2C*Z)4u?*zsDQIO^l~QO?Td!o#?c>6##bmD`fEe)vvh*_#_^a^$N6bS>P7nw|B;g~~eEeU#4~i0SE#^Le3L zZ?ypa<5fuTzWy$lL&(B1rK#r_<%SVcmiPFgDQrbfw!B%vmtro}Wb;U2EKO1B8_Q6= zq=-d2*JJq6x2Q5?h5E5U`%#N3X!SfEUrD)X_=zSZhc_+Zx-vRc?&8a`R-In7#l$av ziWY55s|19NX~&M`Ar$^7S*vJGH&_QG>|5`O+z1ULo07j4zm!w6th+I`(Z8F7AbQ;&W?%2An?pp4tnC@A{4lPC z@ec8CE>-rUnrNQlKN3Wy$}r=fwHMfhS82GYFs%|{*BlWM6)U}%9B;9k(Z#h{%BI-1 zgsre9@yt8J&e2Hpcyanzu5kG-+1sS-A4a{F%WPJh$+kbHxGBAM-Xi#8lgZ|Kod|UJ z{7+2}dQnzKa^vCEFbHBk(&qtx$M<6MDa{p$z?zvSx>UNkM?zS{rfBS=lRLBhTi!2b z6yp`DN{$~Xb0&yR;Twd#(7m((%vj}FX{v0y=sZ^1Vlfr=IWaVoxJ+hV0tVSKWx~7c ztTnPM4qgUFEVP^(J5zG^M|O>pbjDZ6b@*S6Sn@2i-I&X`Vr~`0C3k;+I7QkHhoY!S zjRYj-Y$o@DFm|xWLVAxV&;Yaz+Y5hi)iyhtY#RX-=24l{x=04;Zaf*E3W;#r;z+%; z5qd-vnajO-7K80TZJ>IGdC(?pO9XX`iO^Iv_SLo;^47J{^(9d-0bLSqr@xPb9?f(D+!TwY5nQ=E! z-QJ9UNQN5Q6%o8Wiy4)eW#K_K<_RtzdMd}FrVd)wjtVwh9ZFv@{{W%%g?dfO#sZ!S zq^q1|CQfm*o=KK*<^KRB{5I*-r>FdH@f$}@b4yNPS^7OxrDo)R<|Nz_;s-@W)%)Yf z=Hq3?-Dk7oa&hHbDDSfNP!S%ngxjQThO@%x$_R+@dtUflu;d!eOS;=_sE-)+7rGph zY{v>oH!g!D+P&=iFwiou)QXsx?2|;jA?}2z3anwtrF4kMt>OL9`i_XOdV5Paq#|5h zQAM)omy?$hm9aX1qNA`3Jk^uxxT2M`MHEcB;BS5(;fZ0WCBtdSVh-|ve{$G5emtcT~xb;M_(>c^bR)<2MuY7WB&j%=3kKf#(rj7@vd5D=CP0RJp5SK$REajFFt&}m3oF71=#rE_DZQfLp4*F>MpB& z$sai9{!=c0cIfdw-S|)8%wLaQrRp~86GbB|+9>gnaF4SbOtQzNRoSk#^XQXE^ogYs z?m|wyKdPJzhweW((ZlDq4-+rszXitTVy<8H{{VT!yHMD^Ec9u_g)LK+bXgKz)8WL^ zspg9w-|>H$T))(OoAY188Tf7EW&ZNvvHt){37j*3JkP8q-5iGdqtFJ}m2+GmTZs+p4JcfVaUQRaY>OH;> z<4hTUdYIfkf_{s0evEaGubik$)+*DCCvI#B9F``_J{0doOQTN@@vX*Mv5WG5oBdjy zOF4(7E0Z#{3Mm-QH<6JrDkLk;)yw(DnrQRWHZ5~Fp_=sjH0(t#4b40@h#q9ys=fZ@ zftaurQpbg`-XOtiF)Gx&ywxmC37|6LYcD8&HD9nU#NBGWPHfqkpOb!E$A@{ly5GJC z0PVKjHcV;~bB?yvyWQ2?4v+MRTz4Yb$8U`YY+P{A^h`4sdZl0EZ zHGa@NHn3jJ53*J35(IMMqCKA{U3GKrjSI(T@iV_85|nILwd#)CnHh{TMV+S8g{7J) z+Kz3Lt)`1(X|78cvsWL;ZW}*}{{X2WJ+$N5?GkEHsi$6kU4|wAT5WiNde_{aIL7?V z_?sgESjHP+nw>h(wB*xd%+pmSfGiS!-SFm0tE^^pxIT`GJ|fx!y{e$xq{QglLsXN= z#4YM~f?Aya0DM|P$B0{EpNKKnYzMNQn3b2P8cwi6CS@$}^J_mf@`#|UY^LHZtIJfX zjsTRqZPf@x(kXk1htucDRw=7KB^2tmJUNQ2)3rXtEeR}~(?gIRea7=vvO6T)mb`#Zh?JC~C9a`{8l zLlv3KWjS63>=~({@N=|&Q0PEm$!cB=#5L+CZEc>) zQcj*oGo>!u4FI{|BvVx^me?rn6TJp6dT4Bdm4@agDcBx^R-dN3x*0itsmEWLnN>|5 zKon72z0p+InapcReYay>qtCclES6nNGjO&5=>v0;BieY0Yh zq-Sf?`C5Ge%%-Lv?HkF$mtPV#ndPpU<&-ZcGZrr(8CdAt2e&Cbo8UTL9%9*nk&|vo zMCHxb7zVTi1YH4ogiF(hiR8ZM2)c z2P9tfyO*;W?fXLMf~WP3jY8!O-=WSv6QtU zr7#lW(6>*Q>}?xt?_0=I`^4tjs*h4bb2gaLiLt&N+gy!V=N>lNZKldyqjquSb=DPp zDhbxw`S(C~>NK{0EgJ%ItjzS7xFsdI9r;=^=0Z!z6aGc}qL=K(aWhmfs50cMx6MnD zwYv_^3=62zXL>1eaXZt#3g1!?wd!cjOyw&tCRaOn*ed9PxVf1kb8S}LZAhh-fZ>Vy z9X?~W39EbSh=`1tM;y!My69f)4 z!`+T$>6w34!3%^W)Y4o_!il2)0530;VB~SaR${(1x20d)-8H&p(ICHeZVG~Csh@sd zy*=@aX5fh0F3itp(i_)X-PGjTyd5D6UB8ejr|_!$mJ1uvx{Fz-QOx^L!Q)V>%u{9? zX_H}F%Pf%zlI8DIikBiHlDKYuZx6F8At58$fyr4g zoS5hw%F|R_v{m`6TDg9b+o*p%QIn`*S$Ro+!Q6zbHeJUNl7i064u!3Y+pL%7ZUhs<$-gg{~WHFwqk3hfMTc z7f6%bfaQBk-AH8VMcXEQbF&jdCQY=6gqfo0n&tP!D+L(KJ{!c6J1L{HGSLL{&B-8t znHG0mOY{E#T4tQ$0L~h7ZBv-yamPYb+@2YR#6-m{1VkVCeo>kD{bAaJ3`Ml5*QeQ+ zs0&w+Ib%!^2-=I^mn6@+C!(|DE4b2&O{vw5>Bj}uS-G>9A#&j~O%y%Z!;xrFKSbKB z)fS~R#Du=>nRnd=_eE(Est+$TNIh(SuhZ|y*!wcua@fM9RGFu_yl1ksFPBZGtJC>HeJ|xPmKRJ%O)+a$wPs{jd?T0r zBa+B$NwCwg?wR#OR*&sv5~Hp}*Gi{LpOtk)=B3*`?S+5OwXZte#L_Xvuyxd zQfadn_eM8yF*Kee&%k}~Zzm%x?7GwH1~OvfBIuRZ?mRhvc+&g)k_N)riRSB5GCQ&d zfSzy!gbc15d4aFLyC^hvKxK)4DIKS@+_0qcd$-8C2g^Nk_oDYkrR6LWb|qfMa$_^O zClj?~sk$z#)Ba@civ3Cl*b{ixSmmz^i-8P$L_wZceaoW6_0*OaRxxy?N?1`x#dot} z!rwN^S0*sNBubEOU9?PoB)Gol-G*+ZRm@_{JuQf-QxbDB^79hYLZWSdIyVcc88hKo z7_59L`cgduXB@?~>b4)Fo08Pm+!=Ze zJ7(mx!H)fd%t%yJxRc3vqo+?-5P10%@;D)2J{Ub8DLuvW1s9E*zQK)T`NJ1@slKyGPlfEx-4TOu$SD7F|#5G_pl02oQ zi%i#5$_iCro3IHoZko$#Lqw#@30!Ri!n)~xS;m}sgP|+FW_p5uGjjYwB)mi7`=R!z zoQR;X-Z<>_F3RI%D{aHrCT+LA+GlrAnbV^%6|~079ASZP3q>_i~JB$!u*_COnUi$+}IV^*;rA zRY*$CQEC!&+VsmhWZp5j$EAQp=lMrdf&IFfvi(oD`*3Z#m!j6oYN+Gy z8AvxSM6M|gfGNr{{z@AkFz0iW(v7om=^B-EVD3oC$=k0~5lUrxPSzUWMEp>Gjz5u` zBHe(MhF;}?W*Ulho)y|Nn0~D%KPxnCyDI2^x&$S~ns>$s%E-q{kCumYW4)qC`eS-6 zVf=e5G)&`dn9VlOz8MRo!CRVXu33LboQzEwS1D@jne4qPckiO!H+vLpZw%EXZ<>~= zNh77}IIqhkRq|f=%ggCw&~j5=M%7A_g&;s}n`9&6@h3iUnWtuYF$;*RjLc3lt&6UI zDGCd|&o1SFNwAEFc`0)etciuC3||I zC+x>RmmeT*(AH6|2*!-#>=Q$+Zw#HQNujzWm7?%)82t0rGvr^9spoBFC7Yhy@Fz#Z z(+ggqWaSa#XNX7#@qf}AF;e5$w&8kzhURdj$9h)^I@R1GlJ)%ZSlf-Zrj++5+jv{` zoHmwF#hnL1tlaZR1-~uJE@mWF-KTs;_fc-A6Z&O(KjMA0Vjj;#k#w-FJfAsGqs!j@CDjCNclgoH-85YgIAOu+Z zX_C$~0DF~n zSksG#PNwn=*I_oFg(Yy79-+&BN?PGdil+A4N!8CICGS|@#LfKm67WEuo278Ki8p@TQYx=_CbW##>_Y--iR29KcqThN(XJN)B6+|;gRK|q5jS6Dk zD`75631(t5%xt&!95V3aIe56CRU}qNjm&PtPM99U8xZNai-IDsmgyT#KdwmyGANCj zc~kL4?gkAF<<288vNEB6HQn(;B6bTd$k~amJff@$sLFC;*8c#*1TB<_oOny^MX?B4 zllOm$0YOyR4Dg9vZxztau$$O!K%{xa+NC#8xpw+RVUgH8EwR&MZ!TWUAk-a}`;d5t z!wrt*L}+%ycXJaF(5{MAA1Ptma-w`i?)YJZjBYJh5#lt`qoEWpIAcQ|Aznxls%E#(x$b*~Vt z&t1>>Vxl%KwH&uKy>DKqI&2wYDyaM|;LNN-%uZTyk%z~BU_GPa;*9g$(`5uxoOwd# zG*!N$cbsuVNi%iY`%o;}8fMKhw@3(^5KivhP+_Fjgt*G{L4!#O!Z&0gov_=23SfMo zl&I`3U9rnG;erdecKBmETxCKFpA1z1Vf{FFjt)^4G*c>*QamT{#*~0{1gyDS8Tet* zC65t;SaLvr)a*Kym%CWKF!B2=qk1e=b}T09)w{kCsUVqx^6G_5R9kzP8LM=KD+!*H zB>md=L}d$GJO+!T6-DV_aNk~6L6fjx8{5htvjpBtg$hD0K#n>w>#)S+U(qqNLbRB@O&B=RDhlUCmp)X6V8+A^~7MpLwD|#AqN}n+qc0iz?v(zLinu7ZI=oOdX#%Y9UiP1Vz00!PKy1*zsmC zFB>XjX1w&E*->qCWpfmud8S?xM?{?I(IZa^dbSO0aMfGUZfcu2@FT+J?}j}ZwM6>r zpe4N+=H5JtU<7{56J-PSJ0xj-iL(_s_Sgx3qjBen000|XiB>MaKPO&HbPmq`ggvKY zC#6mLe7uaDz@hes<^V_&>}aa6R$&=)=dVe>sQ&=`omzCgHeLFLHk>D%A6nGBxDBiB z&hOs>veIY%d#_ec`hVtZY(M`1$?MrHi!$!dVk}WS;bcyUI1P?{d7SbIQj{;GPFa?|?uKhI zEWQ2Fy*PLAU?MWXtMsoyRmoB-MrndmY%=Aalm>1dTzzG=qs>SuX{vml z9w`8$$YpJ}iz#+rIKeK%adI`%szVKb9z<6(la&&?{n`vw%8acBMpPRyP;3U$LLM7p zSX*UiBua7QF;Wrzm=L2o2YPMCgW(>Zh6?1nND>#EQBy0v)JqEN8HQfGY`Ws%8)8^1 zofEKMN4!5r25MNcRBBVt3KLQX`lbiE{{S`jMbkzsuZ!e&b_1Qk5;#_p#vEsV^zjfG zrk#;(wj0aX(idYz)>ow4<+JwXaKyb;k*7Q_4-ktXaQ^5ga$$y%jHxp?8nVV>Jv+38 zt5`0=<|!;qHbV5P4+(osrwmKziR@7}nS?_M)5fJF&hl7F*@lOyq+b$8ONq~E@-(C@ znp%4k)oFOD#&@#qks^cPnF%6)$4T=msN9M5I+thjKkZ9rvZnomS&L~rao}1c%U2G;;%jgQ7ZQ?fSSfXjcOYWFf7!~ix2QS|ab=Q%`+QWHR-gsB&#n}FTAdue4;7}nE zxQDbj;E1QYI9+ULR$r+pOY{%e{T5_VFg-L#OWs=j>lT?A1iCEzf9h-uJFq+{MUyFN zT^*?wTpMj@RAWSPvi*X@s-6X37kWFng=FeZ&?Vh^TAJ##mm1bI_-XsUR+dxl54` z6IR+nzRY>d$jDkVLJ|#$kP2IILTnbc5ynlk9K?irvx0{S0ZuBJ@QMAadY~8~P05>v z+7}Aj;x3edpu(rOjfPP%<_4UcbYz~YNw%)A8?+}UJJddK%voK38m5%Pwdy(0eH+=W zgDXp*%gM|*3Qn<66%uKFNgWBsvbIqzg=6FXrQ-p5CE@uxP?c_u)b!_4h%#agL19c^ z@l5`L{IpKDvb~v6RE7FI22}30*@6bhi9N)y$$U^Z^w=lk1L2d|l@v00yd^3P_)c+v zyZ6GUBFI0c!S#WEo7-U7JqY+`utOp}Klpw}-WA%!$iy`F(2qI^f+>q>Q&IhBh6uq!xnKnkFJxAJftewb5K0`3r zihEfvcg`K?toa(#7GH_d`v>&Otx#}Gy-yH5FSKo%PD~{>eb+?_p-6DK3!&bbc%eD) z=|w(&9b{7SFV$1zqZCnoKN z-i6cb!LDXAz7;|oX)=txy-8VdcJ85VE`hU@&94zfGjAcc;;a+tOIn6TR|wy*5m&GQ7yEt>4{;G=Ir!A z^w_IADiT?oMyJExc@r}-q7s!C289%Zb=V(0f z%gMImd@4B*m$MmobNtgaO;hRZPYU%v4c7wFm~u&2nj!J1E?=J%ceyD>9+A>=yyVvb zXY9ZTpK*#$DIR@M2?{H>dG!}i!ov!G-0~P{;)VeXPwe(!{=1j3fQs~hpK|9R*Z3rC zpx&ddpN2SGlVREPS*YCpNID$Y9N#B0JNP?l3fdky!Fy%(t!-1CxV+hVry zMFh>EjY3K1SuNXcyh18m0;Onw`%$ZoCT68~w3YtS_@k#Dz}N$X4*@9+zZ51|C1DM; zPBWdlLR@u7O0&PE7o1^b8!@sa;U_u4r~_%Xt!F-%AgV^WD35_E3^qHJKEP$jrJfXu zElsP^oe;wUlz`n2QS3mSbrIt@(qd=!Z~q9X^kF(1N=*Wt{=q-Fc}eB zX!Uzhmn<98DQu$7gN)`DmAOWCQ+6z*>vJ2!6)SFq%7o5IOV0~xS@5JeOSt2<0T}UJ z?8OOo6Flk6GC&sR6wED#l(8vuxXX?tb}ITMTxf_0mxRm37E>_{xu?5?#n zUdncnisV9}B{@%w;7ri)PIWibgni{SCrP^0(6^7i;v>5>Z060gi z=9q;HJ87!ttDPglQ9o4W`d?Agn{25)En|he0_up|hb}Rcij_luwoJHcJVZ+id9X?B z3j6uOLSS3*C4-6BAf9=lY>c```NXw>`JCtJX|yDq2554`MKd^h+vV)9&Nc9G-K-;- zl8aK)=+Ch^d1GrAF{LYKvy_Hxmu?@bMILm-d>gwlsgG zb}#{-WkpA;gwrwZOE)?kYkHgNOOb0XhRmk?SosbfrH zc`w+10@N$Q+s-$+5#cLF>yzaPXVKN;{yTm~$-)~QlNPgazqNE`nPaPi0;j~W(sp)~ zsh?KJ3uO}Kx)QRoX-%0cgm+?58H2}+klnIDaE`DN!#yE?l;pkKx&5w~3ywt5=ISF& z@m<5zQ7TA54pf1|5?}r(4#2WBkJ0SMJD~SqC^K;g=Qyn(fM&6y3W%Lnht;PQ(7RM% zDVBH4Tl&5jC2>vELgKs^D~Ty3D?ZrGOAz!QNUyYzyRy{{U0X{!8#H<%9hTpQF#x6^yn6SZ+>r$Mx~x&>B<=B=YE2i&AD;(HDe8Ft0F~oq@nR*mMe9);`%#lk9VbyTuf!EV zdpw<}Hz@wpHYe;dG7%GmE`eMzaUn~9>@2v}O6AMp@k1C=W@`pz7DU8=piPZC@*cNP zHtkz~cw*&2p5&yg;JnS|L3OY)n<#iy0CHO^dWZI4C@_m|X_ok%d8Wedq9hn4z|vDn zr;%mD_*LvtKPM{KyCPGLHUjL_yN!!TYAJrGnW9~uk<;LQ6F(y=s;~N-j&JcF8;-WM zzrqgQ*>BNvf_5*4DpG*V841{T%3JhxT0ptkE>a2yKT3MIh~HjlNLHdBTxc2EF#w53@0z{EV1Ew8p(i~mC8jKU)a{2%SCr* zG##N~xr%NerOMPKW`@!SM~Hr2__WA>(beJ(WL%3@IJeSU=#QP3dSa`yd5+k~=IPVl zaRtFofqVY=?|wJ?az?8sIZv2p`={Gh z{{Zjvk9WcRzAWp~e82J^gv-Wn=CA&S%EA7T4!+A`thofJ{z;QIOwKrn0*}^X9W6VRHeQc@y=Gxic z(n*T8ds1e0Qmiu$wl>=6+$5gL`NAK8f_#k_xtU|c+jw{BoT$}}vaVse9QN;|x5^|x z$}zX%m*T^}Uf)vRPx@+KX4PhYhcMeM<-julWBcPg{wco0G4uE2q~y2Lu|yeX8mr*> ziG(ce+&Ba|{p%C`mA{d1&shHQxiR$VQRsW-r7(-Tr!x@)BrO-uSdsX@+z0zZekyA1 zrhPHTiYLkXMirB!yJ}iw^~6i(qzZl$_Zge=*?u_64mA2xHjj!aqTknlQnGTs$hUC{ zeAXZS&BY!;w=+DE7$%>!?@A%wNmZ*X(F$Z*c4}$o8kQ9`{_GJF{{XrZpP9zvhL|~A zk0$9lJ7oTgxI1BN%g@#u3_)COe5Bu>d6eD><-mvvtCnl$9%GyNR?6KyCtHd5+!z<_ z{v(`KwrR?7$+x^IHl=re)S0i+04}*pEYq4N$~kfF$4!0I)s=0g%(iW`?6|z`G!~sY zJL7PjY`Pv!S|!lWNdr|XjN@sXIZ|w=Mb_u3WK9)>gfr5mcM|8%NE<472I*nFe`_C5 z$;h!NRTjXEouvBU<=zXIf4VOJ08=8ZN){~Ixw7$gx`k3sl;;_LwQ}7pHQmoe&hNSc zVT(lin(GsKMl#vVIVaj2esdw3;Z4!o$x&B5SG{7=7>w&4My$k}UY*5LmNPKx&WlTU zFwYQ%d0X#Kd_Nx>>?bC6l1l^n3hhU0hrXmxOr@RybPS7hB8BFqk|bz|(TlmZFn7^+ zA%@&>F^)9D@-+s3Nei(Hi#Y2+mmYR$qze?wqT#arjO-qtQgV$YP{p{ixlMInA(E9w zrC$VHKtWSA&)lmF^;K5PMMj?!Y;3m!)p10e#WxA;MTr>(-Ev||aP5r$F1SbN+}H*OwQ1Wm z!_FI%L@Z2yP71z6ji;@3Z_m|@x_PWqa@wJ`9A8n6)~I+h0WizBe+tm(@~yB_01~oo z3xt{E1EOf8qsz)P;mFhCW6Fk@sZrP<9 zJ6=408gk_^Dq?Mt2{`Pljgtq?)l|zR_bfLTC!|u|7N+d2dQ5#~vlpF~ng+qTi$}sr zH!Xw-lJi93f{sbCQtM!wGustw7Hy{j%QHPcF*Eg(MCnXT1EXRlW+)1o@*MI=>Txq! zjQH$zUe+<+)>0KpoQ(1M!@wR)la4!-|cTZX*f}Z~X z`}2w;DI`+qQkLwq1a21vmo@|>bXx|=fdZcEATN3}R&H-5ES=bC1^eB+^yGS7(1tdMh~+SGHjH@`>uq?Qn_Oo>bot>F^niGtFGax;RUD34*etge7v(0#XppibrW?1qFw9uu|C28%v z?7P)LoHOA;eKzw&}w36*B}k~Fy)mS^SUWvSyLEzCC2KK#Y)aDiXV1%G@l z%12H0Fq~10im8Sv^m$njv6^ePkhf_Ho4Kx8^3Lw%i7=r;taTIeJDfLvI8^xon0ln- z?7iUl+$>t(PHV3|~n=+36|j2-dQ3dXK!|i0IVCo zM@aFxrexx=mBqB&n<(Q(5 zS!?s>0b$FVLz>TMrq8_0l(`GUYE6~NZb+S6zkD})@d2wOZUHBm5vf$YOwly`pid>F zz1`Yp`79dB{6J*ai7`yA*Gr1*Oa@h3Q4$lWBfc&b(DW?i>`ce@)vM0lD5=82zrAAB|Y z6c$#d9<2qJT$?9U#K(w7EgV4e%{5hpUqfg{&Ny`Xl*5xeD{kOlBD&?zHq;mQD#VG< zq5D;DtK^Do+ueaSPJ>05+P~g6?nUPgMd=&duFY4TnsAL=bY+{gnjsz+Wy_%d01D`- z4@l`YCKZ8ywI>lg^vOLXaJ?>lC7X61c=<+*Z=*zG*|?}XKxYtPz2XQJNw6d~F8=W# z`7X6EWJ;WiwhffkG5#Q~PE@5|mF-8w(@pTYi*93|But~7ha#FdnN~cHo8L$`(K`Mr zrqnTB45yro1us?dE%ETS9={sNF2CHJDstAq+;aZ^tugL)7A1m zu59OOR2{rfWacPUiK^t((Om48YDNXDwbc_`qnne6(c)yvnkBxfIv;6UaOE8OM97u- zkxw*#_oQc3*ebgHqwt^MyY=&pD8Eu~_L5jf(i(+UoDzQ(OVp+w6cU$WR@Q}AyExO0TfjT- z23#KKc>H^q*S#M8@GuUw36ye$?(P>{+*Cb`8l<~jm8pZwNv9K$d(l=maQXUcDKdIl{{WShYHLlZY-Xdg3d@Z=%dSd) zbpzwXIWN9F#{=??r)ro1((xm1joZ^<48y^wk9KX&Lc194(V?u2U3KkEm6H9SRxK~S;h?CzQv+=y| zRedHCO&W)9)tBjV)Y+zr<7RPxqvGNm(R;o5KyzWeRjF7}VXSQ(tWes<+cd)aQ^Rvj z4k5y`4aiP3BX4Y$bw@1y>CP}J<;BTXvAZjLi)W=4&dN$>4dO);BgE1OQ&$bPzCHf@ z(mSuwm!#!o_BglGLkkb~8lXYKR-q5ez7d)_x&Sz$n)dDI5q+rGp6WV(#;V)gi49%1 zy{zH*eS|TrCIoNFA5aGExJ;gD6foHs4{6gA5@RTH1-x&j?-E9QxNp=u4OLF*`Xq7u zjwaeIV+B@R9o{09P+&~0Y>y%fesJ{TwoFEcw696ue@9N!s-BFyRIhBG5fsDBQ|7m_ z4n9a*`9oZjCFi2(v5LNbrYg6Ro$elgUk6u`ov&EMGSY5MN(mM6rE6DolYi%tM;29M zpebe`#-ZA`Zj)_pUg&gl>iIz`=#|=%6#C=sfj2NmF~w+)No5-sCeCZeP#jN9YI2V9 zi*C4NDm$SYxH8xBjG5P@TTYHb^cmX%;#@Vs8n-&(QL$Ws*s)&#gcuV}ElF%%dk(e2C7SjI!vO>Ht_7x6VezbLIGH-T(-N>I8 z3W;vbaz1~aQ0y(#GBW*3Je#dOIgu63Tf$z{YO2`Ry{}01;g)h(QO5U)ox}t@B$0Jl zBO15U3mwSp-(jN-*5s&C*yM#Lv!7IO9xzklmi}fNU=hd21a109B+} znOB-@#n(%GL+qCpzG|^)BIrSFqHHTzVpYbQ8F7~`u8|~Odapf3CmsI)o~&CY&0^!6 z8OP9}t<-UHjAkYjPBbE!ro&ezJjxs-Q!^bf!Vjh9BB~Gd5>n53!d*fiO3YiNN)Uf9 zF#{?{ar~oyy~`5S4itLHMKDv`blGGB0$fS+8OCn?nE4N=DxxLX7Y{*(!-{d>)9q+& z7|@TQH55|0a)-u3Fv5CW5HemEIh}$=xg%|>8E!0jCfbNvqZKb=2W>^WQV4*abAu&| zZ*LJ!MZDnInnp>ID%b;WTh0{yi9qelThrl;%*DiiFzQxuTrow2JC3mVx1uD0J%Kvr zZgFueTdQ@eB<1AsqC&-;%*B=v#Y;8B!}?tzs{+k!I7%DV!*Q)U!7aObF#s z=t`h}sO7>?#BGg59Wu`Ca`8iy)e1S?sPL~bi&hw_U_`Rl_Mxd!(;5UoM5M79N*g#f z=%Rg51+slaxh`1_d{GsJjb4}F(j^2+k#iFFbB8OLT?P}9{22)GK-@#281pusf5YK` zu>li#y^?hQ09a)A3Zv>skRHCwXO$H=OGOobgAmNTQI9CO#iGL}n1K9Ijo{OT3k?_E zQH|i)5LjvDal(fxkC+Ut&%+BGq^&^$()H)>f$OBoq#GO14?c%d(U zAhM8?6Wn|}<1@)t5!;BIjO?Kc7qU!}1%@l=_VZ4gVpL+NR?xZh`!Fn_L=GF{`@d!a zR25NV-f8C&!R`$1ay*g@H9RuvT*s;;IM_f%C`5U~cOsocc3s|`UMRSdl=3a1oCk5@ znc~;O77H%PT3bqPW}ehjfibdfTu4lRQ6o+j35i)YZnrUwDhejY?8}b|L(L<}c5SsH z5OyQq7pX2-$c?(fsGJHuE zf98;jgsOOlxnaPv3EbTXd|L=hjM`l=rp9QAQLByGe)v|PksBL0JI|RyJ}x^-2V9OVf;7Qj9={>e`51Y^Z=z*5KwVNOV)H;$_?53`=eL;9~2Bu zTDc)J^mgIdA6GM8tnWvtbp6VZAMCUq`IbIL6VMwB%FAZON@`%oq-H!(r9T~te;Cc` zY=)_NI^oPSzL%TB^1IO$5q{;kzbzxTgYog>x5p>MkleGE3}m=*8duUF zS(@G%4cA-409fiqXvp-uVyql?Be9klELBRFR1j`tK}ZhXEYp-f7G}rdHCW~CqF`KI zj!nvB)QqoUaEYmg9WU1Re_WbJZaFu^KMOa@#O1BL`V`c#79?z`(sNT1lYwx9O{o)0 z?CBhiMjZ>};-^xS**`?=PdTViYVL9(xs?(8)-sqQE?+vyEhXqvj2tm>aO~irbdib= z^@6B(fBw{yl0JcW%5d#kT)Qc4+;0HH^w?%J&i?@L$Ajo4U1U9Tf0Wi%?jh}fP)o;e zsiV=)UlL;v{T{W-(b*zY*{6M$p574tP)~}UMsG`#KN8JL`Z#eu7o273%6Z-;b6^dr zT(L2#lhfsWOMV^nPp>yq-%rd;GT{}(0?ni!IGl`|F~1Ki`5UKZ8xfqO8+Vk^Cso$0 zB(z3TqVR1#LQfAAf1}OMkrr2ayG9D8>}#MD%n2=Y9X2wgvv^ij zxngvx5*ZIyu1iKIq)@`4IfH?hrDyR*9A|hcCIvX%*?g5^>8}ykvF(iyj4>7s!1$b` z2EBzURL2EKU;4?Q+-uUPtNf#O4D0eVzD)(gK9UQybO%c2MMfIyM!BKk}7I+{L^?K~He5PrVWsWs@iA^h?YA zE}J*QvfHXze_}E+%t%gES`z+IX*i?gGBC6aY9CC74^4lw^+ylHMB98{n!?^x(1^bw zfqgg96tlLUg6H07bK-P>e{w|i6-D}M@fcOUo$Ny}&5{^BiE_Kd;#^-n77z6pzM*R` z$3;F*>4n6!n!}X}eg~sT)LtdNXr`2lUlo?+oL}oTf8wK~D?lob((SZruoKPU{52|X zbviCML|>9XzHOGzva?kBRW_{6p(<21{jXNxYx%Kg{eLv z{MG_;l(&I{zMflWnKCc!(Kb$d5^WBi^~rnSXD0q5Rfr0orZ&}HJ>g^O$h=%cO$$wt zeANe(e_TF_na~Okr8d*Xu+0Ae_VW;t$-0h>?;(zP=g_H=^zvA^$m1ElqH5lk7^@#+ z2OCv%j2#t@aSmu98k4G7wM}479P$$G30W0L@EkYTVSCOanNK&&8%*y>rYnQ2Q z7x#B^@b|+kWw*#fbnq7oCc+XyMI*U;QSuw1ggjE<-%nTY#3@ulwpaeItM;H&u}#X7 zf2GPJ{vsHR7eH35?&^SYmNp^L7R;^U5#hQCB$A>9z318g02COgnuc#dBrEP1g)>+| z3QRf2&-kJT*mEc#?+OnU-Cihw;%gpyO+?8@?D0UCV5|$nJ#(hmpG`%`8tqf zVf4*G={te&-pF?Sh4D2WURINawwUVCu`M->`!iEyo~~iEOw%P@a_(3r&C^gae`DJh zPe9yQIGS4-Y>KGn@NBTacE#+m6(?X+&Ousf7@_)3D!-!liEztBArLFj#6N{Z3|1o2@!MKo3&0~ z6hWxlJ0Ixp+Ozd-msT*G(HS{~%_r%kU+y?>X+7Og>@j&DX%69DdiDA9y^xB1Qv)>&29@zj#*{1z1D<%Daq^jAv6PN<|a4-B( z=s7LaZTK96R>o42rc1v5P>69e#$qMReIfuw+Bk>937Oapfrel05>T~Q5AeZysxC0J z{6Ut~rR6lZi-rlaXw&*te<{SN>fMlfXkjX*6({PI zX;~SIP)UQ~E4|6(7+%s3>9jekdUoIm7l|95m~!6`vZZ(Og!*n5=-PZuJ?UNP$wsMA zX>DQI?cBjSPCs`Ip~*D!&I=YMlTIERN0HAJ4@_!`U7btC;VvlKf9ZWqK7$pC$EAf% zVe+=5Zgcob8maavQ+`EmYid*Yx_^B)Fzbo&6a@=)6HjGv79pj}OIS`gc5xC2lINc_ zjcBDrZ0QUav6{vUqB&5WZGn@R$QmZ50PZx_-95zs%b*{*C%JHaSn$F`s|;qO)yt81 zP=AUldki3P6q)vCe=;2{_M%*fYkJhVjLBv<3{sl6P!c^_@Q;V*1W1wO^LH{ub3Mga`k^ExNXzv`@z}N}o>xa-@wHzgE=+gvIr+y1@j>B1C}YUg zO#+&GFeJOhY$5v=k)iE8P^E2O6lBei(pK>pqcg5s%ce6#e~^lUmI9_{o4QK>01i-L zkrJ*5p1m+?EX3rn9}+>-Duu%}gQC@0bqi4|OXU?k!%5Q{`vBvOTLZSj7|WGT=NplA zHeMa81GfnAc&&_&>~=Wsvg1Lk9IH+umAtKtjBZ9PSZb`bAS{cH1(aopWvg6vA6HCS zV&&a7K#@2|e|?`Qgbgc+kY%k=4ehqYZUJGL)ShEoxP~U+PV`8QBszqhrNb5zITui# z`le0C%&h{v|MVCwQM7+#hbe^(02%7<8o8F-0(s8cqI$B#su zty0-V=dHeO{f=sd4qFg0ZtCPYfD~p5#_2 zapRD1C7E^Ox1<|VD7lLR40Be_c5hO~n}3{}u1mhyQ%>#Eqi@UiM_Mu}VCnKOs`{Dw zlR-+)L_o75*E3wEplNEn$@%xi+4Zz{s z_eXaMQxVL~lhk6tMcJv=Sq3OR_+ZfBJzZ@olHD($d6ncE!)MyeJh?ig|qkdYqH z8_qxa!dZZJ&}STUv|dFE40S3!B)^ED+oY2<6}v|@x(3z(+-o;DPEI!13HN6dNLC-Y ze@!`wY}Bp0!X*{B7OD#CA;v zf5!yVze;kEA zky2!{;%adOx%p(V%zxqR@O5#26yUG(RHnP=fwbv6uEKLy4^56C3H|Yz{{Riij{{nN z6L8=Rr=rHu_tu^hsQ&I0{NgX+`7`kyAH>`_`4hkWDtP{BixUjZ2~2i_ zL2u%I86_j=Uyj~oz2uy%n0ScZf8rmyG-3W9&i+DQ__vK7Ds)$)-_Yqz+Xe@hu2pu4 zH!kO_3ArYl2Ah_6c?IjLqNcgWXa4}(m@!JmUAO#?JIwwu=45Z=X;imRb|2{d=(9gf zs@BGX`U$yCuS(7rY;;^pvU$Aq$$93SmOHq=6UN1AEPsa2Gs*n-Ge7pZe?RJJZ%nVH zb7l=uUZG+7OH~Hxb4pZ-xnw5Pzk4K$s-}FEb6!yAb;h^1SN#rtEV*(wO7xsFwXaWI zsQOMxGli*jYV|^Z*9#J4N;1qQW2S4}?v7_O^Gcum_c}ap!Dr^TO3(g6HdpAQ+UpF| zF_g#F2052w57i3$``7M|ey6cA664HbS0Az47XQ6Zw8j z^tVyuzc2hdf)>ivfByin&ihRCdD@P`=Pa+`dbGP^ozAq$y4d~Wo>A&>^LP7C0m$LD zKO&(ow28S>&0H}d>J{N3jfu@@`3U_vH%;4g%%+QZelGZW^L7hkVrE}&MnRI5ygnFW za>XtdpNMw-BF3KosnTeXhv${ zoNRnRsm*fNNG-{6Q%mBlq76_RpP8B&aWB+5Q&y0te>)dArcx`ZDYF^eOX&?wo|nM% z{{XClpOdF9FB!Vg?krjB+~Y0~?)_Z*V^l`>Ub1u|I8`o%%trrQ?noP8YoJ=v{Ps&fNbt5s!q z2&SeDzg+JoQ4=IkMAVg0e4%-Hj2v)rTcq5Ze?8eA62P)kjnQc`5^YGDONhx!fjH`y z>t8bDQ0b!dDi9HwMF(TfD8F1g7}@?6ry zf9ZlwGklA`*sG7E&IrZQbI(cQiHe|8-7V0`?(Si;T-`S_;#DMrjyowVLCX4KV4Pis zCz75)Js~1UVkb4bc1~1vvs|;gz6ubYs~pviy)rPyAg)xZ^vZmO=0((Y(|JUi%`|H) z*EB@dI$Ufk9Y$97C~taeQtA>D6$EBVe|TvxuM4HbP8xpvq4V}KENq|jl-jd+*8$*{ zC0^XiI1dBzvhwz}E~coezar-zd-5}|5*ZG2sBIcXUVdN_OdBaOR^}8YoD$3P*FPkV zdM=HM+d2bfKA4rvF-?(@nXeqyAC;F;7Kl`)MZaNHb5%t7X9vBM)RSP>K9s7xe;Prz zDa^FxES(kt=G0uVc@iNey*ld{bz(%T`3kIhTvV}C{EO260NDp9n31br1}UoJ?w(xD z6>?oilxh7wzqq%JsTH(OL<8DQ23Dzs+{4&zvoIIVQp#(yH2R9_VBWbZq*|s}dx>Xg zTDeQu-pcloDd`JGot4A&nzbfHf7F;6_MTvx(Fim(RM$N+Na*9_=u~z(w!GZUK0;zn zpgopmn$nY^Zd@!W9(kei%ZnmN=;7q6`5R(3Y=1}Eq_phU1Dmmx0l0*eoM8=hG|N+M zc`7F)6>e%yUBU$*03_Zz9FYMRJJA!nMM$2TncLj1e_}nPNy$&Q zESsi5!{9lOR^DQo3a9HET+2+?D7#c#bp^t8!@|&P1k(3ZFJ^@}_f02LJC~QW6X3&}JG8JGTcx^rCh~6kfD!RbW9^#>@Giw4G zO^P%?*;@Ae>FHL{CQrt*nXHP$7V@faiK2W z4haa|(iy`)KuOE8vsTzm7N!F9%{EqG)v2e5gl!@-T#&TyhJKlof5e`r)Td8+AL02B zoJJmuZlzcz0Jn|gf2KKcj|#8aj4#YsGiA&5 z2|eiVRiRWS1*&UPN!#3*7Ny)>{ovh*xemG~vROj^091sIK*tR14u22P+naV)X~!Ir zaKuP$5>h^;&3M%=_bfNo9qS~DtZcG+YGgzy=0;A-kX^)Cq7HnMmVRG+59(G2E@dt| zKlE9x)vE(jf2+((%d+$Cn6#vq3T3WIZ!drDfPP+u<;-kKKEddedOV!7!!$!LDxtl( zM&)qxyChHD$}&GQW=G^SkhtT~hEig1zNVOVT$y}8gv7{Q%`?wlUoXlN`ld{!$!`UlIW@2iTk5^Hq8z1;tpf$94#wtX7>+%+`CK|+qw+D*FU-(p=~F`oR_W*QALrH zF$U#GO#cA7bTb_CUvvv73fe3fMao?+nDpCH=Q%SkIJZlNV%A%}K>Ia`|>PEj7uxc5;((rxwK{zpErn{{T4QJFtX(8KvV{ zYJ)qekv7%LtuF|yQuPduPRY4>;ggfqvLLm1hp4)u%^2J0F_Tl8Jiu)d=n;rBs17`n za4__&+al3BCPYq++)lEok&op8mP}lNlZU4Mf2ksA@6seC!6nGUis^S3)vEi^e<-yu zmj;GW$@irP);b}&9xJXewLp_TS*JCL+<+y;qF{{&cf1u5;k2D>`KOWqtXxjWARe2O zWTpYeh^@G7;kd)B6UY@c_rm8N$TfWu&N9_Wk!aotiCKYc0sY;jk!l5(a0=-cUyubO ze`?b;ndy;YK#IwOPS7GqHqA7eroPg0NGQFHTzcG#r{ZU1X+mrX63c;1FvFZe>ji2| zGvyd>)9)t`JZ8p0h`OT4{!D^%UyJ&_{Ye@ra8`xgib8Mw5&-i z@XpJD0S^`AmY1HtwlB82U#6M&LUVDo~Anj&h8?{HDe?2mWaTr6I+M$T6bPyGKa|hv>YnIfPE$gC5;asxT zI(FAukzJSjjjiaL=}9SGtPT^LtsPo(iN+rxwY-wuGgv&heMPdGQPxhB8KrTNQSiq; zU^CY`CNQ-sKXGKC)C$3|%{`cEuxwTKvFQH*MB2oghU+0GR30WKgM@_Bf0k?WjG4Hr zK0ZR7c4@ZcYuHAxSW6xLnMS$ZbDQaP&jACqaxIe;Rck`5Bjp7p;! zdPgtvKaceC`l@gIZ{(@`f2KH-5K_v6S#g3Au#GLbzXo6HNYTT~y0Ov8*W!JG`W$g{ z+g$oL@q~kOfb``iea7ac{4D};w!eBufAbx9m5zRY5_VO+-MR4c_@gEk+MJ?BRg1q2 zJh<*i%813pY-%w1mhQF>S{Y&6Ev9)zs+S()dc_rr?J4O=mc_=Ne+3IjZXIB9VD%QD z%2FbMaMycCEdE-dH46njm?YGYO%W)J1TUQ-GTi()`wul zQl%p@B+wO^kP@bbOXURLhsBX?h~1Btu6Gt2+x80dkipI^KD8k9$qf48Jus+I$oTdkR;%nH~&Tq~%??oLlp0#svZ;rvKjJoH7Jn?dao z6BQX<`EKqex^g8{ z&I=1P8gS-hKB-CV;&U2PaL(15r}l}uIF&A>Vq8K8YHwHEHs=|0LnCz^9tR;)KVM1o zOAS;cHrcUxe>_D`tO-xb%b?B!99JqPT>Q9s#~<=MM#t25Gk-5H?Gv-QkyI(7%g%c7 zD})QAzF%|(D$13WOLKEQqTS1eCO#Wik0^>{%%ggf(=xIINpQG1{-~6+SggM5QyqG$ zeBRoXc4yGH>3738l4xzh+gzrkgwTm}ye2@1erl;1e>fPE8#XVn{27v8()%6W@OBy3 z5jqb-%&sOp7i!mE|vV-{+YM-P6 zk`rEte^{}v6C~5(U0jbin6_e`Yb(O_q)4bLh^wmQ!yVtuMw_SPYv(TV*y#50 z07<>UXBQRiHg1>gmXX3sd`wL{AaNGeB@Mkn3#N2M?>I1PnS6B@IM;Qm2&%we7YGp;P<9u=Sa(=O`9U62s|v%X!3v3)DhJ ze@<%u0JQ?jS8_&O{+%I3%;F;wt!`*&VEODxvxKG-x2v51&H&fl6Y-cSFK?Vp<85zD&k#qVaEIVVb_pv zBMOr4S@ymdsdAWwc=u42c*qm~!>^#IYU*6Jn%!BpN=%%nX!EJk~Tva2ljlNNqwz zEg+SI)QFf)JeQOdsTphqM6AhT`wDc}&Bj&Bm&F1PrVOQcGDw4ru`ah}zx+@(e-0x< z#$|Z&NR}HBq02J+wAcRtiU@W%9ycq&nHQ93G^&ejloK3_&I+SwD1?d#VBNsr`$hYp z6s}i7!UyTVz@Wm1ob0Wx*f98-gB$X{RF}l?8Mn`Res!uY{tj#)OBaL5TYMRLj zilkylF5ezW{urUPBqZD2o=Fu7N^F^vZrfF3L`H?v>NpbJ z{NqwCi(q8utj-*UX<%aJdP6{C1Y8{d0Mf-zU`Ef(mljb>A%Lvax};S1e| zTW^mpFJ>T`j+q|l1_*KucX(olD7M+-%fk@>j>5)ViJbajhJ_|l zyfDHK*=N7ngO0=rxwRnbK|#j3z%qjB4Z+?71B!mcY}0&s+Zdh&!ii{$lE5mL);nly zT{>ZfyXYvn+>k71montV!KYLUfFZZI^!A}jz_BAH;HZ2f1f|4ie{e;?n!%O=Va@_b zxSbbbE_aCj&TzIUZZC*(#zx6l`}AZFxfK60nB{KfW4;>tkmd@hodVsxomn@{Ra{Rs?+G zRwg9MfZhD#S^-lfe`{b)5n2uAMR$@$-Xh$O>{n!Vhck()r7bPJ8F_L}5f!wf-eBS; zEJ}I4Um%VTqTFyXzZPhsbzF@6!I;W;^RUDVOf zZ^*o}L`@?*`E-#v8B?dzulSQx-$raMScU1jzCNNoe=^}Ikc_}uKJ1lXvP(*sUxjwF zF>a;d{hMGnnmN%kG1PEpaX} zjQ7$Vu)0Z-y^N_Bk5M!?2|-XbWnw0K>0a0sEs|8P6xF5u(<)ItGIXg8HT27DCRbLR zt;`>=f7Lb8x)e5r2WekQw#V`0`)-9d8A|T+Hh#^!K|$L5j20L z3un~uAeX|pwxw@JY!<@r-xZsemnV^@3u4Fp<4UK}p+TC}tOr*Sw$UEk+e3HV8Scf; zDPX2nK9cDne=(iC!kX&fI(_92iwr8awkW!?6Pw@>-1Lf~Z~gC;#B5+VNpSY$f=ME<8CT&;R`ZL+rU8e<3L zf6TgwC0hRgC4yY0H+GT^d(!J`&NGv{K(7fGR_ey?`DB_wvx@Wxw*4Tnzl?OUUnj?sw%H?e`bpp=w(rpgMKP*tS2>tK9KAgJ%?+Q*?cdx z8f!{S5a!rU825K~^F@rAS44&YoD48c zkL08))OKapF=ehVg*ak6Yzvucj({238U2+104N(&RJhJyRj0UmgjU$1;rHY#e{ioI z*ImQ=;2}p?OOk3pNM6(!iEu?KtsSU0FWI^vbCf%D#j{c3D1wwblysMt5)!z}!w@I- z8I+lEUG69~YKo}sW(--d1VtbwY!ppIHzIBs7l@#&G9yMGk=^*U{{RdKHX4=foys<^ z_@Wi+IT=uxAl@&|Ak}S!YZl5;f28-5V(ZMd3h6WjX+GvfVCUSQ(>Qh%+ZwTAXAQF- zrTY}@Zdz8CM5fbztCv1GS7pO+{<+w?XRjqy&N`9ITzw{#;i>eu2l_@}Z%M75&~}4? zsUd-JGmJSWS)&VRAuT=#D`knAD^4MNl^}QfgIro_vCUqX^@%J&Ifijee}uTx`*Rly z4RpzOtul=skw<#K?oy>?b+(OylADe;WY{t+_h5Vg+iA&eG@pk}ebp&9M!+Xl3z4g$T$wXck0e zaenB!g3X{eE1LIsxL}z{+;t}L$?Ag;74{V|Ja4y|bo)Nq#0tbcMrd1M%ejy9h!T5{ z6*{4f9Fz;Fm+-_iFR@*br_(Ey;+dW)S2vPNF6a}84hxB(bJLVNf77|q!;n_N@l$&T z#&WamO>X8TU2v`f=|a&{<&;X$bH2^bweL!~eX<@MrZV779F0kui4Y2qv`wymy)~nfeV;UNM-Qb+C{H3*j23HpKF@Iu$l*wN{)Y z7NzA|QZJgXBu{$5s?>L-v*>@KSJ78S*liwP9qoDQY}Hm=Qes|0V88JCR$8GdE?dL- zq%jj6EB#W4~8S66TX3OOw5Gj`t-xPS+heYH%hf8igesDXDp;_h)I5wxeSHQOlb# ztI|?3p5*M&iZ>@T@etfLw!EUM!1_ve1`7HzFlBSmf7XkQXQ~@Xk*HM~Y=|EjW~BGT znW!QwYUZAK#_W7HYS>=ZMq4-fJ}_l#Xi{|gl+|)-P3D|45ts(tyi+vEQ8`07JTFab zW8`Ja9wgVK_n=QOx@ST^Nt9uPK}nUtCO15S+h&Srix9h;)ya;MvaHFilWR*4rRv9Lu@6O+s0#XV7k#uyeftFc%v?FbG?Q1Y#MD_d;^~f zlwozzy)u>@KT3igwzom06k$y*iZJvlA1CZvfn1 zxgo8MOkDiU3sosQ8T49db0-7@I)~bUn6P7(Y69~)rxnoB1kkmCVi+T|@?YA4 z8E|Teeri7tOjPz3S~h=HW}7}Df9Q#CICzCPVj~5s2-@b&<(YV^(kY@?NQJ2pgL6(Nd;EoWnTm95&L?LarVV>453)gGT_9S{Si%)K;>hEEq) zR1UT+yR7@Y;!46Eeb$iHeDj7A7fZqHsF=NH+ARHZAJyJon59)p8Zr_0W7JXWBX z{qdt4wlNua_%it{8m}071JNoMJw>ZdQ(o7B(MJRG=N&9Rn980*%UvgOB0A? zG5lOtLt~kMOmOr4e~E_vohr@Z4tl^Yt47;lpR2+38R%sS_tS+kW?8v7J`%S$uhwD; zFt>?TuYEK18LPW1a}Hh8cThs#@;IZ!IKtD@Qi)jlOE#^J=3bVe{dmYu7Z6JC8xk&8 zE?xO|#ZTvz7o~|GgwLPFW~F(|FSUKAPH$?n1x?>|zcBKXf25xLvsL@!gUkLSTXeDL zxF5ioez2w21Nt4%sqYNtBB)Ga2Rf`GQD~Rtq;NSOo67z@C$Hdt2;jri-Cy=M4F;1d zMPy~<XRDd>0Y1t64TWJZJdMEjj)i=#Jt1m&Ez^nG{UGU z>4&>D;ll}Ye`Y^o)v|ZOg(~C3S3E$Ye~T=jZ=&;mFqV}j=B9U++8)=n7~9;@iZobh zqH>{_aS>%~FDPp}Ez~)sr@e6v*7bZ#8Jf@=ExJn{2&J?bh3WEdv^?`l&|m%5eB$u= zSj)+7nvJp5Q1Mg2VDAe0 z--#AIC*jjAta+c!XJ1OJZmM1=ki-MHEzuvc<6uNzG+uG)6a?LyAE?#whKlLy4-)1`} zXZt_F{))JEdX0go>{I0^^GwbdwBM|pWRoR$+$_~s?~GY8vzrnX2&`GPr2eZr_ zHe{zISUHP{*>lv>hxtHJWY%kZM_8DikS3%CoZPhGcFIiQ5;QF;YHem+-OxMGwNJ6H ze{ojSDD=5GryZA&-Od*n@3)RIG(6ozPDvGgjCi4`F*enU5@JG^4!|>ydY~4YH%_OY zB1F|x;MH~pnay0CPYFb+8WF@im2e=_kT8yiYwO{U)L=i>mm}uuAx3 zi`HrHi&$8167BXX@_$H1C#=_|e+rvNVx=bFmf~Te+!w*+o{^&j{{Z1LWRr3FCHhHJ z3aaGRB4i#Z!m-&pi^12IQf9pU@ueH2$nCPJw<+qI+gX)s3{DjlE7KdHGb`I+7VPy+ z5i;bf2#K@RLr2R7hoa-GRY}A%luS2<Ic6w0f3Y3CYb z$JY_vyAf6Jr=DpXEV#W+pxTCJE4kPsW5Z2;n|9m z?ABy2zcAyEv}?@y^7kw|(RnRbX4TpL-R7{$w&0aygiK}_l_$4hzfR>$(NGu5=L>b; zNl7O5ncFVWcGaD8K|fj+ndY36xtWxN6(sCc6w8YXsGDv~ICgA9qu8NV=!O5pf6DCsN<|xK>XFDsx#Jt5k z2%nN_a2$4JQ&m>7=u}SbYa2h>D?KGEe1;V*vs9%jyZOJYe`R}7Ac;*fqH{p0rt;+H z@gzPu3n8kk_!~Q8yIlt+W&32%OUcRZ?K3U>%$h3nrz_f3=NobLbrsNcZXd*wbqPl1 z73o${>&c=K0TM;_Iq8Z%D&zL+3^OK#PBnQl z(1?*I8mjksf5w$nGq|@Z($Z5Uz9Ac?l(>t8bkL$y4KU1c5N<~zyYzPuNPN|O{Ql^m z=oVIScO;775M0QL__B+JEtg}aW`)U?)e7D>h=jOx?#?Zo3La(9Dt=2~j^IY&;t|ba zrNgM&zVbE>A@gzcA-lzImXVW^>P3Q}RWg^FWZJp1e{H8v;VZrQd_3cf**>GIQoD%; zPY_Eu%*>d$V#9Ld>eP2gN<5eM`9w#12Celpp4s+hSV8H7q{{CtpArdzZ7vi5*Hz6w ze06a0e!(l;&=sj3-9GffIQ~vcJ22^x^((GfYJB6TiOpSwZqlrI2~V>v+YoKuTGs7` ztjjHRfA^|Jj^fp}Uws{A+P&x5W=>(2WXTJDTta4jL7sY|cO)l~TA4X{4wjW9DLKZQ zyk05Eb_VRkxDZ!VEmQ9mNaJM0&|+5~QzcH5EX>sFhT-pKpRh?pOC-rM=}*gFbRs(k z+?tWEZs|j~5p#Ja(imI9<|smU3azR4{{TNYo6$PPbqVC0JCJT_#)NGZ5ox1yvZo?QO`*S#X$K#8fV zf3}6NI&Ac@IU)KS``eOe2yJMy*Pefp!xNC$$EIe3Th_K2Gb2@HYmL9UW?+N$U_wck z&-Zfo=^L?U*|jq-*r(Uz9m7&$k}R09D$xgL5mF+|{G&qW*!5wIjeRMzE+I?yc3jT; zhlo!sxkI9KG7#PVhVpY|vb{{RedSj?nt9%Hv%yl;Zk9-rq25T;VoMSf^m?9!qSE13WfS2(*=(}nti zO3t)1B*_-$VY61u3#e5&z^_rz^-#t#x=#rV_z6AO-L%-c-TQ0LPHPIv+G8$ue<3Nd zvL*Vr0A@!yb6|x_e#4r9XWpW{{m}UFFb^uy`LZ)fXui*?FarI0g}=aw-`@zymHgBy zg{I6mSlpb$JCh>exK+LDW!|}R0bcI-KK(`ZJd;jNOE@LEBO~oxM4qtQ(DU*|4oTk^ z+@=#a%(5HL75k?6Zw{@?pQ}8+e--4BT37^)tqYYV3P`uTy~9Ctz<*UXAFDk6H0Kh1 zgN7h#n040`&D^}l)(G0>5eyOf&q!doVe;(3m&w&Ul zlA_UMR)Jlw-J~($T*&BVuUI;^^EWqO$f#la0wqhW=}MeeI&gW@h_vkHk_qae`IDQ-?v zGE)B<{VYzLgVJV48=^oUB&{tYQz1b?n z%Ym}>6wtmWDnGZ)o$OB-O<*i>S~n5rP)gU3y9)a&&Nu!$jqzfsgVLpsS1{hkF(xFD zrs3Rl=^aqTp&}G;1xw8rDC+MlwO6R;&l3|IiS&zo7pwcuWAmdCQf z{{X1`Wxl4Jtm5f9^BQ6Q0POBCs7ipCvdgQwxKwyE)0}d68LqWB&YBxawdEZoQk97r zcGriL5qi-fe=a^z1`a@b;N!6cpyatjD`0&?pZ<|d3jKd%Lq9Ce@oDC}(jK7#Ous1L zeq#^(Qbt4IM%*yt*iUG+DRUjz@5qDmjJ!YWj)Jm*z`IMwu*(_=SFn4^gK=7fJUm)M>(nz=G>t{B<&7dp$J_4k#2_X_TR6Ioijq`m zrIVX&!7FVffyOl{d!g+k4-2ysyD_Q^{LnGg*-d!O zKFaIz&VMr^(>l9&nI>qB^+Yzjt1-NM7-g4>^Z80L!{-^SdFojMD`;loG3bo85VoKO%bz0b;zT15(O|r&1cJkGzp2Q2LzM-aYb$bY1lpAezwJu7*A&$ z@>qc)Z1Zi~&x#Vm)pSToh??e{C4cO|jHHekXMJqXFC&_Z`d2sPckPq6vvvehj^+-G%}rrNYw`PJcrWg(;Ynm2V@- zIYUN7XnfxeNfoFqRLPa0nn5WUk8eHDU`qpdxH3VE`49^c?-4k0j$)$fAcoY?U=$GS zCCi)`EV~q4`9Lw9;-X0WqJdQqF4}))AqUd$S`kqi0vJDNxxW-xU}qohNQF#nj+%8# z_Mv38M)xTmO9JV!i+?)|%ba;aSJ*d&9x^Ig+ZtosRYHi79!bR)NeQP$9+P#??$7pN zc~QQhqm9az`CNj_zp}BP(SNC-Onpl-T&3nM{{ZM~4o((!_=Kuw`#U&XX)7^h+(l~J z8x!!H$?;HZI{yG?^|zT9LYp*95Zo@?aAosOQKf%|{{SIB5Pw%Pk?qmjcnkjk5~B$- z)+apJko@(BNATY>>N5Hh^(36etB5ejrlddklVx;k#r!eJpYAW!kN!y;J5Qs_nIo91 z4havlHFxqz>ZkC&f3ySYta%a3Qo_Q2q+<#&r;5RFt^LsaU&AdPOfGqACI=a8(n-Nd zylClv^a(R4=zn7VA6W8YmpaeLeM#H)#PG_H&QDh=#^z#Qp`E{!b$EY@!H7m@xi3ld z+}M5@xaBatW@v~10JT&I{8(BpFR3iw(uK1*S!|@R4Q1sY5%7Ol7{tG0M7}8S zP*v}x;)3ld$q9X(%snpP+Cc2b4p3^#9!v%8;-nm}(|<8i!&KH~DSI`i3o`s6y_`!$ zKIph~fmF%$fsIq?#>T;L%V0b*EA1S?{{WN=Wl`z-v{q-nn+>l`xJbnp4N9SOkltU> zBBPMMP+Rtt+(GoIZFdwsznB~@E(qES*vyDl4SBloP$paCSEs&_?Xgc>Lx`lM-1gd& z=}&gCCx7y6MNi!%G}ot|*lG!DyFjWpcTj^ZkMw|^b5D+xclGROKABrhu(z1_=fkDqP)i>tU7wR^x#Ym}Eu5;vSNL4x?$ynK2dOLFs~*u`EY9#OJ&GP$e^hQX@SF zh9Pz(De~?EIox*pF#)*hohn>PULv~KxPOh2y}~&)yD=L>{{VDafia>N6;)ajWwEz1 z;k03tOk*g0I)HV;J zZ_?3(={WNa(xkBe0B(|;o@t4yH2jN0BRI)u<%^o+sDs0wKQvhA{;L%9_?j~Dc7Ju| z-Z4%V*q%O>tl=3GEF+95$yEBPV=`!E$wVGabI99L%oR0;u1fbC5z|wZy&$SnTN&H* zy=IR%U9po%rk@hDpH343UwMHJan9y4&xnUxDRufsC1tT=*dJ)L$#7-7SxXX}@cQ(( ziZDd%3`@LcIZ3{g64w=+T`^`>L4QuoC6=d9{{Se2sKlw|Fy?AMvk(zt-sCOmPOcb) za|A%Q!d@ZI6+fg2vgYm+;>Z4(q9Y9V$|4_&^~iXeMd zFokbdhsE=VE>z2NncO4d@kANi$jo}2)NWh9Y9OkN=AA<2)BI5aE0yBip?@lL#0dy$ zl~JjBtc#D1@?KrgGaiv4??`qtW+(cPj%+@PSOR)UnOP~xr>7QRD(=0y8EJIp8@$z3 zR`2DOaOFjxBV0f3HbQ>zfl5RHHX!uo;;CY{;{!Fr7yA!MJ4X2;%< zlAB$XqSGm&?`Ncp!?PS$0Drjq9v&EJpCFNJd8fW16}DyUgh@J}DmFU}CY%voA-dkEFbGQX#36Ei z5r{V$=!BY;GNN2#ZP}2Ixnc)$fn3ASzUgipHq3lbRv(4-SHIciZmB<#mEVbNg-etfOdPf3_n+VZfZt#tN4&?MO>A0Uno#F z--)ulGB!f~3&*n+NkQLHYRB3o-4VIH*>R?+_OgXF>_1jhOtB%p4lfr;7l$}?Mfz>> zA?p%tGsNS9Al`gX)%NiLu$xcOm(VXBdQw!Gn39urgN4a`eScnYwE}fN^qXB*=DK|0 z@U13xsvW@G*2X@G+yGsXxmuPgm>wrfCub)d%Pi0RWRY^q0VXEMeEIas`arR-q@?@D zSE)@%(RJKX*mBm%JGYs0`ErcSpMECv!l-kdK4|K*`_l0hqi=;HG6MMv%!9kW5xa*j zDA25B$f`;0Ql)CaG$;IVvg&IzKu%T~4Ukno0DNV(G9fj_Ru-eZw@-7ykf!7`qgwb~ZO*w%6JVxPN$~k!@uGJdJNTh#RdGz&An1 zF98sN6B209Is&=ctDdN_?QkN#BcXaJtG0pYpM){In{QSy?l%$>Bedp$fqhAH*P3a{ z1-MwgBU6;oZE-sDBn6C500QIE?m6^~4;S6;((Tt4gA z4py6nWq$@rP+!Ci+Cny>D~gD@pPI(i#E@Zk%LTJza(hk06^R zx~EQy95?)HV{0iBjWXa}5VSkE^sh)AQXQb$1G@jJ4*Y=jM%RtWO z>ipX3y5-NDCEuuyLDsjmW=3VwHym(Q;w~aJNL%#<5JRxfdcw5<4<*~TpG;URIc!C+ z%{jp_DoDuOG;&+C6M`)b>*z~1PE~6R_;*A6$`b4bXgV3fX|*Q85@L^HK6pT`Re`9g zm4CX!C1Z#qZ~Rb3g*EOhr`rBy;o;%xh&X{{6yCLXlwzhfH54P`xJ;MD)fAyJn)_?c zOSe^%MqIhg((iqdT=f0Xsv@8&kW}}#jF-a<4{#RU;fgFQvU}?GMo!{$sCuI#xhSPG z6icIXE?QS4Qu~C>q9wo9=?oWP^NoIpNq^JB5H%_DQuZEk13b*dyekYK<-1ErgeEY3 zNon;h2*gYHqUX4HpGMikc!;bOUbw}TPR<3ndUcKC{%KVp$v{@ZK`;cC4n^~D&!RZ*Aq zgO{4+2Hbp4`$-bX<$eo)D0phT$NfeabgbrpLUL^bBsDJtcTQ!zC z*p$xC@vD^@Ev(7S4X?00B)!U+LuaI_EsEW0ibEjduMZJ(FKQBvp}5m-RwU^mG~pXI z3AR2Ss5Z)Wc=*eX5|K99FA0dJ!>nb^?sahc7OGPluods>gjCT|YEGaJJU2lHK+aAf zOy<9|Q3?Y%%d-YZBsqPUSbr%s8xu|!PZiuS!aEG=RP&m-Ny}m*v6N#oCWkFz7NSWF zX9jT#m!@vRc)UyO>k4<+G|+Ick2T9biUP)j=@)3c$z(A=J7{Yz*(}Kc4X8aa0a;+J zvY_{d(ar}}BslI}w0Os=-|Gib_p$uIfVq;}cfu(E>RLcLgE;`@?SCu<2SSP__Y8`! z;`pGENh&IEJ40C~GKN^oxg(pFb+B-chuIiqhT&a}o~gUUf+j=L6*c&p>rph>cN{V! z9wO?BiP#TD9z9KD86i=*J}xMFi0oHt8#%+(LnWD`(_SY!u4N+ED@ThFv8(wk70T|y zaAiQpb%$gd`#Fh5h=1hg`xzhcoH_DQG`~h14p$qr8GauQ@omZv^_l*{zu8<*{@FK4 z^mV`uOx~DoOozkFt%>@a?eYzO8N-kGa7eSV+%Za@6P2OUS>5!IvV{xva{QRxhmFQh z6@tM^WtlS-ZMLQD;Ts#YNh3S^o6%S5KXTU(FeMwfOXUfOuzzSeWAt;ic!Xjy(2oBA zQb=YuA&~z7d6ltt7K^_jtic}hbmuXOG*p+Qp)8%Jcfvl>L@p;|fwA^?ZZ~mf!|DuYHYK{Z`NAh2hokJUQygw$Ohh~|w~Y>lEEsKfWq05y)@_z>`uuF#D62^CHnet?zYn7#y0ztwO;~o(o6gn|e#OCGXY@ybxpI6Z* z4Q_4ejsP75vn7q(e0?=j4<(!APHg?Uh6$iH%Cd7!xHk|G@eXn9xENm&)bL-G<$BCq z@XU;!G@8y{T*h&+yMS#WU%EQ!2b;>~_rtj2oZc9frGM9{^HL_ub_~e3!ub3D0L}|5 z>`W+Wj0an$xHTL=V@e+s_XZ`iMK1JoAcj zkxh4sDib(Tq}^JFSar8s9w55W74}!nJ@J}lpnXba(6;{oKU@aJX~{B}C(>Nq>fI`) zo!$OWw}0g9b}UOCMy0Q9DrQ%9vzyl0WF4i?^fcaU&|310u@@NXu%@=DV(IsYWx<(R zg#4RZsm6uHf3(a00K7$AG)T5xc$L#qjfydYcy48~bfnyNUgznql)wO!3(cd;@`ZLa zV#U6uHBKPg1GwIG>SZ2|)Zon$07=T;`?4y%;D4T7`K(H8Q)SU;!@h+xGr;9myNIP^ zW(A}sq$tw;rg8lF1Dwzo_oOzf2rJg#Qym?!A4OT}P6HWgJsv{_tJw)u=7lC|4uGat zrfa}tytzh9yoIPpjXho_*u%bwm~L0Gm3Ow{%Dlc~+N#cPNSB$OT(snxMpt&oHMsbv z%YUfYKU`8UyDZ5!IYXwh$e{$OJPind=-OqbiO)zsM}H#q1tS`IU|@ZeSH7;Q)QqNP zn6PFV5Ou80b6rt$Rf}FS+o-$f*SO2minjJBa$5)B_wiKwAyVJw+hsOzRVIH=Pi+?rr zLEyT}&6LFDaE-npCJ7iE^YR>^=Z2!}6n=;eoUu|VvTnWG3#s1JcNg!{C+SuTOp65V zKj_=SR2c?Xmzb>XS?C8`-U$AE&=bbc#+8xPchH9qOi{*NnuO$w_n~h@7pCNw)2u$f!cw} ziiPbsZ5zI1xp~8Q*z_iCA0vtOxv>qU;8)ysy4_{L66!Y$#yT-k6OEwX`whplH4jHB zT@qSx+I>yHBJBWNTg=H7Pv0DVemi9@5SNuDs!=7~s>;jGRHbqgbn+rIM1RWs{ruyc zRQ-CkCfS#X&DN67x-#=|o+Xad~^w zx)&(PWMy2&ky+PNfCWMGu zpAc+cPkvqh0Ll{lc5q<}6o1uG+L4JnFNf)(NTN30`>(H(WSM4(0c~`#Xoj=fN2M}s z{{Rrm%1B|kTGP)B!!)F1>3lgNF0rqT#r0RIi-*y+wp)y*v4t9*vs5|94d89_lPcqq za?Y>35mP@ee<;nH52n@f47%+b9X%_9DpdLg4f?*JJnMU>#Nrh^9wk1@oKJp(ZQq9C5q=DK{jK(RIzbP*lo(<0HA2?+{jxh|>sEI%CUr9hM+v44gnrA$#AI?E#TwA8EfR~Xtm z!&?LL=NeHP#0PJwrEzw_+NxK#RGH~n#v7PvfViy^wXr{zUy3?dnu7lTP~~ciuo|Sp z!ZUM>c3D|Dsqty1)SMM1%Z+qj-boFN!v6qSqhnyaD=oA9CowA~*-sqf5`_Du#3E|B zsOAWP&wseGC|u;anXZb} zj|}6|PfQs)A?tHp3o=zh{E(wFEV8b#FCay57YjW^ZiyoKDk3N0U3``@ud$_O z-XpQ81C>%+PcoWPw8caRB=@d)`8KsGmMd z-+vXyey*Thgtp+$P7q8sGZL+ngcr8i5U0!fa`s{NB9*Lnv1)a2898tKHS%E#ejXf=*1^n1h%y!DLzVIk`XNvy7Ea_>dfG^a4yoRzQCz3g`nPtg4s z=&}=xzba!eNy;=VTrZD;t+ti+!Nw&+$hfLJev4 zK@%1(x5aPH-YnsWRLzS86{2pW%WC%K;a^@Ei8Q^-`NIRn<})c*pV}Eo? zB0#!^S@J~hRfX?jMngR_BTe1SZ)PqLA#}%u(+w28z@NSsXB~`|m6|!JmgWt!m|#I# zGS$12m>Q>WBJ`(x6m*iU@+DraNtCm5O_j>wH#nkQMx(&tDt{z~GW|r1D^u$$ILj_? zdo;NV98K{YyPCFtG+)XsdPp;xmw$1PUhf}F0CKGE|um%q6nW=5&Y-kIKuI8V~Lk3CMra^l?cZ4pV&3wFB0nP^FtT6w{)E%hjzn6{_H z4@^%&+(+ooi(ZweN|8wssedsv8Iw+?dTZw!INYC$9K76Cg|V-;N$CNJwi8v~QfCNR zbM><4#M7zoNZ9DEAu{OtQ}k~AFVj*L=~J0{UY|KDkUu1EC^0zRT|u8EPbk62v$kMd2U>sJeoCLdIX;r7h?(OvntzvQn<1asO-;uo zWN`OpZ0zj^CcD0QX_Q_(nh{EWaaEx0dT(U-&R(rITxgneiP>6os*7%?zbw}@j*dLP z5-wHwG#zPxcIwV7mY1Gm^)pQ&2%in#I9uTP8ZNWoZHf*%Vtz<-^=E1kaYklBtzyeb zsx?w&#Jlw%u9wLZIDgGR*VyA<|aSUW-!FbJ6&n{W_ z*vYnXQqiy(Y@oKFaVm6Q)g0J42GIR%aSRovBWWI{f3DDRQc$YR#J$ik&3l zDjRNV(YpujqX%|^!*a6aCy|8IyJ*vl80v%>WvWIw<7H3h;(zHEm6aIRwB4vxXEv73 z#&O5~DJNKA!lPC}l_$xtO$72yRTrEe6O;PXWp5?_0I00oeXW$^?W1hkrRb08lS$gn z4i<)`h368RiR6S-J9Lw#A->5qT))jDvG`77C9r&3xs#32(ss9`V~p(za+6c1h?7$k z;hJa!P)IN$F|O#;KtBL1nyzSfy{#+)D72>72i_V+^imc1|u zc5S*sSATeT;PB7b1u4{;hisE3SNp+V$~2LUUx_694CCNKsG5v90U%y(2!Iu|Cq5{; zhYC2AUD?3u4Gf1$dK%*mg%cPO^IJ#H98I!Vk_`WbA;(0{*19jr-uNNj$eU20$_CPW(vaxSlZSn3cQaCY*TlTa-rVDEH@$DOk^tODHs#H8Up4NIYSOWmI+e*20V#; zM}N+rqK0zumDQvEEWbE3cFT&bFHcB`Pq8&8Ilck3NwmAF*mQLD1*L*!R;@DRu(&06V>EY*JF@S~ z?u$qH4aDWq#^fHg()E(l9BmcWk$p5Gfr1nr)4`5Ps7F%XUL|Z}>5fE>P zqoo*sXfMb%_6hWJoS&K!m~XR%w{hl0_E8g9D8isEq}zKx*j+N8G9->VR zx)@sv-u$8z9k;ud4x;EdYGckUUbH@yn7Y`6B&6mbSTrOh-s8>}QKXEl#)IO7REGhw zC{A3QG4QB*iyumfZ#9ctyAxsLsDIs}!<1rmLaAYyNLz@9AaVyEZfh;19L(kR$1>zD-5V^ z%Bs4IDaIrz?cKWcz(nY5b{V~FMl5SHDoz|ITPVh9wQX{?7$U?>!4R%caDSwh8xe0l zad*)}3Cwr&5$o;iL}b+fqNn436fM_a95McxThfG%2Pa^US9XU$qU12oX#7 zD1;FY44!M9z8HbTX!8;~!+*_r!r6eJa+9<(_Mp{@WEsp!?=)MABCsr*A*vEPuq?(b z&e%vv<`Tk1=MY;V5c6s`D)6K*T}JL^rF1N&QN=fi{g@UK@WqynJvsjX6lBy1Wh}Jv zA^}w4jQLkXQHiNB64uzn#)ax6LK){sDG;VSD5R?xbrf_WE_cw$&o&ViHGmVH*kg|iIip13mon_>tdryo>L zWl9v&q7ieGd%i^gERK3|(9Z5J>jJ?j+qQ2HUkoByV5wo$tkU0??8avpu1ZR|ZK|Fa z3fQ7vPGJlF7#fd8B!8JdFU1#7rH9Orl0h<*IBaqVMEe&CJ8W$MRf%D!p<8JL;wmI^vlUUHJ?osfQWKp(8>XXy#x&Jl0^!NnC({|_@QAq>6gU63=+15SSM_?vdpak8;FY` z5l=X8T(trA03|{o^n7`Fw8c6W<6V4Gs6Zm_FAKpr^DUVKStL)n=MWRc& z?GTxNIMTm@;m61RkX=S^dTwJe44N7nH+vFjQ04qb20!7!QDq`(zMPvtcnZq2ysg|m zs#FL0qQh75TsZMqC77+%Ju^0+G-Ylqoe?hxwgac#1AiECM}mh&pCT4((z|ID4_E!L zthR(Umgtawq&ZPz#Yx~LjlN=i$b{8;vpjkguFV+ToL ziQ#QY6Mt3Zlpm##YGB`ywsH2!@UN$S2B=~@J56f}?PCp!jYgUyyBXya%!O0UeB&!4 zK*UduQ;?aSdY1Eoa^){KE^5=DW5&^<zG!qM~Xc;vqOOmwyo8 z+u|a(svwqd!!Q7rJFNP#A79pw?!YOh==XY0%0^1y{Rccit8SynMi#x7}A8!&j5l=CRq~4@V zxD2yUxvlwhwQ^;5yHSS2oenf>$bSwE^iio|%zJ?8j??6+eT3VZoS&0<3}0oX&oEK( zl8##YUg*C})Y(?O2SR#5dSFO7w1#L+)S7yigz2x-fJl+R6M1t`1?IMJ{`hL-(3&+$ zCN|qsJ6ovWV){K{4of`1!c9H0#2p52ZzM50wFOa z6kHN-sXQ=B+8B~hs@QIJ_Xb(VNOjWXi}gqL7~#E`)~b_&E_${|xUMefQ{__bZtNF< zS;KVp*p~fQQG8z##%X(s$n14Dpv_r|l;$MCbI)$a6G$k^ne+?6s(%N~VlPM$EiGm> zhS@epQQ6)onr3N}X4769ZMGXZ5Oc=*l&AJ{Yl!~<_Q9mA_?cY-wnpmYMZn+121DDj zZ1cBLAGHde#WNUj#1*u-M7%!I!6>+6!#dF|q~k)DbljIjV?})iUkg=dfK|FJc&_mUJ@Oy$ z!6{FLi5Xp#;>q^~7{R#0VO;hpFPHR*VSl7@Zjb26v}(iIO6>mt6_EIEle7FvkVy|E z`gHOni_m9l)eS=d&yOC+`0Ch=ivi;<+x5ThHbFb9jJ1+D|qhVYz zFGQ&0Y(ZS5wl;*U{HsJQ55TFaBoCs$Z~75ARQQni!|1Qs?jU*RX_$()PnmeGwp>t~ z6!}C>HitJie-qmn`YUi21;qaVT4MU7)+|bSi}Lbrh<^;MvL>i*<&rA*>z+{5!=W#l z{{RuMr1#QAPr{w62W-u`396L7VEsq+QZ)49MG#)~PbAmn2K<^7oL%`JHa5wOrttA4Hyi+_&7CUjdR+#}>xmxtvBqMzKCr_F9{;qbv*Lj5x5vf8jyakPue(Z&7(i0!h` zs0(aP1KV1yJR)LG8)idljk&fSaX8PR`QGFUf2<^4k^C{RV&OBYujWg$$6)mc2-`X&@{t{Wtb*S=VC62oO+%nZ{>fu>|$1_0gATSv7(dpl?|t2 z1Gj`-#75Yki<8$-k zZW&H7P5>uWv*w_tfM|lA-<&%OZ}$NRYTQj;mD-loD^=+_d$KgC>DPX(lYFizX+d0? z4A(CPzls-@HZo`GYp+SnD@Pn0^Mo?vK7Y{qp%LON{{Va?<3n-#_@6oPV`9!No)PKa zuT*npbUl=ila*PjR}T%j#Hm&x3`9>a3;`@UY(~h0anQCe23GM5KuaAl$pP6dPz{m# z)3>rs9m{aHVWE%7R@i|=n-|cIa@==NXRCQDcif8=J&gIJ>9?{aaTCQEtYx;wFMl{F z8sDFa2w4|8%5F&0n8YF)uWEBf$XbPJxT;@t3pS3P6xpxFym#cT5fm-v#*+_Kl$*^+ zp>`@~MJFAO?zeI|yjKAXt&PYGu$o=Fufq%_Lx}O_gTn`6fKN{l>2ZCS$jl27Z<4!m ztPOz}+iB(96S)Zo9LifX;^BfARevW0MdFAsX!CbOA?D@?P7O#oZerdf<}fijA$r1i zle97@rN^bwOs3o!zQ(|y);dvmP;w~5=ymYsE_B-PkPci$cU94zRN9ZS(?hEpo4kvv z3sMz}y(+Q1U{On_FUkv0Qnra18naUKqKlaVMkvKUh=V8wgBp6ZxbJ<3Z`0UPbxskzoC=8C38+P;kv} ztFzr}4zEAD6As@XTN1yERDhVrc&lTzQIN(o*@pP73DyNkqWjZDiRml&h3e(U<^Hg2 zIu1Uj;;CU&d8x*W#wEltiItRgEQluw&thq>ZYPdw-N>aJRR2+kaFL zv1L$4%(z|Q=_`1DI0<2co$%T1LKtAVN|)8IUM^e6p^D#98y<^PXBtpfm8w-rNxl}F&i?>sI3`^Oha)LoqSN~<*r;(KLhmP~TC^E-SO>S<^Dq~{C ze_C3l@WqS*Q^`5X*jTATImG=Sm#c|*OQtG&06md#tj$aQ&~*jW!8g3re`X~l22CE9 zC2YkO>wTyZaKqwg2aPz0q6OS;<76wC^?!yfhk8V$iy+G)s(&{WNp%CUhlB{kt>e4I zP&3$kF_)WH9^U&!)8UG#VrE?p$pZfXrHj-8{f6?SVr6++Fls6pU{Hy^gb86Mh`rE5 zQ?SMPag#ikmCy`2Cd0uN(B-%cMZGz^OxKXZ5D{#UOaVF` z*Ay^Au<^GNWPijuPvL`z(Ay5>$Fa+MH$h)TG%Ssq6PTG5yQ&bwk~BXT3Fm}T;@$AX zm^WhfA4|7uB)52CRJiI6!-iTf53>m=$1T0N3yX+{h+-;&t6|)vy`kD2@o`1WXeOO_ zkEP~?=QJBcN2&o_xPi=^^CXvW5eaoc8c|qtFNuxEh<`VCUBmWcHTG#lEZA+vnPgmE zE+n*lsLhqfDSeZgmYQbr5E1G2V>VX_8KAY{iAJPMNjzCwZ=59~sF|N?PFJh#vKE_w z*a-0#vk&>4x*E<_n=g;CYt0#4%()?jrNT;!q<4Q6#$1_fbAKnv^zl^LB{5TN2Yg%L zZy^i1vVV_ki=*b8x4Rt?viuuX!}~LCt0N;OGY@fzkf9MLvl=@b$)B*|V`!9|VJ77= zv^|DsMFcM54Ex0wyX6_Rvf)Ox>_q+pmLI97iN=IbO|vV*?t*z}B&iKOw@D0bgMe4Yc`OfGkZ1K`(y3_>iEtUx}+R1!8`wKRE2wCD)X5Vy@N7LhEXp zOahjRr+@iH*mt7xQfe5OrC)xKoU!%7AwA^3E!@8xKPpJM=>> zsef0dix&2(OH&gBLqVpBy6Xs>uXR;Xw9oH_Wo1t?OT6@{(;FZ3Tb#mpY-FdVr08@( z&4#4pn@-q6?K?2Ks;nXNWOXmmX=<^4!+3WFYz>hKT7K;3jY-mzmsywHT)LH0n&sZG zq~qm(Erkz@A4-;4>CAbK9g}YZV3M9}vVS*UpO-md(K15Un#Rn3jmbVLCoeNyK1AH- zpf1<;u|Fp@P{k5>B^m2#oZ_9y&%ZQ|ivBm{VGGS=Qu{0PPvI;?a_{3@L0QGqjDU!n zRaO9=wI;P(Z{jOI`FudMnZAM`U+romM`Dm4qiIP#D5mb z#>Jp)D+poG7UU>$Qf&yWqR`kmebC=Y0A?gy*^L%q%eyP0BkZ`bO^rZNZ0xpz+1#=_ z#rWJRj?|AB!!e{2O+oc!4QqA?FN-Wbefo$Hr^f*uvDNK z)&a2Hovl$EF%Ho07l@mI^$?aHNq=IxRab?n=84~AxPP2WDJ(dfr^`&sl90>19?4`; zY@rY}&6h~j86{wfT=gO1;f(XgTJ(&&9CztCvKj0hi>4iLeMI42(T5P0eiek`cE-ir z=RKh8mXVF6o@RcRD=-dqTsD?){{U41t&Tg}XP~P%ex)%xhn$+Hh00>k3V&|zh0Cjf zY_M}?G{e}xW%W{YDZQvQ7e(C4ImKR3*ePKRX^rAu^4#SM4wY{rh%v|%*Cd)$X!QLW5VBr4zqZ4qx zsy@sa%r8X4^D?i=$&UH6Pk&zdexWWj7E!Yd^ zo>`(qeltjD#g(n09M1Z3NNCKwLZsB|Qn$D%mP(+RbN;cKQ_~73WlFKMNv)Q>Pa?x- zTo-{w@;1)qlZPyoIV?8{EBu0H(P+Xue3`^D4_i;4q02LowY_mTgMYi9d@nx|pSTV0 z#B^1vjv{8EQKVT=pKL9?*{j?o?#n$96O?B}SQ}e-m5rC}6KeE)NZS?JaG0i%DBCpm z=*=mwa-#CeIysXCdPQBnM)=wO+$y+5;*+7xVSGEj6$z(g<<#GN7r9k*j#V1n0Wxga zv)Jot+f>8VDqP)b3V#+4ai&Sm(KYMy9~uItO1bNndPV%K411CLD)dX({v@V7NvdI) zd|iksvqbsLwLD_mjhg1Vp(13S6i!yg#^&X%Q-9<|#Kn%G^kU(HVms05Y%bR zx!6Soa$DQgSU-BMOB?Y1S#tE{DSapCV#$O3E)KT!J;z+_QtXK}B{=KXJ0gqamWH0? zjlP%eHluuMbY>aaw6mBZYJ2MmDwL$TOYde0Vz&aNxV$D6Z zy3~a!;BR#CIQt?OWJLO+XWXC4DvBa;td$zJ*()_-ZGYMK4pbZ`-4>W0EBH}7vRF9A zg!GiHxIbG?ywS}&G}ODS-cRN>r!^Es$hBQG6^A5O#2G~#)G-6}dFfY}F3vO4ZWXSQ zH@Y0OE6YV*?NJs5U#Ls*)>ovnm2zHcNw+YAz)rHyKN2 ziNa_bAbH85X|zs|T##Hu@?BGsKt3Ih#QH4wb${t;iI#Uw_Jn!YK!{~jiPS{bEZ0;| zCE`fL=xtUVPD+fNnQohCSnSsrwAc}I(3ib*@8*z;G!?iR%rpYY6JD{hb+F=XF7vk} z=DzMr->h3hNhh${VaHiL>NC0EGZQ9MgCy?xd;BW_iZnEGg z>wliBq2>1!7p1=-A%f4=0-oT6ytue+xT04!*x&w3<}0K|eRz}#UTH|>+*cYRa2)jl zNFvgAr!H&kuif1Yj^OqmxhgLh%sAN)+|Y%iaDsi%sf7q*h%uqc;ku*B$mTNdUudrq zu83KD$azJo?WzpOoK-`aoWfXbTN#OWWq;&`<}FbX&fn+d9X@BW)k~DwP^(ks*`vJb zOdD-8Fp{Ji-I=C(=gK*Da=OT3?GqENq7AKC#8+ExU-j;d0!t+ZMQa9B`Int$USWp| zv@!b3$0dt)(W3~$l=5!bwdsNnY`BPr-xQ7mv3SAq;Xg)m)*Y9jdqPXard|3&0Dq{O zrVmcc;GnwOf>x3L0KLml&3SU}NKUE=9R40Dnvl#*3$qN9X!whyf+X2!x8;^di>f46 zoMDHl>fC`f+6R2`D@0;fTwuAjrk&a4qCsv2jwP9HUQS+OUR|P{d-R5wEcnkXo&#L; zUi8iyLLQ}9Cgd5HwB8wUbBOYC>3>5}3}cZ70|-)xGl= zD-eAvCpj@vDF+^OZXpSYi%^7KAJn{yTBS!&Rxrj%1w~G6fb2>uzAzzs*4Vzc_Nn{En6!EQ4uzHh)-pW3>~q z?lN5fUa(IgM>AE?5qL{THEkMf94%!9puNeF&obSpk>&H%V3VW{qdVcON${1bK%^tn zYJ2_gZoBaYzhD(QDb%D1w+W#!3xtHOxfA(9WmtKlwuMXj!lyJdMDJ;2({y)QPN#V1 z8gQo6yRoY_$FLeM+psj%3V*#%YGO`cTo&d!onggI3s^jyYp73I@;*0uYkCdWy&Umu z({9U?)RHwBS-~C`MLgFn7cS^LIle4%vvHf^b7>3osi8KA+ad7|Vo|g980v2?MXyf` zL$Eo{&hyg~RMI`^4*12!m)b>0$H?KU^$*9%^v|Haj{g8klRI3)V}D30gutwe{j%AI zE+^AG)D;(;b8~SSI+!V|^(nP!zLyj{Z6!2Pay_1OT~!XyHB9J4b^boC6@sbD@FDlQhx^le(iq@d7sAeJrBvM z{kQ)BV*C_{`eHWeRIhp;;a^ByoyxFNBb2RrS*)B1$x~5675)-M?vG8%z}y~kK1@VvG3vyQ@UR+SkSF#`Sa;-hC)vs)et%^zTXX{ZzwcSYIF3Q*-6f1yeKf`Qdol8 z1wZut>R;;zi|2n1uq9FEfA>Ml5NvGQ;p%~A4#L}PeVD5cv9diMYk%4Oq0`*|0DR?z z;pYU?u$6jheKz_(eIcecQ?{8oOMs*+8Oy%W@igixay0kyg|=H_s}5CK)aidhouWzM zA4=?Pv&C(yZ87QhMpdSY(aFUZgHGhktAt{qeS-Q-?7z}q4bx|Srg5g}xPobhcx+P@Y}`~%Ny|7i z$>{QE-70^_I~)F=lW+Aq9|Y~HjkRncOT=EZN7Gw2%cFV`NBDPnlhay$EGdx zN`;<995XrG#a~ zDa}7P)UC6ip5vC}X0bpEQZhFiBWV$Ej(<2};b(so?x<=Vplsr24Ejvc<|pM^d5md9 zew|^raPQgWuX?X^WBnszX8ppGk-mjv6dV~(q|;j&X)ztyCFEU1so_=b`6Mc3h1#W< z913iX?Wd07;wF?(2i=xgs=+g87k{vu!a{Sl_Fq&^-a()bhdjICDB3MeZHibbJ$8Cp z>$iU|kcnGu$hz|UQS@)bz3?19XO~G0F)%UKFg1pcJ#0V*?me02kie`Xea8h*Wz6=N zx`NHHjm$yRU57I6T;`BOsQOjwVO9%TB&BApfF-`9jJ&||fOXi#QT(jZ!kgHNrwhxn zS?D|^9|JS8k7Y*QV{Al{p+`;d>uhRJ>MnoWmn4IU&_{@PHH)yZ06xma%LQQ$5|NLT z01h{Jp>Ae&AnQ&>aWL#h?ruFGFQC!(V{>mlC59;V2+Y05azR5AY&;hJ7^h*dpe7sm zJTZNP>@;zXGS8*~L6A6hZ!4x3@UPm2rKH<_rWXuhpOl+ zG}Q4o#RASTLo*~it$|#jL|Aaljl8+Wm2*Zl9(Hl=AGHlnb7myjhb8)w-k8%0G9y4- zW(#sMBBpyfdnpuLsGAqv2e<-)lI(x=iKr=%L(JwKJuy=P{Wl@1%x`YXhs74GC6uq! zrSk&L{{U(%2SNgQyKcAIgNV?ULON);G#;KPu?)2fN^;k(?&x>5YHhu+?3c zFcw~Ud89oU8H$TKh1#rWM;5-qdtwq~kYqi+6Xa0AV|k(x485pB_7dzv)STUg$<8Pe z26Ax=Md6E>(57NF`Gph8=%ADwyOEM(Mbb$bS*ue%_*|%I3`$8(iSVoiLuMpoT)i}i z!fZ^*@Wn@SLXs?Z#cg1?>^y&Gi&)J)hN}+5ZGxy)2puUBRF)OI zg=lHR5@SG0u+`qUONvz!f2@j7m~_jV%{)aGtO6#^eDs*x+{E=^>@P-`+_NjgKI>(~ zOY9=~N4ESY;+N9(@{U9_+^n6=fTPvF}+60|H5ZzwFvj!|pQ>Fzt zSS8ig)j25Lo>8WGL!XS$y(fA4Nnm^>w#}H%NlOb&&WNP`9IStiLpLJgA{#`Coy)?7 z2a7vNrwTQCj#}A2q_pM`+C~M(z~K!_pb0OC7$5Im2X{_i?^GM32R=k*$MGh9kW3#d zg1sc?rzy>k)p(*^x}#QpuQSl{{&9vT#aC-%N)*yrpG~|Y73vut0yi{Q=3OPl7OtRF zSj}Gb-XCfj5hH&qR1h)B?idRdxF619Wt{lKgu1>_ds38Z zF<5!y&0He(Zt{x?BS40wZOFV!;fl3J&$;2MsF^_68SDwi$cp9a_O7-r#MA(om?sxC z>iD2Zs2jQKIFc!tn}L+ug~XPZ+JjQaDT;wNQXgwrs+51MR`KNW<7#PACY}x^6{%7_ z5n#~oPrEWK<6j;A#@;&pK|iy(DkdVO)EgOEdedZ_YGbm@1BEj2rdnm)mpE+{*Aru* za4)81D`$HUm8H~b2QfJ-E;3*ZID6YO)7_#(zLx`AUOMpl0@%hGReU*pZO%OAiv-Z9yHC8jS z=qXkkYy^AD`NM=WSR*w7a5(r&{GtSkEL;Kt&C6mI@nL)BvlT}I@8XCAWBUmyU)3W6jAfxgGSIcJlx|+sBo803`dc; zq*bmHC1~88S3nJ>#XRdX6sM7G8J#WSv~r7x*sn`PUU+=`Q9;zL)f*{^_CPv|tK|@K zL0QGL?%K%;7QeL+IC3JBkFjnIqs>TCY7>&IO$~-jWF?oma?L*ID=ZILSRruc%xblX z&EtQ&+X${RbhwK@=L&3}QNF61rwd^}t7)vZ*xC(^v74yJO|jM#gGWU|1tBUBW)rLlH$v1eSkI-j@yCF>yO1Vqwcp47TH}B}i6R&JcQWLgPB~x|%1db4D-fk!4jG}$yyoa6=Lq~Z%9(#iB@T@vpX_CI z&0C?@o)Y2pbk|E?*?}+8$YGtC;`)Qzw(yQ7B`o;9uXf+%5C=vj>3)m*MXx*~CxU0JA5& z@Q(Q{X?}Uc^qh?=@nKV6MjqFyL6}hF1l%qWesKgvK6ybd77uG5Ahds?M{9G3bi+7m zMF-oc8UFx?%wJ69ds4Ph`o*HT%hPg{}n$_1hjw>|bLvc5Gt- z;daDI>q<-u`R5FTSS$4oNHc#;*`+Z=W$>OH6mO^W7Ki#p#3?6|-(l-`+X>S^TVjhy zWgFoU8CJ*oKr&)0^_genIhWf-E}8@~tjf!Q71o1bB1`uqTqYq+$zX1JK>`N}AMYRK z1=0j^29Nqc6Nkb_ceQfF+Q4g*U?w)=ZZn#^L|)C@BNAOpW?_q)W_^F|MAyS{bi|yR zCYTv*qG6@Wc?0VqcBhaBHq|=ixlI`UC5h^a-G6}C1z8yvP%GdE2cgqJ*oZsF>H$&F1IWuwG_c9YoV zw==Er32+}tG`~3KI(>glRWneSpObl+S&5?-UdwfF4gUa_d;(=@3KdOP=|Nb;IKK>4 zFwPvBenwbE$*F)P2PY4`q+a0pLbGMjuZ+tbOmI#z*_+fCoRxM|*;#T_AR)HgzJJa# z%&_O3^(n1!-XD66L90(AcNDC;kf{yABwPrq`%#^|#-=^5NvMA)l(d%tPBP`y;f;!& zCU=gmqR~%hG#_Wh4NqYV+9u>%wUJfl4DJiK`~-+I?&*pZ3{7D9?$1?~Rda}pN8*S@ zTNdrSQvR;mE=nbH(o8-?xq8(g*OUeWZ!LOCoucCF)BY&hr!mv0!^{Y>BqhZJCbbaT zfOjev;euc;Euw$VPi6#+z~>7lGfoo{LKubA?W`kE?N~CO;odMe6mp(=gd|k?*S60} zmT)%JF(o2PXLu;4DfK6qIg_BY`?HIFre>(Qs>geWmw3ER(k9hv#X5V^^B3MuQ!uSA znkAS1UV^}n=ek`ZN;>^Wsjrb0Q1q?Z{X>(JQf&`NwM~Cvi&O4xAh-2VlwXlKvQ4;c zgAr{f4!69l){E;F1&JbY1YJAFB<@en7>?28qF)XvQ>L*dK6lKz1e&|7^YSa5`B)1+J~U72F0I4Ra0mo zBP3YbOq_qIO;rd`pC_AlBxw(t1(v0;!{$CqbelN#k0an-(k1&YKd1r@g6i<@3 z^tywn@m{~m7!Pt1X`S1|o+w#*&}mj~43~-=or<2r!IvgLvhc-BXdOsNdz__AXYC0< zs4%AA^b;vz+N9vTN4kwg7s&P&@pAx|-YBeFcE}?;RZuv!!z&UP<3n4Lu>fM%TP>RrVsv&VYHVqD? zyEfE%KGXx^I(Xc=(CQ-Q?$Yv%Z5lSFQtyXqU7pV;QC@ntEK`Hch@D;$9?-t)F8T7K*IJ?aY!n*WoX2GuUFN)}bQRLsT0~M&2BT z=%A`rE0{FgW3_~-jJtjzjWB@IL=epctjO{%3e%z%;6-U%o3NJW08oLTfuOl%H;jNJO)X0`9BXA3j> zwcZvB;YIHGLzUV*`hb2q&sHKM{i9X(cUW$nj45u?-tbKWYE3$+-=8R3;Ysl=xh$P7 zroyG;N_H%!3Xrul8d~TV5XMJVL8^cLplOOb&B?gi6HPfP;a$2esJWetBW2{;?8W*) z4A7Okk{d@TP{I!%VnD;R35kbDctv`>@XSEiZ|KFcJYkNo`<1C|P}G=I42i01{s8VQ z*OqA;T^2e__**RXSg>s(g67Qw>iM^Ts&g5UT1Y}k80G#aM~jN>W-EAZUFm;EvODc_ zFAoeSaC%%TqUBE%b)1x;HL&PPj%4K8lWeRI8;T0n8qHxsBF_lflmG*%S2t^!bwHy9 zbJNuXAkx9M2oF?*oSq%_S3<{bM^YjZ?T;7hGlN7GE(O;vc#GNbLfp8Gh)Ihz5#i~G z%o2`eT_+sLIr~v}*o@LWW=(%u43~(%tU(p9K^tvU9}E(WPhi6tH|7)w}^!E__(2fzqp5q*Ep`wqM|e* zOa()+@m<6)#*LaVz$%RLO)^9-0UuSq_+4sfj*=zJO0w*dDEv1-4j6xS!l*ndiS~%0 z=>pt)IM}FJ3{?>^p}4%QrJ`2+KGZT}WfBaFLumkDaE_N-7b&sWU8Iqw zwhhfTDPai=E>P39E%VorE`vdD0URsm#hldxUo>dgFc_GoGpLl#5R^)(e8g2##F?7 z{{Z-dkk+BNc3($ir^zc$T`g_RUBX%SC(1jhr;W)s>Nb@&VyT5>#NzCU3=K?MnpwxAIUR1W;mKTNN5h50cA}u-P z8TlN8Li_m?98G_Tgr_DM34m?!2B+l2O5(VAg^Av=$z*8`#ak0%NoTi`&J^R4b!XM~ z91NNnS**1MPH36Iq6v4`iBYRoWGU2n$%>qvMYm=FLZwa)7RugF0hfF+gEi`4+Lqlb z!z(CNN^G0K>ZkbMC)-X;*oug%souPyblIy5G^x^C6ZC&SsZ!+RT4bt@OLkiYp=C|m z$n`}$wCT?z_d)tuU&Kqw!*v}qv)@8oM9jOzEX;)jCJao>#^*^9IQ+0_34LHY#5gK3FpCaTEUBd!jNG+BAaML0O1&fJ`{@-38l9?Z5@Td8mC{WZq; zdV#|q&ozII6j&8jpAyeS+GZK4JS|GsDQ*!DPVfdD5u)cCN5rzqvJ)1YZUcZr!)YN2 zKnkd_k+I@cA3I=d5$kyKQT`t2t;q$qE?wgjazKR7a(@>LP00i}<8tS7t~B5O0CY$j z5XjV|+~XOkaUP%6Bn-~T8M)a)Z}rR<{{VDCiXeX}%sWi1H=DAoJU$||E+`pAkZMIt z?0$zd<=c{E{5BU%17TSuNXdGDy_))Vsx~(1gqaOq9t9L_vc4E>rbB@=&4CZMw z?n~6Ey`jxV)HbFfzjQfyKN^=YV@KnUKp8AuGrL-QO}%eXw{Px-;>*631+nPUyG!~c zYZ7PLDUS%<#3Trd_p%7e4$>EBix}>W-;c= z=kKV{dsVp!Z^c}NzYSzN2WxK>`G#znpvu$c^y+5T$IN+h`Nm9~CXW!VId5$TM`g9A z_9>@FJ{MfIu}_zrW&JBpk#^USJww~{7U zzvF>?v*K(l2U>F&NJ+I=m5^?*L1TZd1BNXL{{Yr9axmkyu?%HtBsQ@xiRJJE()e0T z!oA|U^2?NG!s{*67TF6opl_sRIoXA7dQPi(NQ|nq>D7r?0tDtLxn#L7 zEW6`=Y*?qqLHfI26S1YeIVl*Tl;pKT4#-!e>H>{SNeMPhKaymqsJiNf_s4%XD*piD z5U8gg`bc5CJB=%DHM5)-RN370oWe9xWXZQpp3Q$+(d(}lbai+ymOQ`Eo}(#Mk=gFp zHY*`HF;k?M)y{6o*Vu7g7f}>>f}&@aK2g~HW)HEZGw;aQ*k97}PF*Bfo2g7pEV~X2 zyROVBzfW+NH73*F$x&hKw>p0-;&fidcGz`^nHr^0=_!c`!fZrm9!Y6A#1s@n$aMox zI8>Fslj|$Zs$c4+i>_^4*bVn z_!fn*H2U=tlDMO?)tLrfV6JHig=m&%L_U_>&U+cYEsGHwAfI>MDQh)uXBSyW=zCZ;0G) zvdvAXTFZ>KKcvf}Wx~qwXX0OfktT&1XSu2(!^dSM9s+V$L$kLumk~Lg3>xINaZG(9UDKAp{{{Y0E_y>lP5cAVaOPh*zHV!|!8)Z$wu@j{cd8T~D zc$2yfe0qRpJw<=*sVpWW46x%)89F6RQc+kC|;Z7#m8a8)Ut7&5q02kP)KgLi4$FN{{UZnXJjo{hsO>JbV_`*xP|l6 zjIdJeJfuXM%=JY~*QPb`nWrs?tWIO8m#bB|pk$vF5=MV4)NRY(-S@+J9EFxNErhd61B&Q^%fc@r&sm0+qsRGQ;8IX5@#l&sw2 zyU~~y?c6>osAb)EKYSSoo#TwYFG&{pEhpF#8%Tc@e_G0?zseviVwxzdwm{pvsdg3C z2s}&|>snm9$DUDf8!jhe8K_GgDtJ3n^AhZkv42kXT>k)8=bEUWd^J<{nsH*--M-e= zz%QgOQ6(SqBMIullzl|__ zh9zcU9%pkf%(bl>1;R`5^IYPU=vLs<6Hf7&g;o-EkB%db8)HSHZt#|+w;3`t~ns&+?a&B(*}S=yt?)C zj8WX0*Gaj(3jH#sa3(5pgIsvWwjKOLw&#~}cQ1Tx$L^IHjOG^E*MWhwR1Y zX0ydJLvJF`axc0Zrp(^wL+lQrljPdL&$@xFuSv?k{G_ZVwU0oTr$lc0zO5+#0NG98 zg8u-DJhocm=i|y}%-tnR=xy3&5A@{vC@R|%txC;3h;aLJRMTihNT(a!ku-ncF1&z; zKflU8f&5j6^*EP411?{DRyL2&b~AX(==8-H3+u~k8#%8(AyjGpA{N;)P%l1eah3F15?DR6H@ZUXjKx;(Pwt65IMC$EB}78{VBt;q?5oWPyD zLR>cG{XOyQ@cu^xs@=MeKKy?S@YJbflfd&R^J4STtnUWvcb5>Sy*s+0d4Dm2JbH~- zzYmstx=gtzO_F$;a$;+E*{Gii5b5O@fBnU;iM4+Q-(kGK$u`KVa|B8`iHSFDs}4`% zEMFiW@LB#MDXg_nJ4RuF_?*l{e{^n-&+(_^Wq-zUQ^`#y>FMZ4RN8;e6=tB+i&GO_ zomy(g5P|S7f4kotAIfw1*?e25P96?eW8;scU(vUlm;=i-i+Iv$2p_?2yp`a0vR8@1d&HCMp6%9kuNKt%k-7GwZZ=Ao2s{{U!lspLNd zG&fiMkG6XUpffq>m56_wqmJ%=pC{S~UC(+e#hsrx;`8%udQCi>N`=w6nQE7!q>Sve z(Y(us%j5<9(Wn0a6m@R3Q<<5KJ+vx6)>0P_vvg=>=`z^iPfUzI*>RYaw=2vbmxq$T z<8Gi?j!4Cnlc&|3B^!9WF`<0IQJqP$sz?f`>fa0;uq8^NDouZxEF4L38yP&$oK*J? znmsQq$D~^hsW*q}2S=*QydJFCF*p^TqLo@aPQlq;(;AVg$kZBh^(HQXKxJL9>@v`G zMkp%S&(e0ug|}4+nHh1HtFR^OEK5}!Pdt=i5sT7pqp8B?dU^i2A|=KMq7N8 z);nsIcYHGlA!~-V+FW02FDS=dHHw7aQk0#kY(o)xUu+tC2I3jXd3*~oyO1UiCf$5V zbxiWpPWX02^&a`mT;4aR{XwvQ(aVK!CID`Mfm>=#noEBO1$4#jwUTN+{QdE-lM5?l zGs?6nsj5>F8!xGpwN||Bm~n6jt=Xr${GohjT7wGGKEU3bI4=myxUSSEXPRtCfAZ_R zEWET>**rWXbZg>g)r&`(mZ?QNHC zGPTwlbww0&??~e3%JY+`ehxlJ%D+=<)G2J#DpM9%+@X5bgb6ONwNH|3^Ne}Cjrk`` zDajY9^4T)THZEi(cL`hf@hl~+B|O+<<1b1Z$R&T-?jEfI@6T8i_85#%cC^C(0BH(k zID%=W$es3&EqTYY{045C9KXn?$fdmXvFfUEMz1||T}U@}W6BC6Vwr>s*CuHOPLV+N zNt!`ah#9vo@kHH5j+uKj)&zsLUb-(*EyuF+5EU*Jhw`&amb_6@8Y<+BoY$N!(SlCr z8cBb*c9zHIR-FLKJQjLl_n>tm#g(%zsL>8xOm0Y1Skn$kliWGxuwvLljl?Yk4-Y7| zExo|JBi=Bl6zD|MWx>m#6FV{ytuvGP_kT#sELwI6*~i&~Mjx#U7P5yc28pvOy~z=n z(?bpc5JW3bHzxQsh)qG_X5q;oY*?Pez2koYxD!!~^o`Bu;wTnzBqa4J=Lt}4x(4Ez z+B}N%^(}pvC~zePpH_SU4+Te(@*Cd|6a=u;fyzjw-xtLOY>Yz6PR8@<{{V&+a0G7q zlidv)sR+wXqTm-j% z)G`#z>Of6<`im|vbUK1a>7%bHctTVt~Tisd*Pv9L9o$3N zt@}}=B=!hJ!K`dZ8e~f7x`2PiH=W(kmQs&zJ~$|10hyc@z>})jOURj$Ey=5%%_BBI zEnxEVS83veOy~!&ahgyZ;;bcEj5#r`eNY!0GoVgPd|&AxU4V$$lyM1jLX5tkP>M%w z0~4+dz;$Y^)|{-VZp-6c6ZgjN$0w4n7K7Gb!8zx_D)J>z|AyiluVlp%goDvnuIl)U51Xp$$F9}81f*pM`$cv}=QX-Wd zfW0+wm&L_w5ju#9gwcN_$fEG2-@YJKDp;bECyEAYR(;D*iMQIgxJbj0pSgKM@ji`Q zc~Z5BDKhhzwqCUner=->PE!N#nw@gYA1}kQ%8?L`#v9(0Jlz~$mfT{_i)4#)`kkMTf&3;K2UJM z*X$@}6oWJ)8=&ecP*Y??=CNv_Dri`V70K1@K+kfFbp4SZ3{u>lwC!Q=4>l-D`9(*5!DB`U_=}@LlkUOtj#p8RP*8?Z#$sG z>I%Ltrkv35__({G8j(spVuxFr3uWFb3#3Lb)Ns+(2@DUh+ND}?w`W|mE<(79;?5NA zF|vJ4;fHXq)$(n$JcModqcqNpPc1%2SWxhzUXZ?vRQZ2dG7_xe8>L@hc#6W+4HL^g zad=g%UmqgJNzIi?3-oFCrKVdZ-ei&;O>aZE!g(?k6+yr~jD{Ia(fA%%j?(m%BR}alIh`5+EMf-n1-M^#1Yy_U82#FaA-e))iy7wn~ z!TMoMW8@9bqStLTChi^LUuOmG!R|!hXw$-WSo~!pm7l_bb zuD?@g;(tb5AB^M$n*77HDUlmWXh;}0OWRZ(A(6yrE%^#k&*=XE*+A%X6OJyoZzhz7 zKXO6G5KQutLQ8rH?0Qd9W?-($>!zLJ{rP`HYzi9Y95d)|u~~EVlvbt$a_V^P&-01; zRr`SXhiv*V>^hq5$%SdICs5wn7xjwt7wimjo9ar_c4M*nDv-+H`UvtXN>m7cd|#%4 zFC~daTiN|A%W;Nj18}Dh%D2V)pj#Fq+mg^1`(=eB-1T%iXxr23K!1`!(-aE*Lr8y5 z_Ps`x0!FEFq9v8WBAih#iUDV75F2)s6v>%ZhTn$RwPgZb;1k}E8?w7|FZ(w{^%Ri9 zcReB7^AzS4idex$yUquXYjGtC}2wdItw8@aXFw*yy?@RSVl zT1Yo@YjY*d;~TtJLcLc7*}f;6+LPp?>yL|wp#K0+3adz<+Eb;Xxy5g_0NGe%MuwAH zNOVrYa_?p?>=#(xV`v8P@R5p?EpuoZY+~j;b-(Wm*@#muK!0vs6xGO`e-VF?6&wEm zYfJs*yk7XLeT#Hh2mP!v)SUgIiv@A)OPI3M&gfm7V^#!?Dk5xnxM6}^!`Qad(4q_n z{js#NCqHH(b&s&G?T`-^@*zuc1&y+SsU|+a3mIK-secp^ds#3zzd}SSekg?&Y$J+5 zrdvXDsJfvvayRZb{>pK)Au@j}cM!(>CE`^(17-VYrcGgYCFU6i3dG^H1WA0?&K%V? z@t+hL!)vn=Pge=qHvod`xOhZt9IEqR6ayLZ;m~j&@Ow*rEag#D>tfyCjpM56>{Z?}4yL$0;vOys(tkaxo z$CGQ+V&txxq=PMwvkg`-yb^MSb-8e?G*c*XbcW$hn7a{_cZXjksZ0AJXkQL#6cQUZ zZW1XkiD@$;Ggs6pY?XiSjiN}3Nx}YcpN#(i%{p2A@f6v{dnx2xR^~iCImb}qzj2e# zD4uB$Tg8M6WLjO*5KRXSaG7Vy*o0LvQY8rovf|;0GRk&l0eL3&Z%K759ptNbenb1C zo14t$*!j2VUAOFCv%g84aZRMWQu5OEXDlF>ZtS>KHMcnFaWj84Pe;a$;#(S{7J5K! z0~owc&DN@qAr;@Ov~&BTTDwPx?@3Ce)-uI41x1l^!rS}dmuUmh#91~sTEsHA z@|N_CLRFW=@o-7kEfLpJ&$U}1Y^Z&8VF{d0|66AkwJldmXO}N#}*_RM4OW<~ry|nF7qZx`s2DY(Z8Y-I*7E#pe(ryIy!pi}1t=7}h&*WL(0ns3x~9 z-xOzaYI~gPJmkpD5(fy0K`SYtdP2*b<(ZLi!Sst}BWH-hb0QsmsB)G{Q*Iz07&{ik zkmtC|;aGog3{@&rhgNWj2ZrU0Q#uHDEZ*&rLVyNfOM|Ad2@*Y%{Y)?-py_cQjO z)U_Ln*DkP8+`5=N{`eX91Pc9_s*qbRD@PWi3yu%1Z`lx1;uw$aI}_;G7-nFcG0Ugn z^MpDb6msoGtT^YYVUls-8@|jfu^0M{5|2~@PE~)ho_g!E8$`HsgtKTwzQ_uOJa@G? zlI|FVyg>oDD!)*aoKCY`6Al?CguR$ks8uCo^PFcT;W5h6EaN--5t~Gq&IH3E)8%W! z(pK>06&=He9eqdxC%+efjj}-|y`+M}=0%O0Ckc$5WMHo- zVCjF1rby(Z#+K`2cM#ex!d*54s@T$)vkFwOZA)=AF6(?;C2tHOJ<96@-XJ#Z6CR&v zp_#ft~ysq8yBzLJ`DT*oU!No|5+;&yKQ7wK%(a+EZDV^5c=NIAoE<9(kc?to?A zi1g&Q$bRO<>6L>jrp~LKjq>9vYwmiY_)&jhO#AXs8z0L9RBtb}DwYYT4L=prW+`vC zkc`E*FiGua3w%5A8IL0;PEzJLvR=6$E;ZR+5gr#>%akUpzDj6gQEdWg-KT8=M-EGo zl(3fmaJ(V|-S@+=`+qfr?W`!EbW8(}5qEn8Q@yfJ?)1gIx3a^y~MEz}fF!^*Nk87~Z6p=!ghx>PP+ zU0-|!K+7bAn}alBtHW>1V(JE}Iu<}}K`VSPK^}0CgDwbI#F93AN@NIbF)wN$OuHON zx)SW>w&Wues%MxG)lesQY6ZYO{upZ`K_X8~OdF??aSTIBe&M{$NLdRHhv9#=4Io}f z8o5wX;Izwye#|jcN@OEOnDES#ml&aOTj79nMfPRp#GA~Ah7zHRr^|%8T%1Vf@WzD# zXJTq1t-M8b*MAHelmkD%R!&S@WR*PNYA%8vWC~6f1voB7vy*U|F9ng*38IF17?vAemiG02$E_Wdl5!Ra>*d8O=1u8|VW*wT{HYhg` zt8ItLRX?12em*@PE&QJ+ZRB*l2heW0R+DY2jO2wq1QUcwTyQ@x^M^Aj#lFphLiAXW zz_ihpI`sR@xF{|OWH*2R061G@(M-DsH?WKX+azzvyD&mlk#D$!zDTJH_1IRVVgASV zP|WW2X>1dXiin=@?#X%gz_OA%jb}CqvMf}j(NSetdAx{U7ZtY$_vaVsT)wiAi9HQ= zM>_QINsUrf$)ZtzvXZ&IUu%-2<%48cxWMy7*T)0kXlZ9hi<5(xE#loSFFrEu*Cn}V=F&_g& z1#jGtd(j~pO>K_7G%)=X$(j_qwMo5r&oF3L`od>0X~$_aZqT;*iuQ=h%}&n~EVy5& zYlJfUzpQ6`%_e{BJXMb}nDn@ANahOCaX1L`E0zY%Z1ldTq~%GLZ7~5c$cEDH`=FKB zKE(Ye8&>e~x7g|xNigbYf#ng)-52XT32bX6iH}JPb4j(auTH#TR*2e!S^08{UQWS& zCNHS=SJE43m5FW0RwfE%!%-n>T?Jzf=a}Vvm0!Vw781#E8m_^=4F@G#j0B zbQyj5EFt+X0@-jubD`{;5ca`-zly4p4hdXLF!GR|Uib&){aDF47wTOfmu0qe-jml` zX=X!gWM^qJ%!(DM#fhQxk`WXZyLT-#FUIlzW)GcRPTyqsiPE? zOkuIPJVf)3Om=Y+D~JMI1he_11VuwuAnG2A_fsZS>;lgw0zvqEXs&>S56m%cY*<|~6dW;7<$JqQ%H&D8Mh#jWRY?ofyG^7q5x z))TP}k zO5z|d=@}pKzfqpEL29AxDo%UW)3F5Ph!=kj;IilP@A<}O<7H1Fv6E%L4ec00TXQ!! zQx2C)^*X=Rc(gP4^*U^Cf!3JXuV;4{Lx`?i|kb8a@m0K^?BL%0=d~` z%Tou~c`eO3re1KYxYj&EjN0-nGuVo9pc7RG3Tmv3w6?W!Tkgpdnnq9BMk>2K^rnB9 zn#Ro2YPeo1~K`g(RtmH7fBq8VYTuS?2QrX8+5OB>3YHGQIm)+y(jdBt(@Hb%FQN}WW! z-_i4i{VFRA*YK4#YXsG*gs+IrPbh!Lx(bf#S!szm#_2UGY?~hj}iGCASCdYMWSkdb3vD48+Fn z+Kj-f?~3RBR63KLY7qGPd(b_ zu90fn@*-h26+a&vbf*Gk>Gprwn5L*Sqb`!IilOP+Tgx?0E*BH?i?YFfn@@=Wh;gLO z&uh~X(x0SH`jaV%a^JqwY6F*pgZ@lBBnA`Q#Ba|QZNS>~J8NA-V!-Jdjz^xVp* zTPZb(x}|bW%4;sNr0H%3T!Elzv_VDP8TF@_uP96)rb5?2uS?V9CHpMDu5MY-A{nO* zvNAlqUA&ch+FceJ&#sjzS6wE^rM%0GveV7_(FVjUmpgp&$$5L*U3=5I5t0c< zM`9xoINOZw`xw10PB}1J-Qf$X-LH0C&VJW?PU7WC<09yU;W{%yOt2(hqN8JZFZY@E z`8ql*5-BWN>Fxs~nz(z`&9HQd=EzBM&s?)i{E0O4?}kd1Vp4xrRHVS3bA^|QT@upr z*Cf$7Kui}bF>%J&+I1FK*>&I-U7R3ju5@f~ASDd5O-dQVUy*9tVm4xCXzEWoo7aF{ zS5L%QBKQ1~IXNGBPKvKc(qSi=WLtT+TM2S4-OeFg*{$K4cWDQG4(fsRCP|vL%^S@L z>($d6_}Q=IDb#;8R|>LEOS}xpSu32Y;91&2Xex@Cd*PojkygGY8>Gw2?nAT6lP!&) z7TP&{`RNU>P?*Mps-_@s2-2C!yIvDXfeUUO*e*&w{R@-_VH}#2{6n{9skOe$ByTb# zOcMjb;OIsItH~8pOp{n`bRd+MX_d1UweNsE~hRIlHHmvSuR?tuS(~X zX^t79IE+NqeI&Owzd1?l^0MHfMn!XE!7hZJYTEZfeu%i@5VfyLnK`K@#CJkdMl!GR z6KV0)w~=z>NzNW-io>Ln*W2h*Q&8f*UmXjW_thG|Ad zsN%7Rr!ar*66T)c-<0D{brVk}k4vx3HHu#m*i!66$>L{ePdNL`wB(bLsbCglU7f|( zEW5#c`9mUlPQqd7Ei}4ZqD(gHbuIM6d@qxvgLA-_v}l^>NvE2?Ha&*6BIhVm13t{m ztiU!i1-%xo-HT0{@rrvEnEc>-5OH+ zetDKxdnh1GiCbuO67uAUmpCPT6I&B2I^5HQh$bZ2T3}sJH2DKjg>r)kUExwR1p}x21=Zj{hDLWQ)XyWg7mozL2R` zkd>!Yr9$~KLf(#dY5e0CBaQMLuCL}?O0csNC0CVm^ogmY7CDIrc8lE^@w*@`L!x#i zwV7;HK@h4u67|X{?u719->1zx<7_w7h?X{$Cu}oSN!DfGarvoj>R3*3ZWVO-4cJ7hDP=?f6Fw}vy}@j984YIF|7Dk9BbQ`!_LZFaR3snb${^xt6L?a$HC zkDC_vj!VgQ!Qt<1Ma@5EJij^BGpjJCP4u5I{{Zzj=!liYxGqkjd%NR~ z@^*N9e?fYHhSQ!W1d-qqw1Dm{*pZI8eS-)%Lv$+19?mK`%BZ%}lT(h7h=_j(t;B_> zWS_bb81~grlg7oz6>QHL&}i+>trgJ%$enL^g!qTFt)k|Lk#odoVLz? z9mJGo>Qw*}YD-eJJ~PiCUvj}^;%np&>h$y(7Rcu%WZMy~Q0FQyaEyZ!2u_mx`DY6K z8^B+!+f|K`f^h9hgM@~OT$O*{Z;Ie(TnAhh{zINo$mj90!(7A2be|HxiuOh_a?dR` zYI0s+b1|2Ect}6aG2!QD#@%5ieJSmi?E$<*bC_bcCNoCcNK)=T-w>yG{{UFs$g{1N z$SkUowo-?VXe{jZu{}>{TI}-zO*>%PF1B76J|=HbYQr$PJ#xuT`Hu1gwmes3%<`-*H$@hH_#{!9IB%2dS718Y*V zz}PFMGZgT+LoD;rQZ-`y)_M5H`ixFHe2qU^{{ZCh;-=&T$7qxhJqWk7^ARLpCAu8H zns2ZDP1t`Num1oO{-67m9i@Nres-aSz+9tZY%hoaaw5ErOou-%q*!c!es8O*AN@zq z;+i}g{)aQ|pU|slzK!*VC9z%^nv)jB;h2}oW>AyO934MAqh2q|V0NvlWzEFPkK$^I zfbO0pj-Dymp5IdSvz@3dHdtjJdfnbu6db^mW4Qi|ExRRK64tPU+~GMbR*-bq63B-% zK}Afx_d>I#@pUP)7g5-MUqvUR7~y)AV`sIF&o4iRX9fh2mr)lMP?t>+ zRbbHja@v(|V$?Y~?%e57CMQ~#ngi)+4QvDZmN|Tv%WohZcGWh2uE=C(*~1f$%3=-J zPazxaK1d?y5_KZSQ>wKCGHayJ9-JE|X8>&6XJw{4bw+7YV|woq3TLP%-K=x7W%Qn= zWQug0Y*i;lXr~%*EY-fGk4wf)t%J%XsToAJTF7e=YLI$<_p^s zH1Lt5IWPM>o$n3f{o<8 z>XzrEayguB^&4IE);b5#*Bmjlt%zXS;x_3|wKvDY*bLM|&rzaKUGqaS9l1Vu^arzZ))m)x^*+BaH%S$xY?$`Z}hVAP1mO|yuw z1k|v}%eOfyBQPccyTU+SdGpYn)7@w7w%tzW0=U6&n3Vo9r`i{{ZHLr~_x7 zRdXmL6yio}k0s{}051FA`{K0)>O;0&>l08CRB+p#P&$y+ly=*jojsVKhfKZS1u$BV zWoA6!7*O_qUS`zN06Ns=7Fyl-WAlxQsNu1=GKKntb_&aPtUVZAk8b?9q2y)Q7YEj6 zzr`Aq2|y%Pu1f?ij0m2$RfrolhR;lQMN?q6xYl8tmoI7s;08^RWTXs(QIn<)~a z+suB{C1XQWL2$VapK+U$7Tv9|9cA6cZV5nhgHqLhg*PrvE=v)gL}m>yCR~(wV8V5>l!7p|It40OY;gp?4|T!#Rd+KeG%4l#iU3 zxJ%+Fkm6slSE)2sWp7vFhpPx>AZKO!u7kXQL{AvBdiY?KL1o#QCkFEI6fDf@SSX!& z#(auqa^X!YbY86^6wZi*t115gEGB!9m61DtZQiJhr>2Nq$arDO3K<%W%b)((I}Hobr=bam#HJ?r?;0X_@HcLd}JKs#eQBAgEO+|Vl!8+uvH2W!(>kws(5{_7^WP5 ztD)q(gh$mB%u5pzrQW_M&i>#u=$EFsZMk&9hMCA+CfmUK_SBx@G#RFl< z?mT4>boikT`wVi!mgy2FWDeQg_F!yX^emj$NIQqUh|hyKb@mm6TrdqfBLaV2d@!b6 ziebd7q}mQ;4nzhcL-~1&by(htYfj^T#^)}!;^DR(5wM3%yqEh?swZGDTe$OBF66O- zE;t5l1!{wF%XRTWZdHa8&t`d#6e?#z>PH*J-3fLjOtUo^U4?aghm6cz9aj7>48=+c zDT|LaifPdV?>*3l_al3RZRE}pz}TdEP&j3<+M+f@H%!Z+TjjjgJAZ=y-E1;{$1k}t z+`J_pS40vrsaFJ8 z!*OwG3Co(kAhvBOyztH&Nd}p0ctBFybcA>EG50#Mn}=HThV)mawJU3XmFynE$;>f# ztS0$^rAY??GO0Bx&zThj;7A>dgba#`nW z4F!o81B8fxe-aEHptUe_%!+2OG(TvP0@m>j;PBRDL!8vF{{VbThvAB|3#)@lw6mQL zUG_%r5wXtBr?DQL3|AGexgVad7Ub?w|4)H3_h= z{hgfRse{R^BZ{0-%G+sf+poVUidUq>te>O4(JFu^ErIa~32_M=zVG)e5LDmoB_w?f zHmzD__OML*VW$)CHsAADOkrsgFtPG8HRuJl8KWdzA_4`(BvXxl-T6bK8$lFU{TjPt zo&iaGGaxs06ZHne{NUpoK_S!`{SzB#t#F)<8Iv72ovK_vxK<0%u+Ldwnxg#;+e+;@ zPGSrZB05|`L*9f%vxeJ~veM4Nh z3sooRrID9!DZQkBJ9qy8Ytq1OU%a5dN3y?Ag~MjQN9`YE_2H?=Z7UMX&5u~i%d`Oc zdnTDKX$HJDfE$vt|$! zSu0zZ`*kM=`9oujTXIJ&`V8zno}524T)OHGkmd5uBH$~3*B(mxBkU@k;Qs*i6Hb`4 zZT0Uu<=@2z`c?at`n`N$eBQuzCm%HVu8 zFZLu`AL$1eln>N6{$8zs>3Xq-X$^HI{2IWS{SM( zv6|g|hU}cMtwO0?>h)C==dlvjrQ5p@AK{<*M zd3&@rPiw z2(B-ShghqFjt#$tc9h_NlDtE2#FoW>`qdw)Cx5pEQr7E>_@h2-gRw?~((8m%BgNGm z?9w_T>{#ULHt{Jyt7y28vy+1NiXO9|(oHMrkxyo&v6rT8_0*ZhW&pfGndGDQp+;9t zn(*bLA2YV3L|Fc);7iN0+m4}t2Tn?Yx_@7MUKAP1_v#{9WS%_)dZGP}c=QT?)z-l; z;fjeVnDRU>TC3I52iO#0-5tVaq{^t6h`9z5skL_Mo9Ub~^1O2jtl{90MQbI*h;8EK7OF{a+Dc<8!?TrvoGF5sc&#n(h?OGUv|1hS=(D#8Tw$?)4%I&A9H?4W zg&` z_V64_d=NFUB5Gf~73TYY9@{A^!RrWszVv~f!t(o_s8S?AN8%#=l!O=K>jq21~`tETSH{ zEp1CQ@nsq@PKblLdx(fAF`zEww(Uzn>V^TRBI*Oj(-<@9OaRwvy@QAV_;*<6D@{o!m`e9Df2)svSc>gih~J$}iITq4a^oH=y{M{^J%@77J7Ak-#63_;>>0E+Ur_AGB$jc1INY7ZmmQ&2 zA8!Yg)SH4Xw&D?~aSln&Gvpkmn8sLu_KDf$p0%4xh{SwITW^ zTqVN_@8lA;jkAXRCxWdYG~7=oFIJT-hUZ56Q~9J@3|!x0%HG20*rr&gaF!sN!|5@^ zGUw|UCHby@spSc5-(Z)HuC~R~{{SKaUj=`#+&5I3oSGTxl))(!!1;FyK)!4AsLVLM zHM4ab$s;s)n;SV$n{&U#c!mxBEz)pfC^a628fO@Dn#F zYwW;E2CN7i3&cOe3oWWFnQmAw@fX7mv7Tc?J>X0&$$wW&DVD<#zQdMVWbNX#yimkK z-Sy%tbS<8C@pVB;3-t#g=V~!Q@WuBP3^{UC1T#)6c&#WyMu#ly_V|?r0B!uDFjL%8 zR1|c7SQcW1Gcmk7eXoiMM^wzjzXn6XMg3w}TDS?RGxcNC7V$-@>=7)o4cdudj4^_X z4kgq#WPLu=BshHmahB$syN2;E+J>)5AKYm4y37#(jmj|VfVi3l^LMg2mmw7xuH)i1 zB_~-KJlEMs7(@(Z+5xo`Z}7n?=vWDff*X*3isJr}E)_=7?;OLW{7_5o5h~2&#H^jd zTqWU)>}F@!sa8s1k$HHOdW5{9jY&Er*>Gk6a{mC~fYh2&Y7TqVrf6I~D52{66(=}^ z#)ERX34Y?7n4`RMvKA2A#SBFUxg#ptyc@>^Ae-2`R9*X&As#3eG_Sakk?k6<7Z*=| z$_0~R!!{eU6(O`osspg(>`b$C;0ue=KGaiA$K!n#+%HXjcm>|h>urdq5ekTsdr+xC z-m<~4ur3{_;!_qV@^cjlrwY+(UAwMdpC~*$9JzcBe=G7Bv3WEb^JV`4Mn*E)MM0Ob zH1k+)ltP4UG*?Wzo?hte@xKMGtf!WL=6@Dqg{}p$)!Pm98`-A8WCbahc2;V1xSgGx z9qX>IJk?e^`0?VzwN*T4E1#c`)@C-+%ri`qwpq5B2K*o>A~AC2HBGUvvF6zvqRTj~ zZiw!*i1WFQo+R#APpUF8ZH+Os4@$}UT(rV9Fa)KM0SkOX@b|)5_GnBdmYmyvHqy4C ziQJRIl-i`=7DVf%p6RaZugjb_9pudTRXmNGN!cA0DooRMiGi@YjetwlD^E5&Bz~!)Pj-66w5k#~%_FI=OAXA(wM@fwxjVQ+e&V_O zvWoS#HaI#e*pt#@7fqYO%<~d|C?&+yt7sZ~mz+k)pyPHlb0NapAj- zM;KR)2w_*hTZKCDDGWw$Ww%d?k{$5dywd089nCZ!rh-&7f|Qng||^5MK;LG zg|0a1D~rTM;U4Is*l{@`jxr_>Z<%CJis9)z^n;ZI#_i)|OnA8o4uT_p@qN)sTZVbZ zq%CdRNE;hQ(pyD6(N5%q1t&1fuI;v*5hn`Z_J=61=qbI+X3F-hH?#V{)fks);B$e{ zcRO-b>-oVjrp11fqi{~tsar*+w)C9TsGFHy!8n%=URlN`!rv1fOcX5&$&1%3Htzl# zwSc6VXSm#{_bbFAc6&sBHi*U6q@dl}SUqs}2=xf}!kKmj22Wx2ZCoHC;xDv6=Mh?< zvW;dbu1SM;4aDaVh{4*zr8YErpExEateYWf+Fl`v3>9ilQgQWGipaDtZ-c(^QjIao zU{@hEP1^4H90{3G8+<{Xv7_~aSKM_gw1996DR#KJNnGKH`oiLWHa4G_maiE&Z!|9n zApWw^1ckb;Ls~MZ0;7XAiacCiA#&j;MDg?=O!$j$4VW``Xdx2mBI*@@elZmk1wxNF z@DrY@W8&#>k^!)^1js~;h&=G`3D(qlhj(_L)-!e_cL7$S0Vy)W+r)5qd^-Em1%Ou9 zkj9GG)BQq_i`QO%B1LR##U|9rvT?choNT*OEm%Xv;@_vv9SlcRlU40|(cX`=U?7tw zD05?<6GPOi=j9XD%N`&u6*(_&-h+6T4ur(>(ydk{2QlqZ{6&&{5Qog)AYU5)0Q0fP zyFm17lc+rMq`$4yNQcAoNVU!TggQ6K(RlmORGBx0iGB5d!nW}A!LDVKAEeS~ylLph za&I}CWKQdMhBji&^#Z?RSK>{O%+emsmfiTR8)1u=VXIVyo^rE84cfTvgTrZY4pDxN zDGo9?;*Ygy`V)E%?RrRu#F3vT4P=v%%Z?e_w6nn4&0fpR_{WHUFF4Hn4S0j=7pxz) zT9b2)zb#UK7o_zP4cG}~^G#7>9$qKdYGpR14;N2YDVxbTf_ai8y4LuByR(cLIt>du z@)}BdLrLM>XW3n5sL8XCl+jEmxA|oeF*7ZcLoUsUT9d>0!hh>o`U?}24q8J$E=VRw zx}tY#siMO1Gh$$&6${=l^rou4g=zFsdXY(^%F3jFn-Z^*i;lKBTY4^fCZ70KX!Tes zmK-Nol*Ah`dXq$?;mTb?ULx{>67@yJPL3aXL`F-s zI09_Dyt-wpSEwB2_bdgLd`UA>v!z?@un1_7~z#QKu60k+K`| zP1(kkiVeM$x(r~)d+`7cJ&Oa-MuOAa-VHxRpS!P|_(#v>% z4iFiM%(VIYDxcjspDypZF10ytV(Ub!&QjT$l9`yDW#^b_q6gb;fE6uJUIa}2;_7|z zRoE4B#U)alZhh%#%7ac!jN^OLIfHa~Q~S$PRPuE-{)uZzNL_IKV(Bt$hj?=W;a()7 zLz@2o0G}oK#%zi9HbWA)k?*VC5f`0*dUJZ&2?WB}Sq7f;RP$Ya=*yR&I~D4r5HQ7u>duulH~HoM~St2s`^efhR!=9}!KeW0Cbpt4_pvg_SA=3BM%X~g+y zly2kJ1v3ulZcoad?b(Tb%u`OfBXdm?875ZQt%|6N?vmw*Ph(F+NreVU@R38YURyy^7(5Iz`DfD zYR1H4FU+q3k&#Ujuvd7Lw9Y*GT3W|j91Ue@&XJRIvja6S&TMZ)t+@x5pD1UduHHrE zoem0I2TyLv~r{@7N>901y`s5k{zg{6ZtF+O}?^oYA!Rca#Q3GGtA) z4rjyFCx;Rff;LtvsGeE2e$W3+%l>?hUW8>V(}kk2x(lF*M`#3tE^n&(u7Yt50X|<(vjd zTA4EOwp^*L&te&;rDlYG25xHOJGe=Sw$Qxv$kH;8Omy)dVEjSY`#X#_GXrA2BQiFS zwrD{x(kk=Gc|?!eI<_Ck;I)v%LoTm0GXx!+P0~TMlZ}HVUMbAXY_03>atPMtZnu_y)@f(vSgFUw z=ZIT#a++|7CBNx^Vp_n&0;m@3<;`0r-IvI%EV+AMC>=mW&ko|rEL}}zRX0u48!jOs z=??Wp?~FX0d+{@6R)V4F4YJv7q~JQ05^`Oqtra#8sE6T2_s1iUW^Z*qH2P(FAf&A1 z^YSucFE;55phMG?b$C3!kvh@C8%Eh`(4KQP&ilBn>5oHyk1?FrQ?f5m=690U2?=#{ z#+9A<5ZHSq+N9+3((=S0^RjuJj`W)S{G&E(DmGtb)|ZPaGV_eoSrQYNno_iV+T{vG zpm)*GxJPY8ikWVDR7OT{T1KCj9w8+U8hqk9T+pr8W41O2UJ2=Wa{|&~p+B{nMpTV? z9T$F^9SyR7dG60!m;I9XpqHpx8w1i~Y1k_W;AZ6UXJD!~G}D6pE!O~r_S7{+NZ@`; z9w+iI8nus|cy6JxZHLUyJ+s^khp07%T`7rq*;a0uCKYo~L+7FvNS;w?$;&H4t;fQ+ zjg5i!#XXBB#T$EgZ)ZPFbhGu~pR8q5%}_~V(HdBP(CVses!j*RQxp-!5>z_OttL)k zCBkUgvgucoBL!9GYL_T$Vq`J4bw;g?iL#lhPRQAEYZ1tXD5=PT1f0FV;{wWP4?E8_ zMA1grJ16cN z&Q_;?={Ta}sJy{_%L;6b_8OQM;sB})&xUsiD}5u>McwZ2L$8`Z?gCX&))CreR|n#z>Jmh5JkJB7=N9}u@V3phs1L(e={K$m z#?pAnmn#ytePCIOxA7AT%S4V3Bh|##Iazpr-k(XDg)oMF>WqUj?^ZV(fqr4MA_VWt z%gPyt@{bGDCL*H}q0(`s9&(*iqsz(E$Zd7=Wn8#E1^LFm)ndlK$|qDFECDNp@G~^l zCFB`mOpt;08&Y-k_|iJ&bCPDq)5 zLS?XcndPn2MOC(EA^obTO4DjzRLE|`2Hf3tb3GR|T|RKs!-4#ILAHR;Y}R^8kG`I+ zNeQ+~Cm_QO-UNANnXhyc@b~fuU%0yL0|!sw89IcuZfU8>n#N_iXt$PzSuZZ=?*2E9 zv4NGmjaA+viM32CP^QQ@+}y0J)RTFC%gIWCAxx4ks97ech_S}cgDx>p%^oI!+BFWb zf#(Uji*rqj%9e|*sFO&ko?iG)Jj|Ueb@;r^c^c1Y%O{I)Z{Z^Dm&^wBN^w8MpZ4+ZP%2GCB)a1IG z7WE5>tA8a$j&I~~HEyEvTgc;YLXSu)!PY=mOXYP zSvVP!W!9IC9fc8_qB&M#lQ2xQ$;q30#+~68 z*EG)bfy;wudO0^!r2Q6uag@sKJ7|NsQcNcNe&$kzQF`DevwC_eVc031Skk#_$LobH#PQHr&5$by!pT5osPnOgmnc zq0G4R%!cDu-?&srkdvo1)->>)O*pFkCal}O6O+RDs9dJrG}?oITj_sfQF)AePr`Dy z(#HexTy@9dP~1D5k4>mJ-6MC4T%t!{0fWA9#RkC}de|FOJ;>#^I%0|(HuieJv3rrj zZ$L4295XQo)=81vyv5KAMIEy+QDm7pnQko)%gQw) z6w#v&x7>poB6I_P4CdS2u8`P}9!U3(y&=E=y5rU%1dt;As9TJ2xl8X8r}kkCCdZ0b zcg${tvV=E!Uf864j1?-)u;MC?Mo)dna)dPN#9(%0G!eI024*1aWaB8YM9sHF?n|EI z3CVrlkh>Y`Ma)VFo_;8`0hNRdsylK+cLbo?VnsQi!rF9yO;iun`^^x1qDzFn<{YNm z$|NzR7-un0QB1A-QKeYB3?7lQr<4_kDxMiWIAzEfWc$_z?oGM$w*BtyP;A`9`#x1E?!CON8_&Y)-&s0Zt-2ReE7;=6Cll zICfWgZ?ai*#!S*F(7Dp&(!pLh&INQo20M~7QfuLvx*M;A>MUwFF4eHrKFf;%a>Ro$}LD-OqK@032?0l zQ#%(bM@=2vygWagGq{IRE^CV9UL=MonUG}R5dQ$h4;Na7Zo;=NpW1+A8xlKX)$)ZG zs3fFZTgN_d6#=eH(_RujA?fLfC@`Ftc{z+{WGR%Fl$yvgUaw{sC{mAEoP^4U8Ao#O z4>aX}38-U+W)XBx6i~3j?ZKVV24XN}pUNDop}UYSKayzz_5#3OdhJpU7!cCrqs*bn z?m0(ox!)Txu7*@{%fdP-LsTKmv(~{7c-+O@;L*Oqqp{+9$}PiSZ#{Tv)d;90eAAX_ zoZ%T8K+KH^%a7WOZsf3y;$^Q4E!}}|p!K|ex}h;T7jee*9nuM$1w1zns{pJ>P_6pN z2Jsz2pXnJcvs)9c3V(JjyZIQbb^ibk`sA(3Q~G+esd$V-SwdPx=$XE!H* z?Ktz(qtHdIp-IeQDg?O8)Y}y^Xa>?RJv-pi(rH&$@;D}YWH{DNs!~IOacsf1R$j#| z*8sl!vWjFgvecQS`Y3Hb6LCsx{{RsG0Q3Qb%Zi7|XS` z7L|@L4VS{HyVW8$8~#AHd{ydF-;KQ$wqcKqU93pWOibIv=smbB{P=Q*ew?4HLbogE zBe5AiRJx;|2~?f5zkD;$P=8UNtLR;^89P(^GHzSbeB-wN063n4g&EXv&!XPLWZTR8 z3rKkVw@A;2?^wS{MYktjBk3J~jcjs`1KIX}hKAmorBI9R&pe=Luqt!xkdtp`7=MOS z2jQu5>Ki0mAL$oGpECadP|@-w2WVJ%o)&2Ii+z!kZ~UTD5KYQqf9+yQ!2ywz5O1VK zkP#o<1*L+YJ4u4uLdxpfZ8n`e0)rEzRApdAw(Z?N6XvCNU&u0>yBuHO->n_-$}@6s*&N-=jP+w35=$udpcvmeD1)X=qW+&6P< zQ!WO5qU*mDEwYS>u-0R1j|d9yi}s>MVnoA zXcL=FdCqPp?L@f6MgabQ(hS#EAbmhOfIYNl1EtGgm5UloZ4k#oc;QmxC`Qa}IN2Q4 z?i;vxmKQn2PhqRuN_3vg$oP18ONuMkV(jJ%Xlh*CKA4e^JFq#7TITOGc({kB*@jpc z_ZdBl2!n~uB3;U&h;tNs1^L8hVbuDKzqG`M)x!c)A%VpiFINwL#R1hK>LhVOmnSWW zVM50eTAH{e`9&|ep;KT_5|KDLmkcme_6C@ocu`CEpd~=;3G7X~(Mk#6y_=n^|+P>j0dBzPGVsb>e3px$x|QN!E7`xA@-swl2bsQ z#oBw~DuW%24lYK2?nQDA5NyhUELuI~{{U#I#wY*Q~$v8enYJ{U`TnpI0Q zkqc%y7Z>ctY^pxQ^*-k21vu`E%tb8{^J`$DekgJbutz40gG=~e=di^!-=tH0vt#w( zQqyps%wZ4IH9We`0Q8%{Gc@|GKCsNKwCRa9S|>*&&WDT&6_eWdE@$_OQ6X6=GZoT6bl zv~8x@a!FWl!>Q91(ND`rP8HTuqWyS^G{BZ+mxek=6^P<-FU!I0L=xz-5>?BCCE?=g z_+krhV4_6cgJyoux*%&%t`>5{snrlD?sd0+qiGb@!RVrwWGq|xM>~<`Wy<=>eAoI@ zrCg-?Ue<_Q(t1u&^0=1l_hO6w7}3LSld<^K{!z}f_S&A7c)Mh{lYKx%#9t`gxi{2m z<1F8jx&HtZE+;_-WMxD+h|BE7u?OzF6VhT0iQ3<^T~H)0I}O^T_64fBPC0}6x}ZpZ zCO#2gIMItUK(|kloe{MPh3{pfQ=`_cxb z_9^wLa_x?1?Lrtin*eRSH636GCZx`P`JWFAF_?lVWoym{z@q}7sdDspnC$y$pykiB zAlzLr6WA=i%6E&Bq}XNniV$-ec0ghl$r&V0BD?lt22-j8x9VI!)*!SXwuwzi;5AK*xZV8?jo1m&E@RCBMwh-wKikb<j8V3LIV@YDzHkE4;~Iqp=5)zo^%o8kEdb zu{Zw!TwMF0y9EqJklU;maKt&0EC~RJlW_pCEkUBYT0R>DN2)GoVZ>OzP-Nur_zJbe z?Yu?qgfnbvM!bVf)t#C=)S)APrgUaW?eXSC@qO_NCM8H)3zx;=g>D0dXdlzww6hV^ zV!8vde4%27u@(~gu4r9)YF|FeL&a)yRLT=5zYuJsC)Mt{_T;@^{4lom37bTVY~0O` z@VGfzUeZM?1cZeSXLj(8?~A0kx}qxwV&j9{c2=+?$nf|_sAFakbJ*H{^@dJ!X7h@+ z*}73EPFdK~N61UETtUZPu@ndBCy~Ql(mAlQTzLrdf58>a@i~ zp)8=cuz7lbH)>mVE~plNKquVjjg8_-cQ9P5XhoQh?MFX9k=5a^@~TK;$y#XlikM#Q&b?XD3IN@pEr&C+wQas5p7IxlCq7EcXR zTVLMbU%6#I;i4HBSa)#S@0#;)F9A(MZB&*3Q&_@j~UzEQ=xKcz}?& zZG~6~ijCe~$?5ohP(iR+ezEZUQ796y^~nR6X63)SBYO#F8@kv^o}L~UwLlXgmgGYF z*PJy_J6Ld1d&3eh3;>WpZ5GRlB}J=$j3=kNDT)BR!|=gEsgir1m!}M?*RR?8F-#OVr@ceMsl;W}QFH7UtFk)0 z+P%Z=7Ze4@Vj}8Npc29;U#sE!p|F8Zavnx=cJ~)W-6a}-A~q^!p*c6!7%?F@3|L$zf(>9tJ|+TW=IyKy(~$gP^*7pL`EtDy!ou{PSCPxhovoFu zT3%#DoUH=4ho&Hl5EZ#|m1oq5*jz+D=+BW2Sn??ZQ!Y+Uc4ls52gU6t4+`lBuJYW`^vkYe*Pno}O(R&dTEoTo8Vm8df+C;>DL+627RbmqO;!fI-N zOpL{UcI%tKba!tFSVBals(YfP1Gpn5Luj{y+w z#CU|0mp`013I~y=l$tW?%sMLBDOx4MK3b$)_aS1Y?H>@*jg*(IRGV)?JDyS;zEHY? z*i@O4^ozx^raPLWuQzqYy%a0qOv^ui5r3vxF7!W3IoWn>t5Mi^G`QGu()s1AT;W+4 zmy53xX<=MlPsa42pQpXNVeTF-7c=i1k~3w)uaS-MXqW7$Ktvjx_sw8#-6@<34%$XxkJ1$z62MlOOCsYcQ#q|bO#F} zumsjBla*vhhGE4lgssZ#*UAxpO$9uqVS~8NOoKP&W(v_!E=30^U{{&15v0D-8Kdu- zDS4-w(3=eihf)2|#Cmy#ob{39uF)wKslssO&R${7;W|V&4t?J!E2!w^Ddut2GGUPh z=9+8>6{8asyPv%^gJoZdp?NCivf4+Z4innM`-d*u5;H_4>p)Ni)I`pIu9cGst?R#cb9i~W2y+EmdN{r;Y2z!V4fKQT& zIpnaME$O#Wd0MN|SoR$$P}%MhW|4}1sI`}FX^zmPx?c4|PG9E-$<2xX0J0X4@@yP! zjUU0dGIFlOyG^{?Wn|M6U8IT3vkqG3zWB!Pwy5Q5jJ*L11wXi(zc{@miZ(Z2J$wxbkI_S?pImP7&Qp%`qmflMV`KDyWL6nR&-8 zGqQM>g_#XITETLA9mQRhY7gRM+!2FYl^0qA)Gq<=;?GZJI=DGLr^M-F<+S+{8$a8V zQZ*+n8LDZvD%W@rp(aWSi>jn_5ywL;o8x8sYL$A3%_+fu-viRf(1B;elITr2t&5*` zF6e`*zYv(Zq|HI#n=RZk}3igOYsGF193lbrTpiPGb$c+Y(H=Gn=OR zNga13@d5Hw_d$!^`ioHGMrLQ_>LkSMf&PvpayD(7s&Nvfy*qI}`Z`!I5UA8w^KmJ-BB`8*rRR~>J3B0x(v>j9AF zjTva`E8Z&!YaDExcY|PHNyB<(Mbvs>w#H%OVTPOlY!fX>rCjy%X?gNaaUqz?5XGMl z;wOsNsn0ayva;vAYk6qoN8m-}?{B-h0=r^EG#AN#aa!c~zT4M6S&`aJnB;-3zw1AA zWOUOS5US$A3P?F-^~pa{ANhoC{MR%SRM#Z#aXNVyd{T@l*oIvyv#&bnwB+E-I7Gm< z?Ax!PCWPW>*`{CK4!!jl=*e+gpghMP8LT&r%KEeCV}nIa}p zISs59Ht|;eK`Hfz-eg;ClOw$h?Z~aSRW#4Z2Rx&hkHG4}eNO|;G7df^ymXu^On5}I zT}$}6LC!C!On84vY*9D2{>{JV9L%cH6h_8>Sh`kvN~ART!rk@~LIDD~yo%M&@BaWe zrfN9ZEckX;wi_TWIwV`n4BxsxyD&+#3f%Tb4OfJDV(iTLhb<1 zk|Y)uy8@h6Zlg=jQCggva;)#EXN1}oi*)G$C5k#~45IV%j>3B>ri?a|ntHuKa!z7@ zU7>ey?Ky|qB{C%y&8(4AffY2*z6`eZ@*CmY3qLDFx?W%#nj#sd7!lzm{ael1-RI}u z4b+j|g!tf@>U`vh&WxPIxiF*#-eglUndYFW+@QVDmS&|ZSlKvl4V;$jr``r+2peKx zn&Rz}?tZQBz_z3%+?Cge>}RVJc!Xtt8|>mu{)Fql@81N?r+)O(6MwL42|h%_E>oG4 z8H00kHeaSR*Y{d%4oZ`)3A1&oNn^;l>!R@@tXB3FY`mV1O<7VWegTf<&RhM>kuf7jhHEEW?T*B(!}XnWBSqednv36uEtOpzQKczHDiX^pb(jUwK<__ zqCmH?RFU&`Oil{FFT4^p5dh+U*SR!S|n(*s&J6}G{%65_m+T*)r(Uih8B)D20I zr_YScpXl?9irH^Tec44l6td}uYm)e~j@!sBH$LAJ_jpM>DvKPf`1RCb%N6lC3)72a zD9$G-hS?Efs3dm|R*CnFWaDM@+M*^leBtzi^m$r=&K$cot=%Fv$9IW;m`OS#zd$Vk~hue;tWh5b~>Xf;PEav)LiML1D>Wy*k0+!4Vj7t$cFD{ODS&4ket}YT- z%=lV7Ml+X82Z<-J{cc`%R&X16w^rdkms7tuN-)pqK9_nGZ2&Q*1EtS;t>+sCA<_yf z-3ocz9TdB-QNA{(wTP_A&>&(9B=W_WpUXl25?T!rEmfx#ePQ_16 z&Q3bxjuM|z{ps?KRvtS`bsawfQ_=TixWjI^oTRXnnM_Q#>q>FbR;6Ua#tu5El4hOU zN;m*dp7?7VA!Ibll3ZIAfD|Hb}(p+n2~*d5R)v^EYVizRy@j$ z_6ah2tI~9U?e`Gl%q1fsKUyal7MHWj+KsT&;nyyCcPeNr2bVScx{PnbPa>q;z|B6% z(?!-?BPV2l$u7x%SuB6k?u>1hUM8$q+CC+t50N|>vPCM(B2Ka4A!qx>6<572%^|TW z<2s3&mLQj!na#<6&eRtm+y!%l84nlb2-JmYk+^RaPFJf^^r|g2`N`Lvuo?~_B}Lai zBvjQ_2@#*foiMktuxBsxsxJwske7?kJX-*mHi-vR2|(C6|3)$F#J5Q)i87krcO=H zT*D*2HuJj9As8jb)~!LZi;<9+tahf6u@Pq@H%zT)OW*H_9Bi-RE$RwJi#Gig>G|gv zWx*}b5O}kH=*c_dQt0NTpkkhPv0GXpUdLX`nZM4>AH|Y(G@vHKgV*FGu3`y z;BH=jLuEc$V@eD3ImZFsLR=&~LSHEQCO6+vhc~eoN5+*LIWv5;!HWAmQKd}Q(e$V2 zz1srq-_YA>8$Ef(rRuV?0am9F^#g;(grAIJx$(SKHep?LA4$HBUrA)z#>OY4YOVv7 z>2?z41BT~;a(NX#al+tMj zYJ!TNBwLm-@X@&4KN4}WQ>BjQ+1$C~N+gWT`)oYLSOg5rtsKz@GRwVII(&|`>J^%Q zHRNz-(;>2HV#HK@M}n~V%E`);lJyr@ms>|WcWUAC);SzJ%$$|_oNi+4#NrxE)c*ho z9K#K*+&nSw@v)6o<(5&pjJ>4IU`LT~l5qFK4DOooH&3FTJP8{9;;T|JYG5rME)ZP# zqW2dWS&9imwLw=D%8BlMdC?PY#b)NN(o(g6~tdP{{Sc#O$xP2 zwR%mujGV&-`b6ERV_@1Osj3!hs>NYlv7skjL}hah%xx=U;;1)N=;gft9-?i3ddKcq z^gJFwnNBd_VNHvTDkQ>>j1GpPQP6uC%BFE z#}=!g6jMGVQr04_#zY4fF3}=X5z7u;&MH)j!+~&zNTLR1;w9Dzh&z{YN*2Y>apNv` zz?5^w=kt%tuGTfmi790rRwrNr%JFEwe}*26RL+NOh{lBX2EyELdC!J5MC_*b8q7*v z{{Rd=Bvh#pJt{)E^r=ID8Jl1VZcW@ZR+x!vfIp5U;d$rm(*kwHB5y2Yt;*pHheEfh_^CHW19B+2K6@0M@YBaT5diUX5TZ11(2_p0C=0e`OJHPRtO& zC`#mILBt@<3JyD(6pC=JdsujCWJx!gaKto6accPYjl59{P*YP^a^Zz)2+lK_B(pyZ zHGROJaxQTFWH3O`itR4a7%VF}Uy^Y6q5`X-`z6QK0?0NTe8Q`Ii;KhZg-mEE_atRq zw$fkpu%`i6>Pl1Q5x#yXe_wLvu`Me#-lf$8Q9h^sA@r=*lqIofkvbpC%_n~pCZlXP z8LMqbB~Y#vnT`yix<(*P@n@V>i?A0S-Q5*no<^rJapmIiL7*x=l`MdM=+KZ9FzjCQ zjmoWqb{<{c<$-1;HXXh#VxfYqM@)sfuSrFfP@~1CwF?e= zm94ci+O|cAgm{;+IA4~r)cjKa06j%399dbOv@RI>+CXxfDs|@-?Go#KtGHn0>QbhT zkoq)ml}e>8h$<@Ce@W9>nq-1?(NSoOQ&Dftct@0M!PKWap5M)?pF!rmFshTx%hOw! z2IbiVYkJVyM2Y6S;Mr9?fsD-^IEJORM_qGSgD;BcA5})fx5z}s+9n3&?F2uIiYKa~ z?ncaQs%6V_c*@_3EwYTWjh@>S!;Sv3;)uCLwZJ{LH(5NCe~SMAz6NrXj?e+kt4uk> zh5rCF1Z5*LNZHM*OqI7eY#*ppC+-C1(k5KdCJcVeSEv+YVY}N&Hg5eQ;)&`4{=-1p zLEoA%EOm3- z4isI(=>%B9f8XysT`)H&L-?s2xp$VqT&=-|aZu&@$d?w;vkqe|-iqbJ197m+2c5;+ z)^0+9W-eVoJ&FvOyoml7gjQdW5AAVs-WG9F8x;QFoW;b3S2@A~P++*(Y{?5)jHPN!Ig6FV%Xt!of234b_L$q&)5wQ}b^fta8yhme z5OQ;qwI{7%m)z!DEw3*>lv!|s>r)ey6K}P)OuFEqT19bX@5&)fk3@N5qVaISR24>$ zGZ@Gl=W$&F62zQM11GVT^TdCO3dIaw#EM2s`#ey(gsZWG*tE#?D^@BLY$k#;iQFK` zOQstbf52q7kVi3jZguB!MfyOp=mBEqTs+sq2kC+!@o~BEiF{C030p!-5|_2%>G?%! z1F$zSdXnTN!e10s5U6%B{isr2sFVp+gt>{2hcfDjtD@?{2NO6=irs3A5nh`Ea}XCY zBD?rv7Z`;dd{r~*_O2)vLZ=yzuLq{9!)& zzfVBlu8|+v>4ud<&Q>nE5o(0cvIwgg{8xBks^B&wC0Slp^u=7iM8lP1=M{39G$89H zN1A`j1#luPXX|4l&#KrPj)(&FKW=dRI+s+ovpjEbb+#)i|s@^D6&c#ef9!u0C`%4uQQVqy%Ji2a$5W}3U@BNsC zOw`L(-;sOZ(X=9F#%%&T$ArGmlu2S3dcg=9*kuemY1)eC!*In19gMjyDg~bs-TR^i z+{S)=Jy8gux1Jk^tEfwc0>r2ibAu(}Ea1u(B$gI-eVBpiLAG}|-wnm$;fPpCe}}hr zrHEvtsw-3W1rrJkNPu6`)a9CsyTXDvTzB14mMJ#pl5l7=_9%NqXx(gIy(HZ6&U z>qu$Nm&M8!9x2-#b+a9;Va=mpe=(qAHo?LYMhBRYD(D7_-IQeH@tY@7`5k;5$~AKM zfvS20y;q-caQ-HhnwN8lpqLdaN6PX3uv~nZSAs#50*k77kUNl2MEczGi@%VF3vWDn+FGF{`e^$n2T0XXJ5)tXp$s-Rm$nDf{7t<3(5%i^%F%tFEMlQ5? zbW%vJH0bPb8>HkHuuR0k=E#{LbnwR8$-JGU?7FrrihWx;=JTS~RId4Xz}-QyWT<*K zdZoC`HYJF0mdJ?NM8FylGgeG`Q?{5T;}PpjJ(B<0641x({NtZ@ii(^N3qFD%|9zH(B?uY4rGIx>&iNOH`>@Z z&2pDtz(4(1Q^|&Re{zkOy6BIEQDbGmbW(}wZbDmQ?JZh8s+QSWJv=%rCdImGO$}Th z)AKH^Ar%Y=f_L$8MVF~OO!X=c5kx3WHb=A0Z#|({B|~d z#!RDg_YY06e+yM0NzB*IAkjYcc6*Hnjl6`02||P{)VgTB zEH4j;c)k@#BFjDlWhN?2ij}Sp@g!=({Uyh_yk;9_V`lF+ZtTgW60}k+*2-HhjZ=qq zPcdA0k(&gIh?KZT-x%o9Eh7E}>C8JLgy{Q{-4x4WmXT$26sC>o?6qQpim|s~-Yc^f zsPNkof5BR&v!2><(`N=@q24?ug>enSSwY09lJ*>8?WjxRiav9aZ42T){soJ7Q1A9N z**Uv-vgABJWfl|#c5EG(<5_7oTa|Kaa_!s~aF6F1a^cyd25NMhieC;){Z2J^i`6_f zfqPMssBcVL>KkA>ETuMS=UWjT&`M-B^)Iy!f1c*vCNkvRGkkY6R`cPE$3eZvjmW&t zt~e!bd_EY1kwx!@&vYuN6Hse8Do(s49K2B)HdTOC8aZ}zZqh!exQz`PNzYhqr&5=- z1k17$?77+lQ6abCh{!5qWMx843DQ2BxVn4d&_=W>o4tC`y4C%tWX^+*#DL523alz* ze|7XGVnm*6E4X1YttG36k*BoCeUjmV>n*aGo^jUM^?X-C)Gq!*M=f~sKA|h1nt(N6 zWaGk9R(O0R7QI2oaqN>j$D1S`pA03GVp<7V&K7hVZHAPrMbDwk7>JkJie8gICgvQY z+mUaKi^Nd&F=avLXnJ#Ysy2naJ}ly=f4LF|0wj&(rLMNX%4{vk81&camYC=~KGaD; z%acS!T59Q{y3B;X4X|q4E;4M0o^cy^h&YnyaV9J;SBJeU9mv* z4WN+^&N`O*o-ZzJxf`jhTiRtB9FvFI`M}R`DtO+w2ve>oksBHlXUzv&dSa$@Gg3C1 zq>&LI-D`xOXjTS&!HCgN$JpA5vHDR?Ri_#f60{l&iPB$}^^DXtCs&EBe=z5!C-(0a z%hP2kBFu!twj-{+yCF|MD1H=KG*20&P1hqP%VH)1)7N-}uDM~8Ix`MNT$>E-im)mu zrBOpa3=kkDC#9rUIf;fD`)(lH0Z^(}YrAB^m~;r|D^%;fmfYSK#&ldMr%0R<@m+P?$$jdh6R}{KAbWPheV^5ejlwB##BQPv-$s_(dYi~V zZL~)(bWtm@xV^i1#@!Q`NrOUGhlE9Q9FhrFWyEaU*eB5!5n{SibMvoC(!t?EX|A)$ zeE$IJ7WkSBZ^_YZf2aBq%d)|C%Eaa5NJI@b19!y%DAF(WdnKqohPYOo@SM|=Q%Qdi z8=NKc%P1(uRPtEZ_ORoja>8Z+2$KLP<2^TV72G^fMaqreVW&D}cf?-#)5 z!@V7a`f@&{g3LnT-8dH@kA#co5^%H&^lC;=Ven8OdR^AuBDT@a+@UY*2k8bSg!huq zHf@0+WdUJRe}HOOE?wqrGPE1%;k#6dhZfj$Le%i-n>IEeC)8Jeh6;NLbkP|xA`ILM zaG7xr+KOdRTEhn9!K-(-xEFnr)+9i#!d|C__>r>XY`7G&5&Yu1hc0GA1&k(XuM-Xy z@CzX@F}O?NNEVHa{Zo{^q3o{)?I_%KZjw~_q6~%ke_Vuf_oOOtjQvr~TWs`k{>x00 z%{RV~9+yN{i{}~u8m&^LG-~`sgh7k9OxmLcnFS3f zSdyiMf96D`rX6gAlcbgjk#cz`RyK|w+HN1mQhSQyW7~%z7i~e;+RizdIE%_;$wt(=rn8OUwwrC(_faf6nw(@?9Wu;n1d>M2TXlcdD}!sV7aR zgCf8AQo3c;Ukaypx-sQ;8L2H>AM}noEx~i&j&Ki%G3l)jjyUcnr zf67c^dK|7Xq0;J769n5wI5P+i&SV;7qn1jjIpnZeIW1!G+8?B=Z4=N36=4dkS#TWW zt!hb|Ut*UvFs;uNvx}G$T4ZRPv(N8=!^~@S8u9X(8>pbm?FxSc?&&PbY6ArQ*C}t?6xh{G6{(Ui@Ds0adk02P1 zUS)@-8`t2cGQ%fUoO)=C2`+V1f6F0UqWRz1 z8+gjF5ODrl^{(cV(K#Ix^`_g}$Ka=PrQYGsF##2=tbmx#j-= zag7P?RLw^X$sEJhZ#z4szyy3YufI6vyfozr8pC;7e`u~ucG=F*?75=^VU?wClE#dD zW^mbyO$JN!kwkEaY%ZdOV_Y#(~ngqWkz;ll}(yPx4IEKF7ogy`Q*I&k~)~=ByE-qJBG@v z{hv(V7MIMytz<&NSOhFC_lrW@?ur&xIc z$2l6ss(neeC^FAdg~@k=d?b_dW7JI;=`k*UG}kB=_wgjR*nU)rj59LhHt(s8<+}dx zN`A{KWieQe?khjyqSI+^H@jBT0Vyoa&<~f%W@Jzb5g~WsXJ}WdvfRQ%a&^m zjEy2&k%+U79(tiUF*f}zsfBaakna}e6#ZMdduWTVl0}MsX1q^-VBV27+J2$y*#&5c zu8}sEgO9&}xg;%E%KX+HRj_*#ZZgr#JK3bg1VkH8S_Q@me~7vUOXsXta2J%&R&j30 zYBLn4szBw);h~XgBuHzK<;hJ@_0vd|skaY5>@cNoilj}M4)RBYnC$l$u67>+WGIND zE39c$+0gYj;xt@Hm1&nZyP(A!`Oye$ZbuwpVsaf=rz@?71QBnrkx^|u`FwyEoknYi2Yh* z;{smE4YANn%0yk7TM}W}iVV*6tJ_g7q0Tg>U4huQ?Dnw6G&W`4{Trml8pB+kIOfHH{dfM)<5T(0H@bvT}!o zBye^Rm^w`Yo(Pi#E-2Yl926YX*P5cNKPF~#Bw1WXgfj!Ej{85>(tQ)ko={JTR#>~`ZyG2M^f0Y^I+|7m7lM_kF?7i#GGsL&VBwmuy zPpvzODpF%jZ+$g-F)-A=exhMI9A6+*=CIVma_^-!q}ISY9u}CaPvXfbs>{!VgGG{g z9QE01jvs*+_rd1FPyLKh$!m0}WZ1)!+b6?h-6EeR%}RJs%5~QFroeF{jW&y-TBWbL ze;*RH=`S+2R_Z0bxPF(rc3*KTH^e6UEu&`YmoigLRWkk14R}&66(Gu*)Tlh`Z(3qV zL;nD)?T&tEny0!VM^8~GzY)xhP+DcS+$(^%Z~kALQzpi;izlY&6N5QUdD%6#(sidd zWGKG)Tq{6*&7tW30JJXD1w)!}24>sZe|>mdbr+f{#x7SCnQW&!m&W>=5@EuTmY9}g zfsk-iPj#0N{L(zzW@KuZs|TdXw&WR=9w|ohvh)z#Uv)Yu7#sIt}41^KfZC2TsPf;wEm6~nZhhFGyw>0mIZX0|-cq-VVe+$UJB#j*BZ(S3jF0RBVscteL757B$I>NHGvT=N z>Hd)D{eZLUq|#oTJ2g*9ZIo0bf9HnjrdkPlXaJ?dW!Yu&j&C(u8FH_Zhli5L$G|^G ztQj+d@odza*NV%!L1}nZ7bTB*_-<#fipK1(>UAln>J9Ohap>2;$Zvn6OKg*yA@rrg z;>*U|rE+8`(*x2ayj+v_#;jOZD$$2weII1~9a*L9SJN94P}*$eX6pdlfB7~%Es__j zaZx+9A>91a=>ivyRRtC#t3A4dURV)-IK&n1LP5e~EOp@`tSb3%T4l zFB?y7V_=mEaa0CIU71mz3k7a<_EPlE-@}x0KO~tx_)5;GDZMufxm7gNTSY}TT)TQbahIcaLRzAUhcIJ7id(l%= zjS{+V+Gk)hN3;GOn)U^kdUXf z9;<675n-T0x}tfY!77H{;Q`NzE`(oU(7kK}CW$T`w=+#)e@qgNgQXxs4A~a%ScDHQ zb{s+vW!!+&kkQUEB6>&V#%&w*0Hz!AiFpCQx4^mEtX=eHal>y2>EewEI|FhreQ{)w zxk?6S-mvrvWMwqn+~YvDZ46(NE^xAwu-u1^3dIZDDRM};oeqcNbykHB232yJ0z$=bbP-HipG8|%q zn33MDcEKwKsxf+>ZkEG+P~))G={s~$;f5;>0_3f{f8dmW#hyIlLWYyG)-Sp?DX~G2 z-O327iE+ER!og-4%?}=&Q3a9HPAG<5Lk*7PBW4hW+~I6pYD85E?P1EsVz*dqIHBmm z%Dqd3@`mmJ)QP9~au~47H2|%6a_Nem!ZMc07ZpO$>5g_oD^7_iaU$k0AP-w*IiT@E z!F3$5f5XlosqP^qaYzn*bV)Ju{A>Jjkt-QtJ~4`uj6?7~<&e-9a&w@+pg$`V~evpA$#r`mv~{EJm4 zX|(|$$xElEB{T(o#FKMZ2MI#8v23HJXE?G>T~MD(Ma4&BIcemdW)-Ok@~C#^E_13V zC4GkNDuI$hX(JR9g?5PyQZeRP;`c*iu|%A^Y9XX+!&1>UI1#y=);1}oAT+yqX#@yy ze?IQ0xlozKWTu0ZMOu}qFh{`)p5)YxW*i{`RE6aQ;D(!;&Szf?V96jM=H`Vt$39@Y z8Q%$V;rL-M1nxbTy1`Wc09nA)kvbiVnduH0sT_%B89=EcxMt4ehC~+2vM(RY{b94( z^F236JYSG!9S#Rhbt0mi3q3Ss&E5-BfAGiFv3LaJ_QCctdvMwi$h_mSv}q7Ys-)2W z0CdVPLVL+aB>Uq=EFPm7vrdkus%=t6k1Z=fpv#3ejZXw(U52s|5Krc-&p7D3dTUiV zZo6Vz3gW;Z7w)-qj#JA^4@yBb{k|sCH7E^848YvSo8uHx@c#fPc@=?Ol=#*Fe{DB` zqn%L|bXeK!U^is0K>)xH7znwNy=|ohVw>1{&e1NWiz#KIw+Z;5;Rs@T8g>W8u^}gP zFY6UT)MZh}&ef(#$(I_JLE+&Ze-TfTbBlx{Mu1M^*->S-rAFi&$ekgykM8b+RU-`# z9C78sBNeC-FJMk%4AVap3?SI-3Cv~c`nbGNbShHD2N-u<)6)sAf%K5`n9Fz>+++5- zV(VbzurkI*Iuq8xvVB67&;!O-I3v8d2c)2(Dh{9r_PC1WyI7RiQmh5ce^}`TN>L~b zAR@$<+DJuhu*Njdz+xo4Rb2l7-2t=*(-2sa-9(~p0@A?bwuw3F)Z8ZtoUFUU6+|k7 z$vw4HLXqU9!_yMuQB0AWe7TGT z%dnQ_6?IRu662B&6NE>SeZ#CvfhZwgCiGqycJ>&dc=;(@L@+fLe`K#46=0c*sGF`n zs8cnGQCGR|fGAM8XbLA1L@pS~c#yh=AtjFsmYI3<#3;zzLGg39-w?U>@)9m6hL+vp z7=XizROwafw%fg!gr3H3LIKQT2UFX-d2`T6pjZ4_jhGP- zT?lg@;fPAqD|5}ae=I;uv`+X;v$$dbO@fuEEStjOT1q|=8=#aRV>Fo-TsGt+Fy4}x zf_9)l)7pf$ND(^%K6g>u`)0Krzk+?pr2SCkNX zsFx;4R8b;aIQKog^WnNK9JXBvIfdr}ub_bTvOv$;_Mkh9e=zfh%(`L=aAPo6>R+=E z77E(uYTa)SOhA=F827Ig*<0+z#OP{+-TK^@i;69qWa%8W#JOEBc+6bxbah}<;JU|}QgWtF_{{V&uxp60dO7##Onmjc8jdRMftf z+bxonz;wB)eKty-5;H<&+1UWNQr{%y4tLjyr-tn<)YdK=L8^l?8z+}(z1y`S5*q~s zNfS+S@8TFIHs-7;yFQb8>(t*ztQSvkrW^;SWx?Jqlm!#DSG%S%I_-pT9JTE(wLLHC zf6uZrerWpzQv)|>i;UH*tGBQMkAFG^eHoBpb(Abx>~KQXISH zj(?PAcMiurVroju&ysg(5q-iMw<;M&Qr#9vMHCF-SG)86kavh#41hIe~4Zc zw%BDy!jAs9S&M?+8)7O9RRt+BLiS&?8WBpVKTnuaA8CA0)d#UJIW9Alq7wFaZR&{Bw?aK(8CjTTIZu=p2o$X$ zJ|AW-99?Qg&Pt0QKClbIex_*Z`jphgHId+G$!#5bqB1LWm&RtXw(xl7jkZ5)sxiDCn~yp!%(kqUuJeQ0g;d|i zB${VCSvNRNDHr`= zEnv`+CQhF4kX%=Fe-r?H%so(ca^MbPT}8td!r4W#Dms@DL6seb)ADXPKG~1zyj(r- zaK+V!?g@({ky@qI17J}PGnoDucyv*$;Dd-pd^|8J9e?Cfag6iwGHx3}L__?bVro*1 zsO0SAkecw_$M%vPV#j^7gfddO8_M4lR45N2>2nhAI6~<0e|Si<>4OrZsTP7=!m|D@ zm>QZ@hf?oed6!W_*pP#!SF~>hIDMFrVY-m)s7moD+`ncB(ul;Qt&;#QS|h{kz_}PK zl(@`1E+<&{eefWLm?Sg3-VwUql_D>@nyeNizli3@sJ%x@dMN!FTTJwcsYuOC$o~Lm zaGb+?!JL`Qe_wSwkpsDZIPLMj4%641^B@I2mrUQ1gm8>N|bZren^<%(w6h&c_nTjKs05*|`p2yOkS zgb*#Lo_^Q!h+IyFxPuVW@Rc>m+LY5$tTV($=^(@>e{-HT&*{348AxFax>otect}^^ zPxFMnLtY*}{EmC9S0^!~`;+tYQ&Y}4nHjs{`@PVqjU6miK@#pZ+O~VYS|X$Hf_$P` zpg8v=Xgf-;Vbk5n)u-gZd?50TzdtAm&6J%p}O!Gc#5P&YT7}h7s2#QBCC3h9RVJGYZX2=^Z|4^gur4HI&SFe~f)coth^_PI5@SW$!FN55 z?cBkMwkzVee(gKri3&BEBfZ8#(N^*zA{4hSe|mZ85TWdxsX1tG0plJkd_tbgMQ!+k z!YU29Fr_ZQ-0?aj9uX8yV-n+$b}gB5aNA_t3rnboxU$c`$zW^?MSMk*46|rBMj>xd zk`=yQ{Gv5rEi{P3v{|a8%gwOcvzCvlCZ+DVbMJ$(8`!*IFGpNsijN?py32PV3C#+$ ze>!*T5g~-WW&0a@XMTjT(lr)p73tuZb+SV#Rs}_}R9^L6Ry0DH-5|GBG}6_v(}@blJR(=szzf2b5{!7=Y7T1oKP%b7tFtgAVVxcTw;SIzI%FH?Gc6L zYJlycnLV@#nez}420(_}L;9>@yGV!Re+*m1!UyWOPueaRlwn_~9m&^$$;{BVo5A9^ zdSD3IgGtr_=ZSc&yWTp$*q*5FR-ldsrKQVsRf~tP?p}Md^p@=Ue$+*Mc$yK|k&&Kq zZNh2d-T?TAtNziW8}G={lfNOsHiP03OK!Njz0mnViwpy^i(z@Qb;E7m&TzU0f23^n z9E7uK*2G5ABIy{NKtqkQAuf{e=Lw@ghr&lsVfJ^~D|~j{!vfyIXAuEX z!{GCpw{^WG;vC?ks8r+86IR0~xyT06EXQ_3ab@j5%rTkp?24*Uj#f`161mQz5s7CI z*oZf&uS%Ft#OKYwdI+_R0%D^@-qB}AG8QGp38z(j;Nvp`2+DIV zCG>g6I3{GJYFK`x(XdkRkKI1?D;8) zi$q){8j;0JRH|ZZ9_+WQ0kwQ3?JPB4WCMrxlRbyr+)nRaWOBCRTZiQd$;MoPt5n_C z7jBHVEZb=?lal3^3Gow2f8ILBGd~%iJ=Czu?NORr#N?q$?p_tYb(O?@*~TYEpE0RY zeuuW2ULbAibJA|g%*P)k#ClDDJ%uPzII32>bJa=7nR81)_mn_$ z?(@niGqNpeE)#9!ZAv!O0~5|R+1DDCm~VtaR;&(@C$gZr=@}b}f6}%zEq+CN8&ju-!tvAy%&qMpW7crzZ-- z6LE4(?$uM>6~uhFf7|&+Zr&$5B-40Ok1oUYN?AN5VeXz%xp;CV_bL~3dNj$kIykd! zOXkcmut}Ser^I%Zj)@@_T=mPELpLV0P5BoRt}|MxHp&aViLes&Pi}}wIoN;FEn8$* zST<(HaTJ+IOj~O=UDVpT)9b{Zu~(q`khw#<5$9&*!T8g(e^X`M8HZ*&EW2{^0Wjhd z^1!M(D`K9ppNpDBFGviA-ovv}6<38wW$9rtG%c{~thu-i0tbOtt+ifb%z4EyhUzvh z6~u=yV#}j0Gm^5U>j{?$xx`lz#YIkf7YTRt#WXCOeoq~d$*f59%hRpSA7B|a&C)Qn z(z!u2%{ihce<4)qT^2Cq#?WI{Wi;+P#YHJqum)g_#t>iM%kG5Kmpq>hSH+`=^M&Ga z$-S4K)+FOIQui3Tmbwg_%YulS^U>CP`_eVSSaPn5opTenNy8J4lW&x`$Ub(q$PkK> zSQ#%hYclWDe~|0>wG}aIrOP{$_H_PHjvuJdsr4&(XB2*zH1^yK+cy!>-YE|U zf4`Aye;q75h5_8ZO4nbLker`zt_E0JZ6pELMa_Q~C}o%=lAS9$H1_Ouxu=1^LzH&j zqa8yu)p8&rPbk!iYpsY%z66uQr%-xRidytDU~;&moAl;mW<{2hbuQ@d z3$e^67$T>zy5-&XMMv%ll}7S?o~2Tid2eK!X~sQht;Fyp?7NqKmg~wDr~%obORg22 zf2Pk%H&mXQ39aT+1TVI>h&gaNiS4MazDWgRjh7=>ktd~!KwOX$DQ(e8n24>;n|CWp zsouGAguLj+^22859+=)tzD-HDF_Z1Od5Ag(p^jCA6S)-u-_ z5TK3i{CtBj?oC!Kw)X{!F*`KoTW)NDf0;>)#f{MM5v8UC0J{6LC@)IH#cA;<*wb#) zbcxBMWQJj)$V6Rv38HM%T-;7di{V6#F~<8bwJ|icO=mq`D#87GzO}ihFMJ!}Atm5d z08VI&YUZ)078O&gi5V88Z1rk!iN_}AN!O0siQNo^6TMfGbjA8hBh%QkVg0K&e~5xz z6~Gc~myCFXzIo{i$;a1-Q5q-4URqwHDsOhFmjuLG2Nv9yR8-4FRE$5;Ur^?1P4+@w zZExHWT0?SiC!d@PklC0~8FLEBsBnd<7g~d=ecHwzT6G-U%tPW4lWg3Uo17UoW`ux@ zi3GA#$$6)gV#>P`eE0N|z`xkMe{Vf5%gs2;E~x{6R|x|0gUnFVBV% z$>a2$ZU$qeeKuM8(-l(82YB<+ZL}ghTIU{1k^Q;U!v$+;SgdeP1Eb*Te5g}dZdJL8 zu!#EC=9ixE`||RQZOGqAaSZHh`CVQWM>xNPV%3c_FIYzqDDGutIA$gtKmrlCg~ib_ zPn=IMk@R(5$neh3FjL*Pe`*z~rY5gE4H-~m8&Z`OMbPFPPD|&kI@lBXjM=kgb*=+p zxr-?h_=b9RnM;?Z+c<`7H1U}BQ$a+_%>@c796mlKm8M>z`-E!3EW9E>P0QNU(KXcT zKiy2HovK=_TDHbECPLvWR&ri)!V%sHDHpI$B5Rl14(~X)>;;TXe~)aFb9cQdc6y6m z7Wsm&@>qd3Z~)wMn@w&a~Mg-8EbGYjGK)(>E1kgDrb@xG=i?w zOU}Bz$Q{3WY*zgnWTq+@n+UOqC{pcj&T@z#R#K7j@=a1ZNe+NA}_Gw*~Ca~1h?lhfn z5SU_|%9BK^$Xs)hBsU-8nmV$XCyvAVZqhNY*ofBZD!_LK}~bhFBEh! zaPc73;y1`M^7w+D1@*=@#3eBE0$^7_ZKagxj*1{*>r(~{nu%ameX zLqk{MV$8bn>J5mtcTJ_$Tg7q`G7eJ)K$>Z-+IoZLgueXy;T*g^irsW%Z(@rhNoH1< zGcHl&=PFo$O8x0AqKbWJ3E!M$#On#-CEb3dR}yU2f00(fm0}8et=1{;r|E$4ixy|p z0rG@$QxxL;!sjZ_#Q1mg&3Z1@Hcz)&m0Tp4%q z4{~u}%jh$IsNOTvq~JQlzU|`S;*X)?VeW!3ZX|mM;;L;@T&Ffm#0m>gNc{wCwv0IHFUM<7zz8`}lMGF~;Y^MtfrM zI(To7D!TZw*Bc$%T(_il$gu5brycDjB7%Fqr$CTZKJ?`rzIGL#=BenBJwF1+NyFB- z*i1>xOc@DVG13K}BL0z%7wQtW%OwVy^oZQAe{R%`ExU-W_I~(uDR11@*!rzN*-klg z5>L_QrVgMgW^(PQx4cey{n3>l(#vJ}89h}+p|f^R=@1)Ul%vvqt0BU*Vn@3`VFf!lpGK4Ef-S^=%vYgI*$#p^n5wJMY5hH@acpDepjfZEZ6w9%(IvoLD$}XXf8!W!)T=pP#c%rOcq;|=9 z!E0q2fyol~WAcOcI*gzmmr?sMR~9rTQN1G7#JbqPv*a<}>9|f{5AnV;d;}yj&MR&vAn%2#ORyFFZeGJ{Y-;0Prz*?lNETLOgs- zYz+r8$|Xu1vgZU;cNnY`T;6ksmab%1yT0fQqiho46v<;SK>@KXV&Zf$e*?D7&`p$l zb_F#igdH#n`z(u^leXmI${!9eYUHiK8dZvz*w~xXKB&-=NS%Y+t@$izh8~Q0>3eyP ziY~)}sHmxD1XK4IP6fAcz}N-$BBwZR{{Rd)gml1&{{To-&V@XYRVKtkD`M4z6`Iw# z>B==Ha2d+P^JHJ^x-=|c3}iFUJ~%ch}ly+0#iic=vIsre<%vu6EFxtpNP6* z!gv^zzVYjmn8EH0(xIitb+5Ap3PeHFxes|k(3BpxHS09#fRxZk!NzNLW9>lDCUC6J zTp7SJk|*p)NYn^VHU9uv#m+CNF1sb=XMw;TFWH4YCNr$)c+AIqEzn}P^_RDLU2Gy_ zMNm6)7aoudgBRFzf7HQetR!G&*LkAC!Di6%U7_Zq4nQtQOEJTeMyyH}H_+r|1eaLb z9nDa|8JAqL{{UG-qBJp7CgCsA;f9k?Cf+}41&FF(vPSjr!W@jGbn}+o8S=9Wl`GSS zbHulJqa%t@BJqFA9QO!uCg-gCP_|GZiFQih(6uP2QONjZS1c@$qA7#?`s|;4BDz0{U2Imx_u-~rNGjDL!5PZ5V6$9Kapo*xjRxm z*d!(5ipbk4e=(NqKZ-Xp4YSgb&v;N>8{$74PjFkjxVm9%+A53;adM7|UMRKd7pM-H z!@BKsiw?kMagdGK7wpBvY#I#S#f!s}mqi*eC?~OjS=3is`!GtoBP&QE#k^+XW{b}};n;C+SrPT)k7{4F1!V?ssDiCJE0-61131RSuzTxcD_4t)!wZSfr$Jq?v(10_ zVodC)$FU<&F+cUrC}lKd%94=eh2;fur8XfZu0QyqSg4hQ-Ke;DxVm_JP-!|TobfzO z+w70=EK8K$<~NE!OYiB3&N@1eZ@P2-7*IwGe~Qg`M7m-Mc`(bxo=ejay~9QjMQ)=9 zeS{=jE#AE)09u5Xa@A2^7YsnAHe_wPA?0jFVNjJkL?{;fF#<5y*kj^?sY=E-nO&EP z1dPE&5Emsc*@JljGBh3(w@=ytu|Q70@QeW=-b|AqAUs;c7!2O;9}i4Ir?G%>9JcYf zKdeA?CVvU!E3@i|NI+uXws8hn$h~Uyd|zrIR&d2I8@(+N_+kyH4fgKe6cV=q2x_pR=oPz;AeM0i1H9C{;sj%Mz24}B!kHc=6o^)~D2ISm zOUmz*LTfjzA5g65W#Ri#3zY&*l9x@;Yf_Am+8Ge^{{T1uYGZgq zYJV?C!w`rhp1CveK(Ii|Hj>_`5(#jM!MM1th(@?3T-W0LsDdJNI+DuIXmp4*Tq3D2 zBK$raq6VfBzV(O`bcGhxyLh4nwFdLTMk2l*5`n2kH+i0E#qhxjWH8u>p1l76izo)2 zl6wYHwDL|Ul^P2|JkWmBMI<@0>Mn^+c+j@ zmF7fB)YjpE^tz0S+GA^B()}5(M;nZlmqHgc;o^i(v8f4gE8--K z9xykuUR_r~g3ed5;1tdbtTdKf^YL$bQ60m!A|7 zne7Hy5+uz;z9EPOhpfvfYvG7cZi{Tci;5wj=wCuPi*T(;2#kM9UxZ6A7=OpgGvJXzi=C!USF^ceWKNF} zWoCaUnV)TO-;13wrLmO=2LWszrarn(UbZr9J|k$8d1qq#^Zt>oR_kMab?IY${UlO` zTPVY`QuYx@*4rQ_MG3uAB!AELqZ|7fxD2o(Y*PEbNFPT!i44Ot6IgO;NzgpJ1}cP^ zcX3AMFPgn4N#WNSW~$jtkT)#Z@!jQ|Wue81*{!`UHUqLPtL+J#K=v@5q0Kl1)9pzU zL{F~|G$dTQd!uGQRXk0R$y7BY&cOCRBFM{sZ!5&mmk|!&OY^rl(|;M5UQSYcO5Qy5 zUG#p(RVf+Dt|Y}14^vtufI@H^5<=&n{UI%sU$2+qSBudD(P&)GWt>4h%ZTd+Hvagg zb|Z4K{{WBu1-Iz!=$}VtCh)8C1Uy5%t3FqM6jho_qK4a#nfMwdA=`6@Lj;6&K!-Zj@UN!_WT!?w>9npq3ZMGZe>*)=141xt~s;d4Izi zc=>F-M`G(LZX6f0^J1kdI#-BGzS)(&NTRD3kw~?aK8<3{jLu={N^JAeq4j-oRA(e6 z2Z^bc%PvdHH1dp-8Zl<}@lvZ&z^MB+qaze!W@An5yJxOZZGW3l^(E9^_*cj{hWQ%4 zV$;JFph4=EyYEPjrrb($Ml8egFVvnj9t)P(2*{&iRHn~TPF7|e4`GY$4e!v}&%B}W z+-xW*8REY+8=~w;_APj2v#d@tF2`HMf zu&T$%((16HqrnLA zR=W_Mu}H zhmx)B)DVB6gbZs0}U1xy7((Gp4x9 zYE2jSpUxB$6ZlLDYUDxz?Bs>SR^00DpS*kU3+-{ z0Pw;c_Gq9JvA$P(Lu@Z_UYFtkNnAYlcU$d5Rs+*!6Ew(|7UtCUqV8W|QZ)9aj?K-) zSr-gBWuyV*oRG;(iX^X##o>fcVawyBsWHRot+hKl1gZS*=^buw%Q~xhl>nJVVwq! z^*vlyUb$}Jx-%j$>B+JDwp#A{pczShjf!2; znSUa4TB8RtvQ+(s*5f= z4jZitCz)~>8i!!=^T9Y7m7-qMDxo8uns7L>5y)bP!|ZKOoe6>@QQ{sh9;mtf#VauL zw*!v|rwmZaj!IkvGZ+epwTR~&HcWok^?!;Sp6Wdrg*Hh)W*AdWm{8j1S5!gP0#il1 zCeU@(E@xt#kC>S>X42&bw+f|ptvZ4Psy7CM_MoBw15a=!84^wr9vC=bf#;>cnPSVs z5?C6Hqy!vsvm&@{yM_uQL}FEoTW0m}#g`GGEtdEQ$BNS7f$lP)sU&VZD7+(g6n|Z6 zMki5_d@broQx^=-O3BHGW^6xKbXMeQwTm{yb|;E9`Hu#JgQ)XWNw*5qm=@QvOD82G zb1by-jht=pZlJ~@!E~ci%dnd#g-l5tnf>kSX zvzRPx?3@xp>xs#_}E(__f#1_r{WKPfI+~QnWV{`;ws$J z4K{;dCn8L}@o??(HQ?m58qR5xk}`0ceY3qr*IC`#pZAR{b@1|5K!3dIOaUap4ekp1 z(&;RsjT?YdYRh2ZD~&jBaFwLL(im5(uOd?qVab_~(xusT$F4wVS`{Mj=D#e9oI!2N zRrwa04#8{HyGinurprCTChM1$tCu9@3%Q&1{{T~XZ1>Q|3}M*@-K#QH>CzOq-78xU zk|*_sDwjOAHkAVlQh)HQw&Ishlap^GA5_{5H~K`&v-uB+?a|~UW&TlW4MaRh;@TWV zr&Lu*VLP%dF5w*i02EzGV6DrWfu-N!f&}rkx-k+iFBQC2!pnv-Eb-*AY+Z(xiVU@S zSeTe@H*_``e*f`|4ko1ss5<&Vb8u1j9Z)SqUs^vi5v^s6?`N8@Drx{5J zOe-fVExS^+2GQcm(p8AK6w?W>6&$leXVi_Y65E)GXUpFO`ejh9qY~lS?GO-`5*55W zIl-c6WGF@qR)0~n5%r^x@p!V*35sM8+s}{*pQ-U$Jutfs^w0ysLSwq<-KE^Q!OUm~ zod72gohF=bd>U|;Vi3JlzT{$2*qApByYDV860~{wLZ}4d8NEg=^{8949+Dxu{_#tI zlZat7+`&xm4dZZ&F}rtYD0;AayH%fr`+3s9oHD>5L?_D#_*)`+inbW=u*&Bn@}A*Ji{Z9GXZ zAVApN!aPNkA-aNwWbzLuT$iXl>d4!>q)UW~Dp7-st0Tf~%iI)ksfNdhfZV$18jXsv ztM<9*&woeSh0d+C`NGfFw8~sxZ_XCV(c&>%UMDMUyU`Z0v+yVTKh_fV+%K>_KQxD8 znIMmIsVk&cH)pj!L;W(f>!5%AZ^78k@PCy=+t6ack zRHYJjTSdIY#Yd%5HpJ|?={2S_*%~&fo0d2c3FYn%y1VtO-4b6Tjya`C3LsEtdQrj2f53O`M#>%PsQ3Q>JFvII zrLntk9x{}XrBmkIaLL(L8YYZpltiy{HPLx_5`F3C3GLz`(&}Gvb|iYWbo8{rGc?;y zy6Z!MqdtM7ax{v95S}~h8tOumdQCbWuuI* zHSbTl6p^8e(n##?r2Zk37MglqRnY>PV(FzZKRwEhNC%hqYP!aejLj+wZ54^R$_*Za zCd{0hb*agDQz30d*HZk=)X$*u9whJQ3&RZgRJHOpRa$;hpE)m0N}f3f#Wq%v^zBORlR!ymmrjDD0M2cXWl$ar4cS3|tg4@X#M2O7L z)nbzwUz3=g!*XLzS%7VpMO?H>>y4x6GuJgxE=iI{O?r(}?l8rbX*w+9g(xMtY{=Lk z;u)zIv^@UDlrQ<*FT;8*G| z^rdoRXq82(xhYV1DnLVX;^{8vkQA~rK5D3i$YJP)tXwsc4r;A6D@|=q>B&3X8%HaP zEkkw-sA=A@Y-vm@1B@=;V+SgP%cVq|4aK73lUcZ$X1Xb=FPD5WDn8rk2y$=Jt7Pxi z0X;0^NMSE+&VPz3?_px z8$wy&os(v3v&)R&jn``Cyz@oTBb%G{j{blWvr{fUNQ}BbS&?pqNJH{SNPUaO%MxZz zS;D~p;M!LC ztTRwjF;x1LDRcYaE0jv{65<^H0Ll$UMC|I!Kj=%c52UZr`M$v;C4+X6OP98B$uU4D zTpbY1Jbz%k{{XCW`H7bkdbpT(>DAWCeDlTLg1bTV4)nLeD~YM@vrQ_?Oa)q8RHWZR zJi7a{yORF+-^2W}=3h%MO9O@}mdthL*ur!BLqV!Zw=Uz-^8nEc`B|E-in;e^9&?h~ z%6G7ur*G0vH?&%T&NvTDld4aXMRRwrJiO8uAAh_&C36GNX7T1KlcUqE?Q_PUH)m+* zN~FF>h&ON-KS?k?(RS>BUI?qg^wgJ`Le^MBxo z^R9z4Ce1mtihAOMo}upd!CaGaUDk()odph@yyKn9_vxseSmn$s^i~A@X_an+FC$GX z(|bBVFS^TpW$#|-PZ3oPie~9MW6}QrW_WL4G&?A=va<8@gL$wL>SJi8KuW#e%P*95 zeRMHpc5+^OXimL8+xzazJmFeX7k_%ZyYoz!B#hpmD~Mq$v~*riD_W!A~DA_sb0YTW?+>3nEN`3cMHOk(1lk*V)Daz=W>opkergK(#A?Gnl4xAlG!%QaXoWp8&h(ng>eZL~#EjL8wfB<|j$1q#|(^-2oGt!e@t<1IJPM&-BamY^i!P?aL5hI)fxb zO_)%Ze%nT6mDCM9^G<&F^*_VB_T*sxXG%8TPDGy?+sQc{PFX!`MhhVYZo=bc*LUo~<=P1z&VdGv(xTuvuG5M13MY zj$!FJ=xsF42$8n2gl39h%!cEf_VYgNJEHlVe^2?il;HBZ{7;8C(u(#Js1s8yy4>4k z#s$UxrD9(+w5h>INYD3ybvvF?xW&X7Lp~%%FM#;K9ENYQC zbAxvCD~G}!_jF#R z{9048E!#}23QbGf(ki^u?vGpe4rfoK=b8M!4Uw%7=fyaZN8O(VSylku;I5mqNyQ{8zknC1RS#Fv403_rzLx%@>{x{LA1o0bGL>w zyZu06o09gQOfKSFY*OkcpX|dC#LAg%xZ97AC?QMbqH>MN$O@udl2i?0-l-}8q<5P2 zMaE*)MD{=RoD*aUqQ*wpA5a}YzRE(D!J~}=?5DF1hb0En5`22Z%kEt?HalDA80WCQ zp_K7%_=hT zf-Re5BA$3i^C%H_A}bFG0Yd?HD-|aYv{CBuL%l&Y(GM{$TbsC{koFpkVnvZ79v_An z!IqpmDuQ|ru(weTh0dDYpgHwt;)F^8NhKxaZw!-^1jRy>9E(3|?L{w8ux~e)67@h( zOMj@sV7FoRxS*4{oygjQmuNjD8X^p&JmP1sgm_`GJ1s^I4qtQ{n^U-(F6LkRmI~q2 z$hXIm_+mG?6id?OVEz996kK--RBlrJ@pd!n1zJLW(&Z6~U}8Le)v!WBHr?7=_+kx? z;%Fx9L705m1VynpD4 zsyI!Cz9>dfOxqwQxQw%mSr`TB0^eS7h5M3cM3pX7oO?C*cH1{; z7eHziw&1BD%90csU)B@ij+LyAWMo_-BONcs{yn)F)lAu?p?XJ{-)c?PBqk`-{uyn- z1;Ny5m-Uag@c3)P<0_me%Cg-j&3`OV=VhFl0qPSm?(;9oIr&>>pv3(q`>0|A90x;_ zPgc~NgG~tj5A%m4^qHI8ii?v5Cw1TUi0`5&u`NfKamI7IiZcbhkJ4$*e-{m~HXu|| zASF`$uB_mJm@wfe+NeY9z<4@L5zYXI9AjlD21o6-P?(h;_*ZQqkm>3+umqj zwGfb|cXqvMa)>e1Riv6HOhFV1+hu0HA8V!oV`DoOJQuQ>&N`5opJkrQNR$5n%umb; zw!>cJRp!4aYOccb@gcE2K2DoBOv^Y^Ot<~%bh^IMCv-8JLCO*3Fi6daLLsbIs4ge$ zdAHH974bkbY#UTd&T;9;y?>Yxp~Yk^>*9zO;9d5!n%!w-)ex~8;x)}VL@pFA_#F6) z{80tF9(yLwBD0J6L}x4JYFawGKDhozuJgYNHcqbH7+Zll$)vH zm`1)UhUk_eh~EkZXj;I9t)3Zz<@jO{^N(6&pXlIhK_fZ443m+641W+^j1<8U=hR*( zf+fX^*8uTr;rT=uhBt&dc!xNJgzaQM^F#)scbA@OT}Z!XAVxRV+M$0+)EmgA_)|6ZziI{j z!mh>e%qaf=tzx?Z>rq75=hJd{prM2A0`e2e3|y!}Bp}U3?|+IRY1~;kXAcdr0;NGT zDhrbGyZ1yP;%@CO(0!PKr?3#nizSFyvJxFrltF;Rm&N>12ob{()i{PGqF3V8kS;f_#X* zlK5gP2VEX<>ut}c{4oKCv4W@`5Ex zehWQCP<4T2*rROW8L@2HD}aZ^@Wng}Wcr(949+8ql7GUPLdWi2Xnh0Glx4_mg(O=m z-pk^QrK=ON;+&!lQOK%pNjj1m!B6cg_|1R%o~RFpvb%Tx0DCAKR2rWhv1L<^VN?6V zBK`2xb5kKelfjaBs}|EnakpHQDUH+OC20s3jXqJokM26v3A0)oxEE{_RZ3p=UP7BX zFUb?XtbfU~%@tBLODcSZ*`(N;Ui6%M5Mp|y;5a|{qf-qST9$g$$$5RKxa4d}Y}st{ zlhw*R!40Y=!4;k>ZOFfeIOlRC#&nk2D-t-H31UagT_Km6Y#Q8ttWPYEl{}*bf0%6J z<5a|GcbQJj)LF+}B;@983%(Ba~%s5wHZ>noftobU(J1VrA{{TX=ZauSNnfplzV-dlqZSo`i-w*iebJM;rmT+nkp% zA9AKqmopf6E3BkG*QN~WzKQiUb9`=7W|}ug&cKbTGnYLlMOGWSMsvl$r5V`dv~2mc z={;O=?W@lUaFZvy;Oe*9jSA(^*1uUgM}KiRcWcFU#SE|%+2-mFh!;@Z)oDPFj3Gj5 z*MyX~ONQ>KgrJta9%YFq`2+P^X)T#JPh-v>Q* zGd&dH&9>~`etT~iv1Mi5AsVyR@UJWLhH%rQ?P6`>HipP+M@*BIk*bqTxX9QC z6)Z1Q%%tW7kcox%4=Bx-6Cj+)rRg+wHOCV8f=%Wo8yA{zVS6C+g9RmwY3Nmf?) zSaMa%N9V|4PHp3RoS}*rv%KIHqkm~EWbPBMQLXVJ4dv|=YC}>GHA}Z68W=cntgD-| zx+0AysI^C>=38-}R%7CaCSOcTrptW;X-?ATH_nmFK0PEvwv2240BE%N8UFxHqWMq6 z6s%!HaA!%O5*{KxpvA5c5vJ z=3MPqTgh;yy`0RREd8j=!JZcLUpSDYJ?>6%7>AiFbw~meH2H!+g}HG$CS}~TJ*Du% zDroLFWUkgIG5n3Bc`xxqAva8u7MUk);veM-$Q&sCoZ}+)NH;=eb~K%*)E;MmY!P#+ z0>G&2w@K50mTAGvM&XL6;(sm7XE#=RaU$6Wmq~b_=tzqa)P0yt)uhGwhc3I?;N;=A7%W^@fu8x6ek+$`zwKsUI- zQx7>VZX%0WkHZzH0kVzWoOS0*H!WXeN6sdoFIC5~czs@w?I!UIzkf+wJ@C|BlXcFiHY6vBx2;?#7_?3k%l254sjowj=-i%9A)EEI3vMPH7Ezux5kZw=u13kc3 z*v0cPc!bEsE&C%CeSZdKjW4@9g2D)42?Ck{4Q;jtdWzf)CZ`&CGZMLASJ*3&)d`^A zSiKNR+Xj0BWsuz5*hWX+#x81Dw!e{9QHz;qeWyVJQhv-92#luIbh;6aqe6i6;bDy20v;V&R7~?r!bg zaFIk(gvqjm1AmpaT)&hYPKZ2lvMFqUw7bO%c^Hm3rAi?ICvg54wC*=@Ic8j2F;wE> zLi^$|YP&478cUTqmSyDGc5-p-geBS=oppI=kuf7=dZ?V)7avm{rRbCNJ>q@0Jx{IU zryavMY=-ZvwHdjT_xU3EM|+LXi8gNVuArd}??Z(BC>gz^cfoz3vw%SBG;xXt!cTkz6Y zx#90l$A1wA>na{i2;4@)mlj#&o=g65QI%L0{D0Kj7$a@e+>1_=?zoQC9Y4-4aI^vXH3I&{s;(fQJu7Y2mpgL##d=Is;0o5l z-EaxDoN2x*iqT#bgN`d-RTlbL+ z7k{Q-=@2{H9;!6Xj!U?JmgZSYpuK!|qI?>gD=5WNZG^C=&ok z)j-8tBq9@KM%44t8R@vs(q}9b9kfZj42+d{E@vyn-U7?-?t@D}>`9(HjhBV-l2j!7 zMBL>yuq>@GcsQZT)*QC_jwUhH5>)bju})<*;VFO;{%~WF2pYGfMhnB|$5?7$^?%@C zRl?2#YXN!;QgI)q1hn%d^(?FVrpZN2tQUVQ;VqMXAh#bWS)KH!GLsTE>W)5KCB&Bg zNy;m)><{R&nUnO6r?_pCu2q|GM~sJx)coRgM~JrJXDn17NXa&4M1rw6E!$`d?e!}8 zuXI|o3Y5va%LLGtB1z>0!PstCXn#rT#CIcVjouLWbAbzbB4??fjNQ^jpAFYQFi|9PG?r!o z#C5w=xNaD#3w%oCJwpeK!^6063Cp-dxNY;+6+;_vMlaCbWo$v~ET5o0&5$}@WsINQYB+?=b@~1; zvuIl{dj;Y*nIQ+M^TMPqh}z>vFVb`F(5WjcR+wb?luw2<$6#*16I$1>3pJyA z{pobCzBOZg3AK$^iGT3ZxN4+LFI#28Lk_iqQO*~|2F&2IINxe$xJKc)h9lxx@c_GF zoJ7tZLTT2`xnl8Kb&DMQ*!CJpEia3v#o~I1^GV0o#gk3&Gc0Cu&Cn=nlhEjwj-tEIa5X$u0AU6;heHo>{Qhib=Dou7jejTFFBqSM3 zJ2W+lJ1i)bgoOC7E_&vv8{d-8@u}0p%%YnwWV=0<+M&5gr3~(JW4&}AdvNIFn(3y# zL<~&H9hQ*1{aeqg|@i4bt`GE=(9&fLb9c@GhBrqk#OMQI5_W zT|Gm}rqGUI=@+R|F3Qu~l9zn!d2MyTw3PDKntu^#acBA=K@?i zJ=Ui_Btu0VQ%iZpzb9;8>C09ktvfY?=c;mU0J2hU6R+UuS&POa+^!+^An8`%r!KF#sDd%nPh1j5;QrhBTQX}=8PM5klR@aOaM#I zmh40qQ)e{%;^G)hkgm{heB0C+=BAvJr@gprnwVl?U6y<^9Enj=RZQW9_VF1??Dl$2 zeqwTNO{XOaX{TRxhmeatNa_Y$m-)pig?|&UxRYmRh3NAWRB5TlnPzB&rDi2iTy>KS z%n?z1`=X`_m#JH+Q|kDK4YK2Xfybrf_eR9yX2XuAMU#P=CT5+<1z(U?sjXIP64j}q zO;P5CluaY%`)*y!fYrqDpOSY*wPmQq^rgCsERG2bMNMUTP%=*`^5kgFtJo%n_p!af$hBIa4EO{5q zKd_pmCPGcgp255kUFbUEjk$Ciikd0$Xej~fs+rrCS!8x6IaoP*k-00p^@c&but*P@ zx@n*$W1#oK=aHd5s6x9|*!^C1uz%V*$cPh4EfZ4UPQI#yi@R@<2gJWPpQ+iYIcB(? zz%!U$Y=E1!w#Z#{4*MNcT#qwdRZ%;KNOmmyG$$_pLM7zq>2hq-W}X)$#MEuxdc4!t zG%1#LB8HlG!);?kGRY$^NT=?ms*DMUylJ{(Yve*tS0^jjAgO6M?5UK}nHUX)mRz&)UnpfZY1No{LB!7CdW5X>*tInDjIU(_ zxkP^7c4rk-7n>IROO75X3V+98YF2t^pOMw0SAa{+L?{ ztYJ!#VhU`(ZlTUhDNG3ut&>IfCp3V<^ypy?3-vh5YZFIJdFLFHnq&pl5G!s6L%7o} z@0@gTeUyf!#F+AzP>{Ih23L&$MbS~YRvki6rPi@joSjINo(p_sb$`%qnhj8~Y;V#E zdtN{h>sVC4QE)bR7P<5t;^O0cwKI0_v?v8|3Iz_wesMF>FfDi(-2gB_{SL!tso~2Y3o!XfGD{awy z;QB`!Q)Bh2-Xz2_Y~c5pd6_a+Cu$@>-)2(P8$(`ds63$44S&W&sr3H<(X(O{oHvI4 zvU*-tp*`8sLHdcZ4v{!Xy=G%h^;?HDfXtqsikxmwBd^M#@rx5Qxwb%M+Lw`-VP0r- zxU|ePOxKYTWU-DM)q^F}hQqj^rzNdwpDN$45#Sz_<)&(@&sBoH*2ek__Jv2BuT@E9 zWaZ>eUxcQubANs+ocVtecfhLkSS73tFgrCNpOZGnv`EVSj&n}$1-6lSig|+DpSg1K zjrg0VtPQDlm#9e6YkRKUIx-F#01%Ohiu*_JOR`@y)*_UvvfQpVc>>9!rtqmq zZ8HSP&dajYsJz2cpady-HfwpFYvz%QnS;_WawPTyjgtOA$7F5)m6_mOxN%C3_3c7#B7e3LZM8Hs>s+* zk&u&WMSqtLdE|+kWV$9%5{1Jaa~V7{GVdFGh0+U%0mc0rUIx=%jJ5Ye74%3#--R}` zHO=J$YG4@=l_|<|rI9$pz@xm)e2VCFy5uvy9UK+(&S2f0VC-{+@$M&)toF=RDpF-N zCfW(_+e?s7UTT*vamkzw8PyZXfyc}A51wVWDt{#&szpenx>BNP!!cnK;ltOGcgJl% z6w&>t@Ajg7O@XrSOoEfgjg*BBgNNlUE1&G>L@AbnnJ0W`$HQbbarv0CwQE!CPod7x zFvi=x7g>~Yio+pNEx~RX-4Q%(m+1<8(9w0u75Q9C>2#UDC&r@Ld{2;{Ohy&N7;kA( zw0}LJQf8^OT6nnCCaz4})oNEg2~RGxOLHH+BZc@!pI7&Prq43?mJgq``fF^=kf8x_ zbq^GKj8C<*3pN*_AEdPZ0HTkiZXd<6FB>T)`ZqCQDBQ^?=l*fO7Dlap#*Q=Tx{s}! zQpZ7wHur?~<%1>~aVyWV)lsJ0-HWaYWPjPJr@%yu_eTRB7EG=Cj@JTxJhl5DNcLTd z*{oqveRy8E42I+(%PyqyPAJXDT{TXVrAuxP+C=3#wo)$BTKc@DR6=8S#7dqY;)27I zrlwrjY(V1b6r`!%vU|c8NUSa!kyLs7qZ`h;LB)$kh1#UXAlccyH%XO#p#1d1Q-6|s zq2U%mePAwLX&HF7$DXU=4<>qgjiIthij&mVOj&mwQya%UmKm&2>fmzbN7GIo_3(jB$@l zbA=JwrWwRNnbz8D1nv7V>G=GG%73FhRyVE%Fe7ExU2ztf#An&LEPWXC z0i1SE(aW@)!S83PE*Te#k!8!yJlEu1lV<9DU5)7j~TGk=L-B_f2m#Y2)~KzkjhxQ@fe4vfQv_AxKB?L=1srb*ODJ`R{|$x)lq`3A7x zRFxyAlO5NECo zkfAhTP~nnt&@AD4fr`h1q2KL6cPz-%E)R<67C_>5IF+8bvhdvsVaUXUyuhCnCB{P! z*_spzC4rND>knL|19_4zQKA@x zeDgcKSJ{WBaBf6QU4L<92V(aGO!I$-hpH~cOpueqZQp7c5-FU5sS@!bmk`0j3AqgO zYWF|d_F%@V>;uw!+fh6|9}G4edZ>vyeY&SIF0T|kDweD}B&-0fy-=)=pmhbVlHYRm zLJH_5n8Ks3ZSkk6ykyDOc z_F~b7#v!5&*6{uqI}b$^ERPQi5(FQzsVJlY0XZsN`DYgsqUa?I41e3I~$w%z|_@+`4@~!U-bBI=;mX!iqQ6o+$HK`!mMPQ1I zX^0$>E#cj+u;hWU+-q-q6R668B;=4K)NeJXJ}9Y;ihrKuJwrV8<<%M7&n!btHbi;a zJW+!wYT((Ix$Pq>s2hTIlPkN{Bx4U86B5j#^Ws8>wj)Xp8J7{#0w9c{+mjOT-4>~^ ztW$Qgv{9h5^<1F*yXK3-AvAK@>>`dGbIQJCb5?5a)y_TmePKs6=EtB)!ke13C^V^1UK?0wNA3ZQ3TF8g$B9Vo zg*yk(P0~nFkyRh(8Jrbi-)Y^Zg#@DvtkO63dZ3D)#(Q!Vsp0-8g(>2m0l5@z;_*Zz z$SJAPEj}(7f^H<9w^AOcshtLn0A~bxe`YB|v45V~ybd0(W(bxRGej-rZN>c|Q`jNp zCcYG!l&ss5EL578bX_?&$#+vIi>NPpBBV4HHYVFWEGqkAqEqPXQe-f$8K#O5Qe+&t zr(DY`+*L$XRPK!)zr>ijO=Qf`JcKG%i+>Dd^64WsfEOaWi;B^LCaff!S9)R#^u+IN zLVr&egkln+hG2v`u827Ea|DJX)ewTNG;n{z5UA{UTu;oPEDof1DqXlYZg7@@B?cb! zX01tni!|d8woSO+LPB4hK)d*jTX2%x%ZFGRi>R(Hf9oh9XkbR+%f%3-w3*9X{9Q0C zF>x7ryF)J*OawS~FkB(y;rMuArOa#_c7LK|BXkW!vxIrg*!27*0u*)-gqP=TpBJ1G zl~H#MnT!!yWiSr#Yr?Stp2C!}0^$gSRTPD8iacF03Ux3mZ;J1S-vLnTVPIUA2T&ah z0`dGs{Gp=3-k0SNsq8FNk5%c2SRx(^ZPFl5-0-p@8jg_!TBd!k7sCZnyV;a+9Dmgy zLy6d-Bwl?#3_xX%TWM(!mNC2%F}tLCVi(pRrug*aJ{W-9Hux_SQr-QYsDZ@nNjTN| z7wtqT`Kn6$|v*p)I<3V{?7sDGCpE5_a{yiiIl;L9&Ae*Uf~j9f;~cA_tw5lqG& zc}VTIr!FD!L?bXEBVAXf3cx~%a`Dj$-Y%bN1eF!OG2!;Oq7fv^Zx1r+h1thSo>I+q zC}0YvOtQDb?La7YKu&d%buWqvV`RFBG0XlRY6VFs3Sq+)v3Pvo#mwjcx_`XApW283 z;gR0(uRrSoz{Hs*%KVer{!tVoEWD?p!3Ptud43@*;4EKrE;5bs8&)NT$8lmRK?6O7 zKS|*szgQ(D%sf7%Z5v7oCuC&=B35a`Z}BWioe~fmnT^?dpe_YLFODH}kY&W~YCJy7 zFe1r?Yf{v+6qs-u-VzrVoPR+yb8X?51;7&$xJxT|VN=)+%xzpBE~pHu@j~T+k7nvN z6&$SrUurTrtt6F`ZsW~kJB^Oy^toEto7?pS`)~M6w|yLmcx@#R09gjac7JQ=c%- zI<4(1YV;gD4>e7ftbbJ*kT+T$CXml1B+tJtUijXPzL6Y^SfOsF^7PX5ZKmudW`f2X zs8(izXkK=3=N?BKERQ7aL`=QO4UXn+Bl@07X(=2hU91*T(+<6F4W#wIzBMYZ$lK{B z)RK|eO;)D!ZMySB;TVmC;kkCbIV3YkDUHR$zp^@0tjemMga^^w;LN=5dkWl#`r2wly zRKK?SF;K&igSJ;QX%7eZV8y#wB&w5tKWZSiHg}^G!m+lh8YP0RI-$kl%nOJ3U|+_n zXJy&0Hpl51F|prE++9nXm6e>Ovr@LgZ8wb}0Iq)Z;*Ece%CNO#smDfzHn!7gcxP$Y zPF?X98JnD!N9ljHCItlVRL~DC6cuz>>bh&hoi^$@s=ZX42|1}$Ct015xq7;6>rd*C zt5haluA_T;0{U=F&Qh^9&9Nl7*Cg=;A>gKyrEy8kf)Rm-z7>{kpjMqV8aaqwmt;lh zjyA^9jzp-X#z0~$Rk>yCyza@kTF|ky{I;B zJUR0Aqg+OZ$8tKYCZrrN_<&)E!4gB(7R#ZVf?l68lI+~QU*{CvLqZJ6%^pHLF=~V) zWX0CQByi!e*iA=F5V-bB&MX*<2$^vido?Z?I|S8$1vDfDZ!d-bsB*D9&Qm%2MGC0f z0gA9j2y}nRA~6EXGDXS+grumWJmGFIke7QmHR*)0P~?=Ktk=U7!_~Gs67Gr{3`kO3 z%=0oNfpreVK({*Fr+6=ZlA_LP97iJ?jZm{dQ%?#w6x-SwoNz>I1Nh%oHn?0*6)Prc(NafWD&Ud(z zvh_`Do(uX{;_*f%_BxoGK`H`b9af0t6&;3Ld4^smnI+yEf$?!ggVH*ld6|RVsQsuJ zg3QUb65vXLB0eF2b!7zonf)Rgi zI1_|mk%{WqL$3(%K+Mz&>>Rtu8^B)-Tt>*oS(;Y^PA~F_5we!B7|Evilz5AZ9yU7= zvy&G*R`|1luxO+d!@_efW>%2?7$gdU>9#O#DseAVFp`6EaB7bdo#o;7qM|e{y@d;d zFKQs#R@-SskyMbE751)3h4vjvydrVx$u zrqmc<6Plu`A|Jab+r;2DUoR7#%ly_ZKg+24spxC;UtnKFQI)AoO8)?8F?ldM`jp_I z?w8iUqAA>`9CVJ_Z?F%HwEV9sy#NteM6L}I%UUwuM~eS@)KeV zl@|w20Z(e)_#2XiQ)ycxpzT^gv8+qn^L!xq@VGyGQ^v!`{IgMFPeL%4rSl1W7JlU&R!~e@K)5D5C6RcwNQ+0J+O6ETb#)NISc1jNStRN|3=shp%Wn&4?Xp4yw+Rncoms_H31KB@y~IDXJuxaunyYD&FAgV~aan&d2Q#$&lZXO$RyM&_I3{@K9Tum>9X-+)(72;eh4nl6P6bGh6!p_xGQsZ5v zRB>BkE<9bAQ2@5xQ_s#DixJ(!`1MA!6^4kWC9{6=E3P3fBFo?Jht%m)ULl(hkvEas z*+Ltd5X+0_5~P3HAP9e^H^weaSV3tGs3KpUutc>_OOEDDS+;4j6ZV$ezc?w9u4Tr} z_AM>%vv|Z-V(O=fk(e@3Y#AP&A-3f9v+vFp&-M<3k(g|y-UYBsMJyrAcT2!jnEu z5ncZPl~EE;W(8Old`8dN4#e?225?=Q9>*f!Ey7P+LcH`&=s#CSE0pT~CdDcY#I~ur z_H8W0zSJW6IkadUGl(lxS!~*Q@@>A6J}n`u$t2`x@*GI*N~3u1-+6DGrs zN|JK>RHwUk`L};WuCPRg;r!seMIM}mCFJYW+JFf&G|^mKB3vc=;f*UH!|XGX=2I>b z6{ub-uGifHHXTJwuy@#wTtmat(!S`aP|`s($W3sYmmrr>c)sYcrr!}SHjEo?I6IQm zfJcaO{{T1(6qCOb1j)_-xFc|oCVYog{(3}WcnNK}V+(&~ExbuMoF3?;=?Y=KA&5Xr zKB4f4jiN~e#OoQ%GL9b$o3~3Y5$VgnoGMf!60+bf5`eWYZw`ZT7oXh)5uvOk2Xc10 z-K})oMRt)bVwpzIN}8>p@i@t6!EK|QEs&j3G^tU=%WrVS93`^;toz^@L)yU}{TI}@ z;*+GU^<96{uPl?wAlQ{4*pqf%O~(nwi7!hA+lai!KIjOY7arnj&7hdyx-H{txF8iG zFS$F>VxE)Y45G@AsVlj7TQ6M0oL7+AB&p1O*+zL*gARTg$`SSYYb_BgWpH&7TWnK% z_?(BvRcEQ-$&%U@4rIR8ec44a*T_nyjaTUjv37rUmq8;d+nToK9}b~Z@T>;DK)kXa zI{hFpHFl4@mM08ORrTK9JXU+X(YF?5G-AuMQR3~6u~?Sv20&al5&K!j$aNC!*V0h2 zR>EDR$?v6jP;<88w&**kcW5+b+OX@h8@I`kY}@rmZ;Q$wVbPU-LrsC~oN-n*XS9e5 zLOp*3TtvOl#Tv%>$KHwuU89qd^5)yZI#@g<8D6DC=;!+V7NxTMLzPBN)=0Q+wM0we zfMmIBKcM5_r_8Ly+vbaez)Of@JMqJzHug5E(|2=}VF{*gS4pm-%fI(Vf77qXW+~A- zh%j9*Y;-wVs7yOgnz0Je8-!2UUno>Faoc~Sj=MI7#_Lm5TqNBxZlu)2fMu#4i?0^2Ik?S#NTcV%+(H2hI2&NipMfL4+;PNkfc6TNNG zV*>Lsqg-!RGg54C(g|^uNz6Z1ZQJDE-Azln;*S{k(=9Xiq<1*^y?jlaRPxzU zj9nR-kaC(b4t9A_m0{=SCJt2ilv6Nm#ZGzn9R6`7)!3z7krZ8)jy>2syYqh%{<5(? zQ$sKri?7<6U!OmmWc;(TV)T^i@lcq+`xBEHXPTNRhGoiC*@g@S2&XI#g+k9I$cZ%N z96mA~8z9*5Z@Y1NGQ%BS;nzx|&^SfB;e0qst<^#h0iw&|s zATJT{2xoBu+J-tWG+R|Phom>h#0RW&oEh}0{UP=5N8d|IE+4||wLJD8q?1$kxoEKa zu3iGZYKf}-(0nYurc#qVdQAyj`b_q{v3dV?`C;E|K=&LzaX#gOt}IAqO$y^9qG(%WgfOTxRV9wx&sPs@XX>kLgR zqG`+k0>=JSe&Q*V8MSL{GDSb8@zNw%Ren4%gI?AMX7zkA5CT)a}c*r#`KwnS#bAX zrc1BrzC??bsH?1L!;=)5-v>$7TPWIg$~M6)%-tgqH7fB69rCXz4r|Ok{&61(jqYV3?$ntUXPvRvUeLJjKWtHP@Ux6KK~r}ZyLFH6XoJxd~W0ZLta5E(9^|S?N7M+)6Ptpb+Qp6yMV1n5fi;H z*ENp5EdHljo3TFEq`PeQ78NpRUm3}J8+Ax!`bncj)mwjFTJy`kEzxJGN4i~ljoF5F zhfl@QjxhVc2e@+p)PHXhU^l$#n;pvI$0E5&T zkgbJp>J@pkzEPPSk*dchs~?f`w`e;*IVqPVrkN2&%I+b!N8cT+4z8y~S!H}pe_X>d z&jAUDTwYu^W!)NU(hN3~u266-HJX!-JmkQccng1~0+lJ7M#$N$o*k7jlc-C|*yES9 zZVki#07%t_1Y7o-N3=`Q>HC>$TbCiYEEKoyk30FEE}F`2;AYuL$3j?|i7-w2$%zqC z5Z2-j{Jv4=nZBo4J-kY0FswCLW=3g&e6E*l44oAcB@h%|ec6mXN2=(DmvMoX-ahwd zM#+B&h=pww`NpI!J&UH#ws%=8A>oE}(&errv$1{~m6U@j}0$nBA5dgV&Pqqp! zGn#pli6TX0&9yRfc>e$;Hy+Z3iu9bVS&@GUdUVmJ5E2nwymq24b}Atfa>_W}EXHmk z>8e+$t+EF(yq>gsMZ!XS2$ES%Gl;&uePxsAt` zX~ra+Y2EE4F;H7{krY`O#%ZJzRB)G)dn0M`6&9M;EfVEbMO_IymsrrNuO$^SzxaQP zh7)k<@(r6Z`Qb#6ag?BhD`^d z5qV^%%_60D;AP1LN?&L56Uj6$HpJnRMAe(L0nK?TdBY)E0lvWCjB!!fEpk?$UYD8~ zF(ej@KUmNrWSK84lgVR!7drmqBR+r4hIoovwI_2kJLz&3#7MfbMC6|});YO2s*E-i zj&lq(C0LjiCW32#nql`iM?PP>iYXF-*ovxAW*nyE5_1CVxcZ~&bi0!@%cCuF&KBO} zS6}HWc!L1WNy$o+4a<)5=WUqCL|e}#G+gyUQRJ~r9U=OQEJZW37wUMnlRtkiP?eQr zWo9L22la#}%@A_WG~v=2jTsnQw^6Hpls`wApGYr2deI9%SXcIXE>+}^$Bdf*EYUe@ zkw$)G>HSsmQx;|4iSYhJ!eNHp65a!Xgd{yAk?S$#y4dHFSar_LQkOoVy*PTVi)9^$ zBP&?@OY~Fiad7U*WodQVds2Vx)SYG73DuZKL|jXgjT2B+PD9QybE{L*azB}Z%x&+b zr_tvR`e=G2)R?HS(ND^>U4iiwzD{UcBW&AM?q2@@y(7eaK^q=b&x^@VJ(~&nsi?U7`rs;n^jCu=<<~RB> ztKF67T&v;wru;1{ofbn?1kcS1GX6&|rgk`v(N<0dc)g&*$obL$G3GKs?goy zxEgUZv3%EuJfjw6-&yFgy3bZG6QJbgyG1p*fGyi31VTmBOU0I+afT;GK>N4rd$gW! zv8!UOUv~BSf8gj6Av>a-~fo+B7~Y{@9%_8M#b^4 zIoE5_Ykx)|uVTC(F(px$nv<3wN7|fsCa!r?QAFms^8Wy|c5yxon7ocRrItz$(jn+& zOs-%F992lB&3$z%B%fPNaqlL6ACgCj{I5n|R~c}tAJlxJ+ID{*r%~}cjk?ROZrxEL z>g64;#B(m9E7VHh+3IZO@uY3hb+JMVhlHchMqja~3vG|69*gw9?4H1IoTp_go|jpY z6BY5V{@C-Mkj&4PwL4jrVA!+OE!bqfuHKtQG2Xo@(I1;~C z&Adv*MqEbL_le#fXh{K!D%e6ZOLZFCYnVJR-YPBF{);?2_Pv~!n8&vKCncplBhG$b zjYjb*XD#I(f*9kvXc04ahAc}Bo|C#J^bb%MCGCscgra{5oYysq2*ERSERF6EL(Ujf zDX*I1OylwkxzOAOG2Sk`p)%prz_WB`cL9*fO6Lpp8{#FpOcaok=@Zm)jEt`5SaN33 z3^^-zDBq7M1O&gI7utrVVhb5`SbF5x#ZZmOjrrRQiO^923RGO=FxZj9X^BEG$$7@5 zb&xAkHfn!GQFBNu5~OUzx+gd|U?vDEqc)jAiDfHwYj4Zh@j|-^$l5E_BDYnBq!}5A zhbmj6$vj<9$^iKTGgclXLg|WZBJL2Y0$wOfVhS<D@p8H%u+ATBeW8^c)F3;`1{E1!lLxFH!iDx@EW zrVhj_wMlgwvV)1xmtwn6Qtlr(banMp^?TwMPEh9oNjW!Y4-iXJ7@j-F*~$s!q_ z*sfM(?>?V6Ct(=<$SLO3e-vIc(E8K_t0Y{9svM!*k(ZEE3zwW6Mu<6XSA!)On%N9F zC&#QDAWAfHR@1k`7ecL3tE`KEY6hiV!)JdRu>Sx!CH5A3A$rw9;`V$nTVkWIP#l9& z;`m~riPCxAHOWdY@&E_KlI*X#8k4f91>zw-E63rBZ`up?6w8+B3z?{vVB+<_>u|R=9*RZTScfSA`J<4kS#^QCe~T0Mc?!CD*DWsA&YWsS|%}<>8Fd zgIY8x(jjLr7M8(8XhgdxmTnu6i-y=9!9#==?X^53^N6pqOA9A0Jk|`g_Xoc2yI6!} z6eVy%oq|=+Cd8ETP9lj8N{$?2)~+wZXu&erA-40^TAzr$&>E>-gQz9_2+P-VE+~N+FI~crvI~`D-7($q84?JH^$~YN0hFp;TSPh`j~%Lv$Y}u!9r+=GEvvzh+0KNw>YEErXUQ$Tzh$+E<;)Kc?R7?HQ%dy`k|lA9ClOuZjIKH&r2ha-$gwp8MM${3NH9qR zHGib$+9`iKJi`@gd+r;RS*mlQ!vZVCcn~ILoS~|_9ILbB-Yle?g-DrEv;hkT*j1Hb01UKp98zgct7@vTU(^d z##M8f@#NzhKdQ*n<)z=6a-;}ya;y)nX};o6PW3_0Ug+J88l6VfkK6|V?Ks`3IWYKlR3C2%k^ULrg=c+dKyz2xzUuLq(-4DAln$8f^3DTCp6QFE!NlLqH2&4- z{{UEh8~Dhj*&XDrFq1ie9PTFt%&$c5(%w<04~g8%J2$S>q{9;WE6xM{v8jqne`@3b z%Ov+gIR_^MG%}SPnff*3A4o4nt&qr2X(rtM7p$!8*KKU&nilfPlt4*j&gC9SmiyseG~X|6umV$hW3F}m#Vl*Ld8Xv z-IjHJl{q?-V{=Z9WW?ZS*3oiQ_j5?xnU+lX zc#Y(xalz?Jb@seaXk@XG^&FvqukL>wOOTeGBcwv7v2NJB(aP;cir_iBA(%lD_Em0( zCHoo|=`N9Wi|Mh6CRigL)H3e)h+Bd8*F?h%Mmf}W=Kwn)>c zU}ghS8P-X&$`?@2Hs0{{ABrwmg@+8_nua3+Ss6L0t=QaKrqr$X=@hH58!D%e+G3M9 zl@XUFUEkekg}hn(qC&Kj)=#l+#+x6nY`$7W%G8^(e^@k+wRedZ`oMp_Jr=F9E=aRX z`=Jxu(TtIo9r9}mHX!m|pv^7p-s0i5*qy?9joYd@Q+p=*3#a80fTZulw_x3zl~}}_ zu)fz!2V+`b*|so#&igMIGinH}#5_8Cpc$0aiwi+}q)4?Q0_vBn9E3&`^Yu0-9G9Mx zCxE@8%!}ZJ%vjQpX{~>vQvR>05{TL&ekijKB{Wv5P7e*G!_{;t(B(|*Y}o^bIoK#M zAxX9-8O1Hr*&&r3+n(Qw0)-J(Ipdmpc;kjfM4UudT?Yyi19IE$AGH=-C?_X~p7{PA zm^kc5T@L3R?FX9lX$hFoT2a8&H!@<4jkf;)6j~{3D+TXkxT1gHAzQB;zc^nZ4#x{J zo0^+eQkKfm9viM6sOQ-2BABR6J2)1MUZ68H^nO9h5VR;ANdiAOO`XbG#R9_8ux;8! z&{0&_EBk{C_i7D1JTO|3ZzD#Y-MmP=NJYa0gaI`6^5jzLixV&jvh1?Lisj!Cc%g$K zmPU1n#TNw9`-FdZplSAN4aPed6%hY)P0LgxFt?6L@08{BE&*kWt0}M;isC2n{Z7y7G zX{H4PMLu3|`Q+&Ho~^u#Z5CmgrN-F+@bO>642-w&4kVFgTnRe3ho;yy_a%Y}ZqDm` z;ud75Gu2C{rY@yaVN|YIcBc}ThAyB0ZI`DHGrWI%R{Y?II%ZUvJj6tle`YEtV+T!& zwi@vtS62)Q*a%@x5vHZ#@WiM}dE)qZVvAJ*+dGsji-f<0A`tLAEwVy`t>20v0J%1Z z&7?kEJW&W?(?K$Z6;l&cK&G>EgK-vCkBTa1K+mwz>htpt2(@c!=6+WU4^X>}-kg|o zspEgoOReG7F2d$^7G39v2(9Vqx+RG-u$$5yRIT_&hV_bKk2f&mW?2#b{urX-bOvye zp)IDR3s*TtulEwh38jIv6}`aR(4TW)1b-Vq87=JD{S+_5Ft4_(PwU6kRf zPYO|FGZCadmUIW8M@(T>nE|$334A5u7?u{;bi`!l3*4y*xah8h#3}sJ zBxM&zh6FB8(&q(b-k1|1{^+g|`NI=4K`ziq(RPJWWy@y?rr!r+{hpd<(QxPrLr-twJkQ44wEetOGo!;oJXQXl-Ay4ZP+VUlA3Fm>p5&}DJ zxVU(WF8Gt~8ZLw~Hi;V~B4vNXL?o9IMFh?EJm9iy?yWu9aFIFgx13utgWPWP!AqNj z4j$}!E)=&uT0-|BR73LsK*<4d%Z;Q)HxT{M77ja&*q9lR6DG;W8x5%&v&#s$-Jz|VHjch)+^~?pq=%ekkz`4(ZW0~WT1{kxia&fpw*-GrYTV0i(48(W z03MN8wsnz|kZio`z~K6(+}Eg5(gpDR<1x+T@ipRCtJGDrT5Y}}D{X|#m|hCp)fQ%+ zWxZsa0?&ko-^CWJCDUez^pNyRYiv?)I@8a$Q4k>lX|(m|y~hVukui;+=iC$YbK{BX zGt}vtEYo|kQEAsn2y%Z;S;tEjCwyH-zten_HACo=+CDC2k)EEQ(_{qg;3YRn6WtB; zRtrueKT{6&QS=+&*~)~_Z5p`z^yAK<1d~Fn0H7~&qUO2x=MGHE1LN_l{YO$#K8*d1 zVS8p}c6&qK6AiRy+3Y7#G(yjxhBM{J!@owV0~UG%;C280_5>I?|Fy&l^M@j(0J4*V#XD3H@;o*yZKEN-yM`_GaU&>*_DJg-wfKY@M zIoUYP;%mb{Vo!e(Vru-0EYz^vz)VWpzc955 zPNpqO2GBN%SlFH(nyJzye@u8#fThIj8&|)-IOgL|Q!Rg3e7#40iQbZxU8G`x#STi# zmL#RRElo?iowL&FnJA`wm&tphS1a+Wi>Ok*&cE57EZL^gHncNR_+J~y)Y*^|bbCNB z!|02Vr!+-hd?yDXu{BkCLri~DcvSrcUfShiUZ;kuQ}VRg=DkA$Fxk5z@Ns~u_oDJI zvl>5HMzDX(JdC)>DI7rV8^JG0;x^7!a+W02;1icNPoF1IIjAkU7GC9z>B(HH(rZ`0 ziJgq&hAL9i69q60l)GeBky`6Wi;`%+tZjKOls+G7VCM0+)L!<9F*zfK>W#^MhTO*H zoJ}<_j*Fn;j$n)9ZEnn0nsSX8lT5$%I9-_En2Ub}r51jlOOt1@vrm#_1T+yO5EQ>p ze>lPUB;Hf4igZ9$hNU?4twc$yDDxHrO*w6`t)N$U;fG5JkxoPMF!3-D(o3oDV7#kZsN35R;>A!^A3!<&twvKMYOPKxWbT z{Ph0-q~i}u@1;*_)XWJ>sNxEw)p^FHWn{B47FhsoO;?ZtE!nEaZ}2$Mmiri!8#^6? zu%G@*UOU9QF=nZ>J*-RN8sx@h^<6eyXN7-`@Xf^`&qCKE)Az?$GpZ7CwYrV-=)3;_ z$*tK&&A_Jii&&~{I;Fi+3y9^UL_3pqE2{bCs~Mk>v!I-OP`_if_A}_YvoA(GH!$83 zz<7R+5~sQ%m6rk=*F?Q?)qi|u%ai28o7=>Di8dib+C?gQR=b8|s?5{~T4~2+QMP{? zLbsY^nWq$U^5It;vriMAJt{tmy`Ak>4$MknVLhDUc~?|^wxS9XP9X9XZFE%6{_&0Y zt!|KQ)O@Y<%-OEh{RMG9)Y$%)O~u%HarEpMfMwK1Wd}qD7yGvrJUQ##8u-tNo6>HU z{{ZLz06oh2lpUP|5zv z+Fr`%Wlm~dPQ65zxCEr;mwGLdW&)Y%nlHW(>e1v+M;1e~y&ry@3>S)-mtwv%^x2|m z8kh-*br5O7enU6Z4+%o zG947Wja3W3ND3^2{Uo%;?xYh81Hee#%?O!s>Ba}(*>)CcxzYP6+QS<{HfBNMm8cLX%n9(8efekRkZeg*tr%R|;$zv}s7paPZ@Z?xxCaaB}qSUvbL2!TOL+qS*_s6E8>5QY;+?3t9ivfwB^n>&|ZpAzc|^w}GzQofk3;7l4AvY_+RB!5=OntG+- z-np0W%IN9i#iba}vtmolF-MtYtD19-3$wd>K<--e9{&J*J9`N%-X-G*xlt;VyM$9z z_DR)z!a{K!Aq0QbdHb$SV$w(Sonz=%i>7gPVqukW(UW9RrUBV~+xpycVy21R$|heP zkz9)UM^#JGUBp;>u{2jpw!+LWViz!ZBA~9R#ym<2y#D}n zXxkT79XCrMmbE4RvTF4v4CTPvBwKSm7fY8yPVe6bU>mHkVfnD4N$zEZ*SkQVi56hs z>z29Zh=X;j?)>1TMY@)@aM;PvTbP?5ZJB^enro+aE7jhq#yNRFOjOG4^(=G>E3eP? zL2;pVuy}t+OD+@!#d-PWT1JdbU>T}7#F#CK$(e~c=`eY=TXaNj>9W(iKJ>_n8Q4QL z{7Pj*Wz(BGnJM}M)3b~=NGBwGS0euayYtYY-nRUUSJ7sm))mHC2c`Ei&jMZqHxTMt zAH6Ps^pqr$bL$|O0w%?*?MAQaAw&$c_eN>NQx$)#St8Si+1-_PX~zu-tBZt_JeQVQ z#(as?6h_KEDyUUGet$#^t+sKxv1Eh$EC>M^8Wyl*c2Hz_KoQGBFxU{ zmR?jqOHv|Ky)$3BINy=Nxm3mJA@KL<4D^3(J&$dgX#$=S^7F-eqyy@_(=VJa@Oha( zkjEF6nq!i&eGI!XIE|kPDV4+Cyrb6Q%b^rE=h2JlfX2jXwTvTEv+C;cp#|JSe+npl z@uQWAvSR5raNT8U$9;MsOaB0|e@a>EMGjz_q*b?5>{EEeWt?#`QC$0xc_ef3e`J5l z^*g*qZ%V9v52MXYNYh)Jb;EE)rF`!2WVtWjoO9*s9eyRQpISPlD4v$y_LkhVt}BR= z!XwZ2g(0@Ems*jt(qJZ1a}NuG*w|L_2##GV?}blL4xu(ff~Zql#^)R{cAqe&!$Len zildkBj#f?`eo`wW8y6PJrqw9eqJV$ZmbosV7m5i9<(64OawhQ@^5WW`27NKGeNzQv z%C!mFYeMxdY4IYq(#cOe;~(L&>Af$hfjwFBHEzV{bC|~+Qt5`-ki9h6+}~BX$Fg4@ zlc&MMc0Q=lW}2Z($kTg4{{Z3g;=l!~!;)+J@E+VLb_ zv}5@Xu8?6MlKl6^C<9R-wTN(Mu z>M5$6ml2P8leMy*Qz`;-Uf6$SjMW=B-MYm@Y!S1v_Umj&oe&8(lKDYcx$a`yb%|n( z*q-<;$qjbfB)Z4rtj590Gj+K}MsT9RL6itnxOEmy-O4lE344sW4LHS1qtt z*>o}4{!simv;?7omyO8#E`pnv&+NnD#>co;*^LO{L}*#d!=Z};a^!!ytQk`q7*5Iv zrg5_y072Cow&-#ds35A%pz6LT(vSrJ!Gz z%XPN&#?{1Rc^H3c2~0@~tJ;kbgJx-V4Bj9V@m+Vd6_<^oVttMqty+0qsYWcgO-k%P zldEqnj9E}>OG!5?IlO=QMjWhaQd*u{%~zar^Dy#qRIa!BMrIg0Ww;T6kd+~Wlu{s; zmhhBZutr2ncb80BFsX|Jm~4B7G$R1s!zY`#%flKKwgrZhU#rP@qe3Y}bHTv}6QU8b zgJF_eq&$km9b#24B1pRc@IcK;sa&4aH5dwvrX*K|7x+dS9fyD8kV8-M@9Bn*9>~d% znC3h_)G-Q<#g>aH)8(Wd;ABRN6s)gKe})h(ly=;@S*U?P)Lfuu6VvTx1O8Htk4Z=s_&g&`&=Nv7uBY9gLdTD7_;Mg$N)^ z?$Vq9cK?D~I4C}6>?)V8IR4TZ9d=A~|mU|O&+n3YjIKlZ@t0TH|1nRtdK;D?dB zz_aPQ`Nfb+kMWo6#2^e3p?0tgq1o`|08n3gLzOU1Na-J=z3JRs(Pv zcRYXl-717V;|z(}QlcA}As((Mv3ipoqT|l(Zio|tDV6EE;fMktw%$_F2hJdBr<-&7 z^+YMuC(O8dtr&npi0u}cFSQVEv|P4IXoPK6(^=99NtV#89M3p5h|<~U>Y<7cw&EjTsGTuTsp}{TX@@VguGE?6fc(*#o~xs ziCLvZ(jZeh65$n^3__SjYMi1AQ6%9Gur>%y#6UTCvw{|^CyD`v*v`Dje#}aRb}%Kz zIYgBXS)?xV?}q3IfhVrHatI-bB6x<_U<@s{2YHuFLR88sWtx8s4Te^yo-Uu-im-pV zVI<~5__|{3OBo`!M79XK9tS@ZKqs)WdcYJCxQnXcfo0hhgbQLi{?rj|?k&O2(hu5& zkV8k*FjX$08#UydU*!ZDo|Dt|q5{I4Qtm6ih6bRZJ=&@3?Og;it`~esqVgz(TB7bw zuMfizCh9OGr1pFx?t!SgRKu+)AG&`hV4J8(IqI~th5}}w8BP_>3LLFLk~@@_r{RlX z(HlH72uLG!qF>Gk8z8c9d$^z~67>Ta!6V`y*@;p&P*HdU^GLM;mqMO(IAZ2>3F32r zSA>7!fEt~I5qGRG2al*C9L!Yt+v12yig1gcP>AtF3c3U;V&1&ID1}7qN|}Eas9sjv z5U~R>ZLll_K{++ObC3NjQZ~laZsg|d@?RJDV8k;Dm6*Ehy{lprveG$a;&)5G3=N4v zXg$|jkRif@BJ#XSElE?&@fe~(hN=?95Vq3)02C>hlnB}01DO4*p=$aL6GDUsJeP_M z-(j!=Zri<){uq(e2$ztFj;?>7YpMc$f?>HKU;eIxPjFRF5Zro0Vl)dbk2uc;mPLUx zqW=K1{9fdS7~N$U3(||ih(4HQv zq?)vRBh?OH0MWWm6x!u4u+F^a!jK5;HxG%d+rwczWZ{j`rW7`ER-1y^A-i|wcML@r z8A~k_56jEzxe;5w%uuFwEmE?3mPCt}+Tw@|&W-u#mr^-8C2^n|x00?07F?AzFPv2N z8H!5Gy$ogc$3uCs;QD{sUfXzW^@AD`K;#{>iM9nXwr!zPWztomr?)QY%}p0nvkf6S z>3!fiMs=2)rT#4zF*7mp{{U~>a;RavVT&=H{)d}v!*k7(GSc*EpgG%+L=*s(6;X20Q(V$D@uzBL*>bw|XzxEpKcjw~ zuv{M_h&HKOcN15LccCizC`zz?O|#D=^DpZhoVjh)>_>lQf7(cG^V4e&Y@SS|Sb3(* zcv_W}9wGaa{?W_HlKm<%?>A|u(e7w(3hLtjLRs#ba9wco|4=0TZ zR-D@mykw30CBqthLukUF^xZ)mRKDjtP*(!ACN^_~W+i8PYt5C-{;!Alqb5gS&522; z%+JmU1AnZ_6sl%_b7|mwHW+}VaXVh^b6ZgAYL65GxdOO&Z| zRM&s8M`)iSHto6}jc931dIr@(UF}q(La=cGTb^Z*(#W}BXeH{CP&;uWOyq;WPzH(;lt7ax>$QIqma5zK zqM1V8!A(3PEvPvkmUr?u-e3gywkHy60_7L z)8U0>Le+l5nKztsQsE-ug{Xhw!Av^Hb#RYOzUUyTIcM~v7nb|HLl9yp{$5!ojl@CY zh*hd!X-4*r_ zCO0ig>20vsjSqg~3CXaU3qKc3SrfPyEpAwZxMJ)sdkrP+W#a1gZis)SOdBuJ)6usU z`bc6ykis4#GZDpW_-D8lxV}oXEAbl*tq>n$JFmZzoRz?`Ypi&dfHOiElJ%Ib|J$#<)BW8Tl{GW#shyPN$f0>50PL?t}{>A`H8d2JI&f!ldRN zoN!rDCz>4X;^8m9=Lmo95_J|lHAS13e|qihk4>sw-O&6f&}dI$$}etN$$hS<+@u=J zPj2oMa_gX?G(ov3&IYAumxeCd4gk_XEx)!6Cr?6ABOamZf$mC=1+i~I6Yt{Gw2HAR zELP*n-Vxy#qQWbP(&nQVVP~9iwd%U)zTv3d;z2ybPlu-N7!u5H5ycUI_F@#kX54C3 zvbn&|!Z%$PQdn+PMno?b-Eu8OvzSW3iIzfbk55&_6=8DY5}MvTPMdD=Wy9?K(G;r~ zX|!`*GCP-tP85j1h9DP%B=E%$QzWK-*Ryv_LLPbR+_hU9zW8AXykU#wa4iMN`Nrl6 zmiTR9yPPFov-{{U3j54&AC#GIzY;i(l<+ZH6LQ`{r3Rz_rr8!KGhwU-urpj#=ScO-(rFR+FwN6c<@EzaEehZX4GG#J!@}DP7bezRG;zY#Vid3Z{y;>ki`K8;hiv zSi4x2s76I4UKBF*I+dl7T^25T8z0g3xKvzh*u3`UMqs)KxJ&NtSf-7M*$ujWfMqg?mLx%EqfOt~>0JHbR34$?Ca&-Mj zv_d{2;SoiKCKi8x5Q#Z_KQ66Y?ao`n@W$BUC^y%LIjS2@Y6R$|tKqnQ=%?fCiZptH z{J3RvP7!5jiE$ieelrrocbjn@9rG@V_=?^U`{5IbqsSvHyS!v=^WnS25LuSUtRW`t zxUTlDi?xZZ335ps42*tdZwW&6B$%+wsH-tJFE26Sv`W!`%ZHp@8bP7}tYLB%NVh$s z+CHm(P<5!hxOP`+6rRpyXrHnod`0=hjJh$u8H~x*Cup0-+nC&~y&@<=Yx9OCP}7jf z(oNEF;f<`!HVy-s5t!yBr<65wESmB1mVAlnd9bN4$;&y!Igpjz-pav8w~4zG*l>id z1Bjd`Bg622=NIN0E{3gKHxiY@b)rp|cfJ%nMAmYk+Dh4(6~sxA5d2Wh=vw=UEan&{ z-Y;0=c9~ms(N5bcRBcidW?OUv+7jd;;kFknF+IdhIlKKN2YF~9x+LACOP~2f?lnEd zPBJ6}P5zD#3SVzP`=D9@>^FIUn576ScAl1ww&mu3u|#6CP?zj8PA^j}lJOAmmwXLH zWnGZ&IleI<9oRXX$q|jnULSOAjv|>C4#(8oFWA`<5hhQhhB zllEeWp5;?)SRPjsV8Whyn$*Vf_9g_lkILnL{#nBL37}|vWcowB9A&7us--Tn}`Y!mr`-08$?NMDDI|RoX1i!kzSa??^>h2YU_v#rVZ0z zm(8E;A(=38T?pmda8|*((H!}tWX;CK!AT*luZ2_97O4 zcELfHdW}+cMB5T8bsjY2i>_%3jDC}K7GHKNifez_CTW$4jpY29F6qN7_u#y4a&VVbCML>h(t(P-HW>Su8Nr~X4d*W!C7hXHVtzTRw%f7PIi?! zKUM-A?7+1!j=0bw=BnDKG|Rd&J~gm^X!P_}Sa`gbF&&bROgVMdQSah@@ z6f`8&KNNDYV%}3y6uIYyR%PanW49E!l(@P%+UsrHiDqKDW&Rj^$5;D_eU2xqA5h_n zr1MR*Nh2WpPjV2tOtj_3d zySy?rVYe5d1q&>bn&%yV$L3_fxWD<_)QGUBcx;br(s-UsSF{Vt;U}*$TeJ2YMdW@; znlF58XlWAb^)0VWl)fQtlo_d635FJ6#$A;sL>BCc6Ov}SMs^J+WH8Q7(qv~CZfmj2 zxM?(Uvs_LRu8670a*WD<&EjoTxv(j&JB%dRetEfuO*LtkmvV8o7==gH`wN{Os$LI?Xv9Jsf4i|FY78LajORYBVq0&Lj}u4 zMb#q@Biz%DPl3LX9)mT%MxCP7C>WKnmza@#L92-_nQe6gOu6ZQ&&hL0>F_!1>FH(i zP%`1SQLpLmVr(k#tc2w3xCtSM33{?o<(hI>=(DVeMrbxx6WaDBoRI95saB@}!a^h6 zfVfuj&rFgqz7>(4vwcmWvz@6_uqA1^>OFd?OWcV%Qf61M?~v=Cewr#qe7KEimuF7w zXX$R*Cd$rD+4?zu)ud#?!+uH2h>>+{a$Z?Lqqm5+<0PG(v45tMXS+aUwyG1;5;G1k zByCPhsQSg`2#d)x?^sq@SZdTpUIQnI(AZyYyFJ?TEV)OnH7{1ZvuLGUK`i_7MbUCt zVqjm1f$W||nrfF=;bLlXn>i%RtM(sfy~j;;T=LOPJC+%Ltr>D;Styj5Q>j&E5yu^* zxY-N4heqbfa>-2;^iyAY#9M-xx_V7nv<;HXy;pK>M%StW>x8YJK`wc!qUDjFo?1ik za#}oxar1g)Z6{|nwySNF^u49MM`j2KU1)CSyl{2D;Yi+`g_CdUl-wIP+1t-mW#4vl zu*-=ho1sE~ao+6J6PCW#v7UJ!QlFBtUZv`VCh*C+BrP^pcBY%*brVaK6O#M0j2AFE zSbN$#S&Jkz8U5ROLNnW6S^0~sTy! zOhS6R%8PA0lUW(|R~4r;xpmLYM<^r+?mt3*xp(C7tk6w1ZSS8+AQ^%}5OL2~)2L(5 zcDy;mGi1?&nLksj@U3VjC);I#^D8vk|A_J&?o0H9ItiFf6p;To1b{pbx1gdef8klb4^WZl9h;fm>hmU-wu^oWcqnm-E zrwAhH%RXG9OpElkGL@Z^sYrH#m?7N0ygHEQc&EE9VBt!wLsS|Eo)K0|hQYkr9wH^V zZuew^1`8nESqgc%V9YmmVgl=laR-`Z1<(sl%(p7^^Mdo!Mj0Ll*8D>QAi;2d(mp7Rn?T&2#KiV&01>zib&=BP&% z#m!D<^?&GO%$jba=i6@X$h@O}*5mse-l${hH_*?v_p6&Xo}oQ4IW;pUEaOtLZn=9; z@Dcz9im1K$Mi0q1O^->&&3p&Ye??8NO6><`HLT_PE0niZT6RcV-Hfw?pe|oKkzrGnsuqge@X_+>T33t>VYhS}r@LZ3AX9HJKTM z+|-(AOc*3G5g-%EJ=czZvCZcux=n7h${%JmzpH9b3zHq338w_Zs^rU3`~2czb+1uw zn^NOXnx;82E(q?~6HD%KE+SOTGgy)P#ZzilaRl_uEG7zCV{-F^pbmy&La4c-r<`Ks z`>j+4U5415a-~q(6OPVTCMG*UqpqE{i8z`Df8`v!d3vfT@zWQ7QO5rOO=Q%D0mLp! zeO3(Arv#sFXh((dCDn8DPbk6bEOLD%>1H_16syeP& z`NyrznUzkbA-s#LsyK?RCb^v!$_-s+3{`J0YuSfPie=e#!L>wM z!y&PtH-z7R&j}Fg+%GON6xlYEw}34n33S?v9o(+66aN7D6EflqnC4e+i3{_( z6%(?VszlYX5VtHUV`+>@ofJerhz_~AnlSK9{2HCuZoA4=cclej%6Jq2;IiVoE3nS8#vxkECV2aBM#+(B|)2BQ35>MaR^Zc zSC1(Cf1BLfQDEPcbF(ukDiyFu&kyztFfxz=!U$Odau_Cik{GTq9`D}7DuyBILR_N6avQ$>@&y+PJWcCFxJQ1f96);857pX6X9}`nM75brn6l=%s zD{@!rmX?cu;p&a3jRkfq6~VbO_ruWyWs;FzFI-9N=MS7l9JMI*t|PiNqL`V!q!hs& z;)CLiikdS&xHjH-m$JKt12h3rLQZu`Ir&g>WYITGCE?0B`H*r=i!C+fPDTF!6mv4h z&G5yBnn}%kUMS{eQRqy`xpyMr7|A`&Dn4YE0>ff|2P}}Do$+wvQ^+v4pD)D{A7M;r zCg$RYLlhw8u3VNGVN9bbxm!Tj*^LPzW;91VE~;@RuP7YY?g4=Dnw98+`AKh z6#gO{;jqD{VrEo-C}J_KBsmWTkm`&O>wa-{0jvS$bJZY%gx`^a&@$p$4duD)k~3xZFzyLTe7^wY#RUjy!zBGKM}pP=01tc%8sUlAISYCFzZ4aAJxOUf z6f04DQ0Rb;Qrn!@_Va1g39aXwOx!+g?IEQ83Mn|Ck01RgNoy1L{ zv2j8W+P59LaYg)0-HN!q`gtXz!Z3hYNhvo7!{Uen+;A=xqo0N#!#5E*r-lcAuyY#| zdW)Y7O9NBSIHKZ`z+9C)E-DUpaeow8P10m$tW9F!g|iuOuMCiW7#3p836ng%o|pz= zl}x-n>BL=7O67^SkKGN4*olo97Lz~l#aMPF(4Zl(g*n^!V2QXg->g#M{wNg+!VA^^ z0K)`Z2|Gid5kxL0XNQP0>9>Y|77I?ywl*GWS`0?_3{Z{Xnh?ZaVxlx>cfcgDO4|T^ zD_(VqBhmq3H8AM9Wv#_;B>86%OQ_Q1zAAmoRoQ`N?N-7P_M)N`uE&X-lTYD`mJJ(b zTPt+n8+dMrpuE)Zkcem1$_i&f;!MmIxm|F;q=#TiNM8&XGuT-L!FP0j3lJcig1 zYIuu-B=K=X2uhb3XT$KsAida+IA?IW&o-iZEL6tIV3&sy4^#~M7f^3n=PnoSvQ;z5wgk+6>N+)6-ZT|p<@3jMcmXa-*i%%}7gDenSv@^Eo4aq`xgg{vj z4Y0^!<}`U6?pU@j)HA#y_);fg*{pfHx0!oFy-_R-#)VsVGPc;O6+m0e`CXhza@hot zTzL-+4&aqM*BD66Jy)MR1{vQlEW177MUmi01O#+F~Fg(h+r&B z18}t^QPv`rYJ*1>{FmJZ8n71z&H(Sa5U5PY{<8Tf-GGvSov6;Ffc>6neZchxQhK$+L6eQrxoI4}?+>)3NE& zTxoA~=hF#sjKsXFc7U&S$p|4}m2&A}&MR;+eT9!7C@Pm@nf){wyK00bc@ar6UeC1- zRj|nMIMZzycX$puU3Vb;(K`mdCbgHOj{8gg%rZLx5wcE3TwAqQ!caSsiAGrwwQaWI zidGg$5qg||k}ez@8_&?)mg<%1=|^v|$tTQ&eg?@?_lz>3B=r&!bj}meWb_ukqq@$M4w}hUO61RvSnNe@^$ci;#$EGylj-Hd@*4i+-VjNmh z21D6t?OkzsA_^z+fO|_F#;Pw#zjil)k7E9Xl?uCmvi+Uv)BT--BDT<5QAd}QR!HeG zC%bf%?T+k@owTWb*;Av)RHiD`A*VxQavUrkJW$J<@#WOBCs41>% z#mRDihVfTy22^N1p85!SJ@M{$+ZEFqCYIe92;!xxsmn!*4k4P z27hsqBnBkWv+=LBId>1sjEk2#H+(Usn>vQo*wz#N+`Qw&-O}=;gZ7pam)KTT(YBkw zEiq`LHl$=;qbxA)m4;H6IlSuO_G3b=LW$XLqqEzMknob*?DK;KUuQ9G;|!F4_9vKZ zEiKEd#TmU_6Ts70vW9Xm70~2unsK(u_W0%WSTU6xw>fkbh@m&d&gebJK{trDz*vX3 z!Y)A&TQ|M%#p+6#(D9jFz~|y9xPev<(`qI@8-^uI)C3&G%)C%U)C+Mf4AZJAdj$+y zc2SNQ!xiSff!?kU^Nu6%GRqxIIl4_->5=FXzKi&BmsBc$x7Ht6I-o${`;YRk4pOosK`5w=l}KG~q?zTYl7Le{-U$Oi<@Vhqrw^U0x_l zdkjY{blIhs0yHlQekfSWRFRRoVKchnE+U9A8)ItWqk(FBQBdW~8rx~oo9O~1zl5Np zDUsKtt4YlEi9W6`W(CfFe0utBo<$D ze+=K4exQT`f>&BG%YS3Mgn>?4c|b&lH+V_Ixq4uT7Gc47H5*5N_F@oh#LH$x*?)Hz zDkA5co)dUPM$sIH?t>&ar!H}vz9?3p26H2s4!>qB5w~wW=I-7f;f4@#F7R%0UXkj9 zl>!tX%Yj-$%i8(ElxlVw%{G8qSV5@!P<35wmtY#l>?>_4aekp5e}ElYTKU}h{Ze8h?Nn~;3&_M%T>sz?OA+H|ZZObf<_ zRXTmT-QiemLQ)oBU3l}fOT!l(i)7duhD3r2CL|-%2T3L`Vlrw)9=KQR#8uG!z|5#N zTzk&1!vr$&3E>hu6mI?~fH$$fy$U{$x{syyE_kO3c9VsFsiY+}I4!7?{$L8JuiYIE z9|HYZP6y<_G`xP5@;-kh+3x&zr_bo*l961uUw7sd{EASLo*~f5jZ+GmBRusd1#Pjc7xO~ zHUbnYZG)Uh;o0|H1Wv{fMH%97#0QJ3h@fgnVRG#E?*x|bvl5{&o4s(C8F71DFdBkb zO15MKsngT<#efE4Y#Cct%gDX1D1~ka+Kj`_22Bxvi2Nnp7hi?IRBOmUDW2$xh51P!9R0t0WfOZ$=`1vo)8&7>gU5pfTMplU)IduP-M@7K}f z9xwiXf`UZ9IF$+=h>6XJ;VFQ*${PYD;Q~?D-|k41ke4Q3u@hYMQ^M2I#o2ppHEu#H z98wJa_*){6j%HFCd(h^aIPkdDVs*D$;kcOs5+9b4bPhSpLE;ZYy6l)ZRN91u@mjnj zTcRiyMjV{=`6;aNmd$H;6#oG6ifqFrcL|AqDKuYtcf#$jBUUWxAy0OYS2Rxpehgb^UYA%(eslER*-dk+*0*@)^Gx!BvdNveZ5}4U!r0=IjAgf! zx|0*Y-w9gcTr2NLwoMCkb@EZ<Zt-XL!(mO!n&Qn4KuH_6@+(V)2(yZxV@l4% z3v5p<#lmBqyj}V0qz`g3=oHg`BFW^g8;W20cG{dVT9VRFOg8MBB5lkEX0=3y-YmQR z_#0F;8%#KyB*|RcoI}FCw5}x*W}->P%BGe;7pO-VRA#%6$kOp4M2 z=8NS3#kOWXaQ!BmH<&8en{2x0=9>x=l}5qS|CACmRgi$T^B(%bsHpl`S||XcIJ+8)ff*Sj%P7Tv3}h9{wT2 z=(b^@TQ9oCiILaE`&eB&qS>C(Wt zVV9nh+olF4=f&D=>n1|*j}Z#wIcemp6_bzYHI!myHeaKST6+oMfs&S~3x`P+0Cl0b zcWccR7n(*MTl$@ULpr3`(_3JW!S73q-D#?P%)ui-a&oBSW?6DDROOUnPQMYWXz@BH z(T$bF(#)L#PKPmzDDI5>se@@OH8vG9Ff~s6(@%V6<>7CKkV?#^-`Tw%6?#};oJE1_ zPdiD)RMJY#AAc@n(^)Ah%cF^wXkLBL&Lphjvw0S+POv_IgLs~2WtEIUSE*Cor`PEF z+F6w49trO4EjT&NSTO^_p9pt5j@5j4w9Q_K9|Sw5gf zZ80i;*!^E7%O^Bb3<&-rPkbkA7GH7eWo*vnMW-bQZM3>_fS9nAmhvDZ@T|Tlm70{O zOP6AobaxKggifHTYKpxH6&TQBGkF?w(mT;VW}93GDcF||%i~GsfQhCFyPsn}8oHed zkcZ>1lKBMu?@dpXn^*cgXZClt3bhgHJxZT{N@dbIaZL^6`zpE$Ij(Au-V?q?t1vjZ zXvi*K(N0>EEn~9e^Q0(3R-lPEpVKJot`&g|nWQT|kM#E$8Gg@eWyAryLE!oOmMhZP zc5@h~(H5g4Al%L!dHQ_diW0OLxFq0mMebBk))A8ek$D>OG-lK$F%Hd4lM>NRpe31q zZ`f^Ha^GoDG)O~n)8uW(%vk}le@aQJ4Pa+5<|cNY^ts$pbMGyMrdFb^N{GJbIKap; zs^@8J2kEKByC0`Z%i`KR?BtnBkFSt(wAki(A!W@0P_kGn{fuq+^^>x88}ztryKEA! zQ>N>Z?S#JVp=tKeNW0N8UgYmp?uFEU#g~T2Z`v!EV;xVkJwT{R&ojJn8aa7{5i5{X z_!p98rhW05@aRk~yp(SC0-vGD4BU%y?aMcljdphwv_J)F~@=NxZEVNSZfh}m&@$p@y7b;3LZE^gf< z<*9d!A!}cuCSiK}kwO;UbEnWZNMnWr7zzWNGT!)n-z$rV%9C%-tQa@k8Dxa`dD zXg1n#HNVy)d(&H1OChk-PMNM_{PreS;6EHWY)R%W7E4!B$~Bf)8=JaV#tovyU}0Es&WJ4_()PHc!RBt~tbPASE zZoZ_dStmGc3VMeVMy56QmP_uAd~`KI@6vD&)3b*em-H%bUT$1IxWhP_M$#|B*Zr96yf$U&HHwx*of>X1mujq1yUT}p!*VA){Nu~< zm14?27H#ygul*~wdfOGbqLv-xrCQ?wE+2A&`;s$e!aW=8d}H***%{z{rPdJy#ztw? zeW!d`Wy|v@=zcx)wPTsf!_;x6CC#e)!{QQ;RmPovt&V5N`X%&X;#vH4=xelTN{gMc zTMv+os2*lQmS(vk_aq~ps~tyAjhUy&`g`jhS!moU zLjAgbsVBd?DAu`BNllZLXA2Wko49u`TW|o4qAIrM1C=%@y@%T&pK_at*)U~{B!l<{w4H&lf;J1seMv*frEDlwr7_=E-d5D^Ht+I+^qtkLPialn3W@lQRb#9X1h3PXnH!@A130r85*FY{E z3!JMSM%lo7JH$AWWwfVKXWl2cM&{MA{qP=(KZ!5(H;&4E7O^~ydQNhz+^nlini&B& zghiBX;c@*ovjVvq4w;w3Zi)$$jb~_$fyJg#yDIfqH{~oCY?xbze9*kzyd+kZh){@s zaFaI5Hqb(Glh1Z!)vLrDLkgD-Sg!2iR${pH<4sIlL?&TzaFU90`NL6mwlr;E7T0Jj z&Qhi7>|1z}OHBy*<-lKjcmDtj%%fD|epiomv8~=q0zHA}ItGpx?L-khfw)l#a3nYV zm@1WbJ-g$HQdsuy-vS9p-HO{Ejq+j}RngAPB`&EZqGjS(Vko8}oRPKMkYiDuQ1g`Zvq9>Cb{NU9Ln2Hc@kTs?88k}M zTqb~jisD^B8`AL>EXXaB4A{mAjSs!(qj+&FLR8heV602WkA` zSgDN>vywM*7&R)p8NEtZ11QprLON=jY30=$aWGXQ-=^KIv8@`emsJWVmn{LC?L;mZvQy?QPjL94B}yfv-pKTViP%Z|iTYcJ zT;P|eZN8-Bxm*#WAF-t*df15o!I5rx=29&?3>moEkKu+#a+-91F_>{gGg!A3VkUXb z;prLOM7Y(NF?h!)M5&inEHt`r2ArXghTLhQ!Ahoi#G#=OnPx^M4G|hMiK>By@U2RG z_(o1u!@JomwEgj;kB0eb5}ZUe3AdfRg#BSDbn$DfeP4-&n5u9&Id_znk(x5L>!V!v z5g)iNCd3Y~$6;uHfKY`*?eS;tgsATWk+EFUvU6R5NW>eMKz806vRt$LiaEKwj=vfD zP3?(^T6lxm6d&9$5gSo$=2e?d{797|F$t-Bj5NRTLYNjnqrm2zQ9y^@Ztf)eTu}yG z2?k}K1Taj+nMa(=L_~d`bO_0f16~sp>5GO1dPVPm ze-Qr6Eu6J~U`81H<;Au-{PU z8R2b3&uR=vqRjM8qJVfHg>!&WIz|Cdv_OsB)zkh_C!}H^P2ky*bAYvmPjMvTlH{=U zU#Nyt!yFGX*EPfq0QJNUQxuYDKb0TA>Gj z#Sxrdlrx$JX~XeASQkwg5+?p-4W7X(hEP*Ojn#7gEcD6L~l!1JW=I;#}Pr6huT<#S69J&B6N#}3Ji_A8`+mvfV{ z<@fIcC~_{wvDW^Wn3aq-=#R9iw>k==DI_-aWDUB1%!W2cve#fjLSV;M_)2C^&8{q5TMKKd(rz0;dTg3mgfn} z$BTMk-e$in$|gqGo;vjseuQ*?Nm=UN!Rf~ln78T3fz+-5@2?nsBCZqZ}D? zWoTnr`bAU+w9sUw8##x&8$n|YsW|(DndFJhRaCN!oR^~y$2})PdJlaTQqEUt`15Lv zv`CNmnqxK19edkxJ)V^J#7SuCF>*N<=~Jt=_34?1Jsog+($bHAH7mPmZ2-tj zlP;f>3*~A4p;&m!@;-iJ&rfO&(ksJK#BX96q07w+tWl-qeL$e7s%C4FDy_WNM2^1; z@v8kdZbkPcbeP5S$iKH@ostPqrJ_*JjQ{wk0sg)0_>+&o*p;CO4>fu2G zRS*TtRacPr`=UCQ?z7TqeSrE$4jbAPQgW!~`f{NAc=93ujkG&ORTY@AGb-^ z7$X{CT8193Z5s?THB_FH7VKi8NJA`ca2xre=5{Yme&z1hjmhJGTP>_>!|&34F4||I z1_6KQ?x4=rx3%Z%11$Oz?~%|+DOt&P5V+ZN3~nwQ(O`-rK9~trEu?Jy zKt;+n2vY ziK!svsXKJGEl53Ou^kX#gimnIkK%|>xPe59w(qqNisIW55Z8(zcCoPi95xOcV8<-> zhlW;_E@)FmWP@sR$voBxF>D7-ov%-mp<$PqlcBQHGIF3GB0fC3)->YwGvr)J(uKO7 z_V!xf{ixS}VHK%l!?o9>jk6x8C1O^_*TMLes=God!sm!#fWNWNA4?_ynZ%nAr_?2- z$>FBXAv&YPB8#SR&gLTzI77&KekE8_ zPKVmwB~u>>Pg0x|UXfBE_rNcZr-Fe&&SqGy;UOq{oV$+3PV*G-{{ReTu)gL>Tt&i9 zm*oc&qT)6yv2b;M7^TQv1f-j*Xr~tp6@hio@re~)ei#>Vs1fFftr8tPJTXhuT)?-a zbCM)~=^_;$iUy=){iSOSk7LD>?H^6{ly6$fmyNXDM@3u^q)tPYjs6WI#6kn6+Y- z6fI^Gq-%S`6D} z*a~#7iYjM8_8iNzyR?u)iYlN9dTFK^Cd{q7s7E3%z9mZB0dBC;7@)Z5{uB~_f+f#= zb_>EHd4EV*JykF@;jq0)aP<*z#4FSQ?P|c#Op4oKg!L3|bqA&y6gLm>@QB4sY_Cv8 z?&Gye?-#5qf1C`cpg{2x)(Kog)LtL@#jygExVRed-O~g?DA}h9_=~6C5Q2=4g*)O9 z#-w?vaSv6#a1x{xM`C9ZR3$Kf+s?npcVVx^7u*p7R+yqC*7sXHg3dA6D1iVzZkh9O)&;IZXK`M5jsFlNYydxK49(Yx4y4yVLK4x@hQexozUcl6*>5TTj^{w2A>)P3`+jF3R?WH?!Kn z&9`IBI6Jw9gt%Q#c75p#h`5}|bEmM?a$6EpS$Trq1Av?tA#Rt+a_@{;v1sL1qro7T z;Zi*Q&BgbuXACPd1SYC~t@UOiMVBu)U^NKYS^}fQzR&znz)Bw2_AVPjlNp+8Ei+mk zA~7%g&$)lj0Trjfehlr5(U_9~*RZV2!xPk*$#$75O4ihb*CFpj$y47Om43=}vA!qq zVz)KBQr#B**@Ho}xJyr-i4r42RT_Lp614%QF}RePb(L*UK)_UgNR#G~RxfQOvkdK5 z6k&G9cU+onp)MeG#c+v!N|4L638K%$x6yqst8#NJu{$d#IVdA(TbPk2w7yYj`|<&} z*reF_Khlc|)-6egou|srZ2J-!#jlR@x=&AgZ&}ZpD{=B3^s3pqW@h za@r30CYr>a#*Y3VCL9TQHpzHecvplAr#}n>DHaBTtun2U@Q9V5Bwwocz^P#7DnQsc z19OFS+(evzURrs<>Pnoe16k%~Y7GwXZ4&7sE-d}Y6=gDH&ZcR&T$^N(=K!~yR}NQY z?8UqNMf${qwFXg@6x{u;CAgs%kiA9`F@#$dN!BKvWNy0GMF@2F!CbV?(c}V6ws(9t zkqGe>$Tu#1-sm-*nr_k@Wo^}6d=-EUT1X*#D%ZPoUiCvJe;S5#v z9L=ITka1G-A3r$Bn~%Pr(e!L;4mo0rGkMK_7UEz~NPtj>?~YbnZ6TtTQ`$9q2h~Z+ z5lPQT%r|k4f<&G9FLK6M#k1&ju+i=-bh39CsQZ~$87Zisy(_efm7ykfG z)?T1p8co6I{{V>V7$*i&TyC*2%}Wn}%Z)}tVMW3PJpEvIrezs7G6|#gpjP)~ z<~jW1rR%M_PK4t(-U-`tSfEC7W@=o2rtT;2DW=>k)dhJPd5mpGQ=a^OuM>LGaY9d3 zMqW-}8!{CaM|bMqLgPcdti^h=%~As`XT+pVk)9)Zk2OuE;wDC3agO^by7KBAPcMeLK{{169T2?U9@Pk?h?N~*lMnQAo^Y;v_PXbsgF8QC%?C5V(q;!OOZ z&opZtRF!o$Zq0Fk*Cu^&6uA~+X3(IKA#)vjmfd4!9GevCuyuaJc7PQ2C1sg#CCXnT z7J#~c^;%!%pD5|0teCS{>0CE|i;8-V%iG(H;k;}f0Hi=$zY;}WG7l|!hq-l%pJO+r zve8SaH$A>=iPt8_BHa!~SudJ*^>c(egs4wYX%jM!ICF#>t58~Kht_46f4ewZgw-Bm z3Y5(bm1`2LO-zuWwgl%8rzKSc*Cc`wn?8zzl!@FYJ~c{I=JKTVy{cYgA#QUkf0Io5 z6%f1N8R%e)*C8=R2d3?ck1 zW|B@+YD~(G_VD3!0eKaiIe^P63koXHl`}6LNDC+7y`z$y& z1&LhK&E0UQjl3dCEB#aVMvZUCe+R{~18&}n^sIeHm?f150%@zqhq-^EE|JU4h$U(t zC;c;h4d!-7il!fx6B$C{LAR$}V6f%%`dBpg@--F>+QuZ=M98BvhiH!z50%Z9QMPfb zj$F)!<=NiTsTlT6xw^F`YL;z^x-)XVBhOE}IK`WV`;FN77dM8<_Vtf8e`aqzh4BMb ziF;JH6O$W@lPu5>$CgX(Si;wW+Y-6f8y)&t>bp9Yl^kJ9#1g0^(7I&W&C7A2e|S9d zw=~ie*_Y%c_^Tc#Tx_T54cWHbsZG4)N?N_1^Fp%^Hp7k-zU-8m-?woR z@b@l4iJTq4twu7F6E#UUq^Bh289HsoFSJM?NuGQPx&k71B0#dM4OAOEjZ6U{+)cT} zFN-MSqMl0QtCD@m0ItHN3Ct^vf`Z71J=+H8%VQ~8o5i?z zTT#n^vJw>c<(z6vyCGiNX2t1SZo_U$R0p!oi2j8*E%6HbIj=O%3r!tr3m-gvCA}Q$ zdoZjyDN~g;Omp2#2NO@HnlCT9I-Gt=&r1k|Ij0y?$+O)$-w%jLZn`(wWC@y@s~KTg zI+C~mGVQ%;^%oDme*$qVfX%-Xm3<+$OFe?;{;^@ODLIAeXs#BPGBzqVQKJ*Fa@ZfF zHCDe!dRl$W;?r6dIC359d1RU8%~Q@foc=2L9nKC#o|ZZn2guE0No$#JcrDAP2BbM~ z@q4t=IW3J;>?9joW>`Gv^D?vZjN7{&766ga^SN`3FUS?ie`9Oly0>L~h096jI6w)t z8e-IdpBZNuDTayJq7mMV!dlkpJyGlJbgVfhqB8p%GL4HJsZlr2 z%saoAXr{Y7!)W7T+OAGAz4?LhB7N`?Ej>W`gjO_^b?V5!DLF3O!3~bgHJd{-Hfh(Y zWPsy(DzAJ!e;0j5-ZnxIr>8!lWtKOkD)F||n3hi5z%T2SQ^{^@m3xXE@HQv8DM{>{ zCeZ}DFB-ep$Ant7bj=spJs069EN#*@V`TLnuH%!0+hPp3$qNQ1HjpXZnElIgYu%QR z&f*&7-ww}9IVLiWl!-anf6ftbH6yuNv(wwTXPdO;$yzb$ zXP0*Mc=`rY4iYXEf4nBcf~1BDo@k?#dfpc)c^LggIoVB* z=r@Z43;Ih@W@aV(RJ)n9>sMMti*FKp)0#&?m#3(IbZ}hr~=cINd4&N~*P44?V+r%xITtWwKpM9~7+p4CF ze>nEuN6f!ZYD*PrRYCF*BeiWJ@GrCSip2d&UnM$Emi6YyYjYQI73*P`*T$O3EmEIQg_=rt4G57dk z%EDPXYB!qo*bg7Za<2+Q*mm3EXRHl>KL$E1b{p2L;7e=&KQ zgsT4l;mkOfHRYnl9#0K&{A_XZVEdXE8f-rgZ0*WT9)5=DHt?WB)!EPPG&`nqOlb^Zqhe4h)e+ibqN*1Ndw&6tj1@tCK;nB&vsX3UTo3Q?5+0QPf`+T`gzp}1PRgMXwgeAx-{@Pn#witdYGzGf3aR0nZh;c zihR%OWyviu>2(*8C%IBSkHF=wRXlGej@X!mbB;EdnYIofB1|uc6-)Oel*H#3vYLHV$8z3iShl>&2R+?33G+BXoAG8)gmZSCIqgH2_In8%xW|( zenAAS`9-OH21Z~;HDY2|e{e4kcj1kgn`4hFmnM zYJ-uI{{XFHW-Ov7v0tf;x_eR8#3pBDexWaQ-^Cjfu!mx~UZ(H3ZHGr=24*SMN+}8S z#;r+~dyy|uONCT^*9;cmFke=ec&t;E9gjIn;EZmim>E6UjzbtSf50kC(p%Ji=;U$~ z*^IE$tsPgKb2BNi6q={pKV~q`V?rq{E+QsA7*nv=>`6wv**PQ6w|O%9UAiY#3V5vJHnYjF<03h`C0`OQ!vTmM$cZMid8IV zPhEG0*lZzMfveM2=)B>&kSgFFdh3aQ_@Kg3xXcjvwJ6Tz&tYov3!0bOfME=ysTsuw zGA^I+z*H6{f9Z3VXoaj)_XAK`sFRl%sF(ieQ!!_o_gxw&Y)+DLnm#S@bP!U zbs?2{q|v-LUBpDNH5GG1?T$yWWAbX-Mq_s9{7as{zB#<5I($ZH#BKaxYcuX8#$GF- zaoiHFf7^eiK)e!!w7;vGq*of&hF=cwY=yaT)`4$Cu)Q zPz(VL{{Rog12Uu(;)$e?@Ii!|_Cw6)wRopaf+gM_ zNyJxpprUp)g_dit#Q@*5QpUnICLH;Fm#HC3>CqC;+t>c{{Tx7m;_Ly zf4jX?{{V^#!BoJN+Nj+C&7sw(GAj<%i?~R-FxZQb4h~rs=ZE%U(L~zAYj=iA-vy`& zCt}!c-h7MUf|Uung$UZ_N&GQF>Mh3Z43onGq^LH@Su*|~W*Ebg0hbBV30rp*SlR9^w=L0N;&x|I zL|hjvQw1@z!XiPEmrMo5q3z|Tke62!NsAEnLw;Tu3KZh4v@?bQbHZGH$i=8|#z_RU z`9%^`$h*2a{vYCk6xh!^WesecR&OGGdxR}Tyke;qa& zU7;;*wJrVdb2=#mxzV9~Far!-YniM-BBm}<*V>DpVVjX^*u34V#coiUVwsG&E-25w z-l!FDlt7B`qFw(03^#TqMH7gwT$hOy0<}u&7TXR@S|P+lR-pS(Y~t)UnwBQCG2{OL z3=m?O3z^8ezr_~8R72A0;ASLaf5pS`BrkGJ)Wa=XuZknE9l{WocZ%^n{p{&{hs1=s;Z|y*{8qtiCYA#y_xkP1+h(X=C#rU9I{-UbTDX~&K zllEalW86Dzk90AZxQz~bKL&%<5H8|WNKCU6_+X|5x`^!LCSQ2M)EIFNe@fe`_`Rq( zpQvqbh|%FCGPV~HvO@^!XM}C=!^2P$PZ=nQSrt8u?!|)x&USi}A#R%86);kps|U?p zX>|CZFC%>|n>!2CyHhRNJFzbe1xZrxM|$gWzTajuutkoU!WTkagWMBRu3Q;F$8gk? zmC0qj;}1v((LR*eCZmY4eddD2k=C4_KE%wR(JC3{K<^y*Bmk9}Eg8nj2@)4q$)U?KKPg z$HZ;$o@=B%M<+?PFl?BHH>R{h{{WY&HU;=y6#oE*G^j47+4@kbe-7ax13{Y zM~S{2rK)P5r5d#{R@rp~)QCZs-Z38zW`dV}_Qn>?qjDyI`X%FOnl>}+l*ZqjopWW9 zlxZ+&wIX!fKtn{!f0A)(vDCLg%VVpy6X?@N*?mxPW+3eI*pNcji1H@sfsp1c=ilbA zmz6VSc6R9^JtloUWUw1QR#T^?MrGngh|H~RtkhEF%bF$G=^55rBYI2n3(}#@ew5Az z55lueRkn?S@g-uU>obQdASUd|HmSjrxvC%~+2y8j>o|Xke_lHEbr>I+zMpkDu4@)Z zwz;7XPlTgZ)&(TI6vlBeIZ~-hvg1!n5oT=>Adx4*pL}O|>NeMXLMJMK^OH=--pr$9 z*_|$V#1|c3fpSdO%f1%gK}Ii-XE7)H$Xc2OAM~Uh(lNT7SCee@;(uIVU%hh)xo(iWOs-LER3T zW1i?y<<=e|xB8M!899(xQdk))hMhqni-=BscreR}{=#dD% z;?PF?k|0wCTV6ck0xI0kD1!?I&v9dzYP`wse;pQX;t(#VB_zU~akIL%`dVGh=L>>_ zUleFYGQ53Fp(9)g5N#F9N5rwF!Y3xYi&Z|P!+P%0jK38pfWb@lC#*>uUp|x3b zf44{BAkRWb^EuN@dTusNsT)D59Gl{+c#e^gmuE>km&`9#-!dpg)08G+q1d`h?k3T@ zI_*y=R^T9l+s-}fc&_P+m#J~wcTJ_d;9jltkMD%OO`BAT#8er9VZgTHFAcrWt-+Pm zq@c4!T3qvhTqSqzjTbr3heN4)ZO>`ae=(nM6*uBKDjKtI?L}C1B=OSiJFU_Pu%B1% z?ob^*D1|pr;h7A>M((;GrGKdSXr;Nm1bA}c_MkT-RR<%6yF{4~H^ug1t}>>x#FFQP z@%*_z!pS(CQMZjZ5tTH@MPUhcrYTbIAzg4>J}5?Wu8c^|N~s1`-EmvP7_2!Qe`w`B zWlruJ$N1o;bUGY3yf1Z?s|%jPEO@1sX3@JyLokhOaU)MAPbDrAh|DZ-RP%3`#jAv^ zqaB9OE60do+JIXipR3!NWRQmM_*NPwI)-XO*a)^=JT}|I7h%^OH!F6&C#buwHQpu%CBh`LC6qd z2}Z|tYX1OuF%^OqO}tNJf>#$!+#?rK;}aMehd@i=;V+uS3`Ee9A#BLY*??%!{=`hd zB;(|^-Nc`iISJU0t2RPq!)qhObW<+Nk8Y!|Jq3P_k=ulwHx*;KH)ZgQf1YPAPBBp` zs_>{9_n;#8cgI(Yz*y?ad5_J0Z_@r&4=?XNjbYh2JU@q|>C~AydR)6D>X>P}1*8-d zPG0W#=scbo^D}vqbXt4$iNZ#R2uP4fi-bqwidERCR+8UE+5DX*WL}uEd3uRRct_-) z=M9WY2DHc0ScYqMzNl^Cf8idHhK9b%0cs4nqPe^wE<1dp6Vws)z;K{5aeF-@5UYl6 zy}P*_ml0AQX?esY#jR^{d@QwpvR){K&QK0k9-0EbhuIP77^(fGTx?U03zpgick}na zFYW@u_@7`$cy4a`NVrI|@Wrt#WyiO6qL6WL+>s?g6x)W`*rYd)e-)#}tVmJdx`qbCJXgFK_Yrkn#s2_ksA3)xcvjZSc%I30j9o|{eqdq> zTfTfkHxx>c8zfF1N?i51{W|K0{NfUP!+(mRsgt+z|R*c~hZyo|0eV7(dy(;+* zLX(p=sa!>$!>mBKWjfI>ha-fBOxy9XyRIST3te1QKV4~SJ|xsydR~}-{HOV97l22o^ka9s=f8cFuRv?tFQ=6QSd8eh`6brzUmS4Il{{U%`@$1M@JRYsJE3Osk^8T=<3NZn4 zVdnW0qyxohcv2)dQBczpLd#cgw_sc)cM%saC?`_rGOAM;s~&0~CPG%7yjlD5jQ*AU zL}kOW3UYwa9_*Rjx+I)M(ph_<4eWEXVIrm(e-6^v?sq3@+)XEZB_Ylf{e^l>Bbj=%<9Z1JA4uLe4a*csMuI|16(p^rIVS6G zdqdJ+T9NTTgw%(^3+mU>nFL^ys#Lj&K)uT=sv z+B^`QFHf1rGU1EbeUG`lu2|k7F}#mOm4c> z@BQNuRBMn`%rmSN059z}ydKm!a~K~o&x(+Eo6#g`AVuG8){aC`7deyE(W^05 z!`_hs&Q$&@M}{&y7--#A6X5t!mqW=IR zwo2Xu#O=1evWYzSwRpCIZ6dCk<;pRq6OM z6%{@3tbfW{j7+*Zw_^KeVu}?B979D6Nro{*s>@Hyj}+Z8#cV@-2)y#=lxLiOxTlNs z4EEbe4@a8CK0-=zVyT8Ij3e~tUlnY{Q;-@ebN>Le0%r8urrs+w^s#5z{?Z;NrCbTa z6>o~$LBZEf{{XceTzR^Ue=M~+_h36p8icT+6uWK{V!#van+@iz=(mS0M@Zi@yhbdj zTg$XzvvNXYrCXVoH6eCY?c;8Vaow3Fy;(;xaIeT8R;o#2?V?fejRszJqC`^mCBiK6 zW}0M(s;H~O%^_K(U`@}8pGohf!tHa@OAu4!UYM7l(&gBa28@#8e{e%H&+jki6Y<dXg?D|9KET%_9}*=k>=gzoy*$wfL% zHL>OIPWaV6{RV9!fB75B0peK!x#?!wZlK`*0Ol=0bh73P#ne}S(#^@QZpwC9FSb8tY> ze|AcMSthxvd1o2kT0BSN%l0Dho*=4I!d_tssae)qY#nP+xj87JE~dVH(A-WPNZp}q zHcx4Wx`(={R@3CSZXTfDqM8-fe>N`7c2`8M;h~bNON%5#;~k(093fs{s)@?mhfYfsmKI4I5TYcYy!8-m*>ho2O4an z9W-`E$9)!9p4p`>#gS>ZW*8U9H+K-8L%eV9hUQUzf1oX)dLB+vPqb1Vnv+>_LTEsc zM%CyqnlCuPlOBd)ndAjH$Ky8y(I<9T`g2kG)U$0cxwI~ zCY0oF$eW_$U7Kxsalfi&ix1aa2SmZF*eBdo%@tEj^5qztjL0%x{y*qmsjh7+V070i zv>Z|OfAuC*)71phjLBvfriq#^UjFzrodnObYHWw;ykZ0q|f4YGwEacS1VpZX}3wY)4x=5+)4nms1 zN<4f;Sl2k)(%MpwY^4gHF!iK_M=(3Kolj>W%TK-;g9|t>l%`YtI~@qz4j`1WfK%-$NP-OB<%-bbMiaH-MUM1+wls2Nawe^KdaD{2vy5cKsf6Lz`)}e9A zsEDv1tjT=P?Oh{U&`U!}u<-6Idk{~#Fpe}TN57cdIc?8npm*rt4yc{*EZZdEf6=ii z_=tv$F3=3YHpn9aPO^>(A?gR;t4djU_Q~ zCm=;mB=1#&zK)h0pAb@VFUVGzQr%1(!%HwEM3W%+3Kr{@xj3GFAxLH^ST;^yi8)6% zo_J?w-UdWX1=fMPh1Ur*EV9iwe>}Uw9J6mAI~J;3^)x_HN_Jqkw-ffIxpO{xrfX{R z^MTU*K$I*L`Kg&OAg0Uj72;QA&GEFg(Q-7)Jh^4BbX`RE(45Ry&)2| z)#L;D`9Q^hW#5T8BSV;%qqr=yCoXN8o34|JPH!4)xD!0P)-<=U1K+FMf0tyOm~K>t zT!;)vkghRkRTC|EGcJG1DO83CSZ$uoGUF2tGAnripfn~ehz5TvqEBMgNa@+6l$PD3 z@Y&-qAh%UK#Z^4wZUAB@7g64lmTmS(oDv^YL77^)s;BRSey6H{*-5RD<9V5PW+;=c z9MDbyKG6`N7W{5i{LpfMf8=9an5w{LOL(6R{+w?|);1UE+ghHLmug;{j-^|!Fw;Z8 zFK~m%o=csJlq2|eG^R$tarIU?S7LaYUkU9}n^Aqu+Df{H(fcQ!h40oquYVETYQ|IV zzQ^e^yMFY3!8O3CO3Y4SS@xYNsa&m@GYii@CGN^Rw<8lKdmUa%e=0SUWAuSbh8YT7 zVW#*i#7#f#FyQ!C&onIKm(Fc9ly?g!^J;v`^wy{=m{z1j=LoPwR~5Fed46#?{z`A< zLZ&7@WX>wgJVd=jOCI+M^r_;DkE7jcr$O6B8FGs>?40|R78+>4tu8kO7w*WTLPq*V z;mfA!KGxt;pwWFUf2OE7n0Jd%rLz4tV%tgA!NEf>dSqG03GpnoPf5b-UY{fAO%rEx z6k2SVw(~3&#(%}(Z;DBba zkyohJt-mAH3_sW|YFt_!*^HRAsa*A$s^{}8p~|~%kXfn^e-dnlu`frLm~c*FV&&d| z^taQLG4Zq*^50;pN{>QFhC`K2nXW$7%^`()lpiYa+U zhRL92$~?mxe^{A0nZ**e*>&Q(-Y___l28imVr9e6+9)N=DiGMa6WTP)#b8$MA;^B{ z=yBwZ#hcS(6--L(9m=C_7`-o1WUe12x;)3`XX-60W0Pe4CTcXW5m$`9E1_wTj+dmh z9sdCI(@)Q_Zyy&A5k~BDbr8rg!`P|Ws;IgpX^~&Uf5WbD?m8?m>@B0Naiqc4wtIwX z&T%~SmgLLq{{WPG55v7!;Qn70%5#oKt+By~*fJ}*MVBgjdET)Il4p7Dh!~)A${-A? zQa5)Dely6%#xPtwhA_H;Fb&5v)2cCLfJ|=}g;Eg7vKT3xjBZBj)ahA)Z);pA5q| z(~bqtate8};^B^#6Du(fJu7MEqa7S-QxP-r;#XunQLzxl@nQn0N8VkK{805%9Ki)K zO1$(KrUu{=m%)+`6jb*OSV>WC)d!|CI8_Age;s(T7{i)2UOZvs>FU|#ZkI^CRn_L6C`ffBB~x7fP^S$bl3;i;fDC-!>a>A}sSKF; zb$>`@#uzRp^@$H)SZ6TZp_Dl!5Ecn(-wYr>tH_r%hb!m_Q!Yr_8Qab^A*IdvPIHAhQ&p^)KKgf}8tL{aFYhUA1iKWZ6aqnTM;*DGM5fR!X!X5h-$VzE@% z3F$xdFmaGm`w?{pZsAyRv603POQ^$?&NeADBh$kK4M5CGpz4s!OhsK0vrJl={>*By z#O^YAo3&VMb^(}UIV=2745dtr9B+dne}bt@<7c=z*f3NESP|Ms?ukJ*v5^}CE~17J zr(=1~v(p7obp$;@!QxK-tD-SvY{bUa8*l)pc*zt_bpVp?d7nhLc10kY}MAPaK%=^Ll%lFidL%oNexA) z#YK|SCjS8Jj6M+1p2UQ?U(f#lbVyO-k!+D+l?tPGnIbrmIxBK9nw2Lxd*QJlRF9aK zw@#=bSv|+JSzbM1VwFpM9!J&Yf1?fRHY$_SQhT%&whU?^BFwO_F@UIQri}VPDxX(u z#*-)|#Wgyo!{QVO?*0uwnoO;%M;?_>?;A*lJ?1}i%)fE%Heu=DBt9-sbKI!*9n3PL zrQ9M=VWP36{Ujw!aXlTyw1(6k5?&NTC62EZx=o#moH3j%_=tr*e=gySRwGYQ zUc2<{)7gP8@<~aZ!E7m%srMerx0~5Tfl<}nO15Y0#cBgpQH<51cPVi}s_39SOYFkl z7_||*A=VdCFs9K-a=IB({VfG1_#h}1v>>e?hvI>$HqvFWLJaz#y8}rSJ|AnR?LkG9 zuEJa^n^N4S(|e^fQRY|h(aD}wGK+s9aCg$2-p)Gzdq6WC${9wQaEssWU2 z_Z>;R(=YnQ?h)|&;vl0lQ!6lesDP<{%rL2qfG0PLm&F%SH%Ou@J}(Ry2e+Jmi}ym^ z1_|CHfuH&)Lkk2?WMiKq{*c(}AZF$}k|lys0`gg@9~4@!bqK+!e`+xq%Ip*z>Q()c zi4$O$Pdl>j{ur<(ba~=ab22XsEmXC+jppq|KZ**YJBcZ)jFAWM#j1;?Pd4Y6@kNl! zXtrjb>0$z*2GP0Z%xPRe@%pv823uE8yKv*?Z zJX|`jOj?Gjdh3tjC}ha7L?dRfyBBK-9GqP^U`A?848H9$e_og?1|djWJgtjM0*ow0 z95Ph@01PxXJ#Jx=bc{g^ESVPrB=&BI9Crpym6~&f+^VUOOHV&$4to*3h4AxEQA}KS z6yS;mrQzv>h6G0EI<(f<&|EN4UIES%Qjc*7>BARlihF!dI6a{5BDjVmyB3BWX#_J~ zQ5BeB;b(Xrf7innEtF2~(9F4A5tu5OxSHhxq^Ke>Oq7Pwinr9ZKmyBMG=wbLw9d1gJp;Ky^a+paoW;rHL%g-WY=whd|+2HuKkn zMh#k}m?2RN?dkD&qFhyMChJ2;0y5NS^u?i)_+XS9e+^uCdE3?EhVBCrGXf!+@TW{o zz1Y_;J~E3)LjIHfH4 z?dbtUf8CvWSZorPb=Cj(7v>g6YgBU{zSPaXYj`lka#yV1k}Mpq-Df1!u69Pe z<3(qSDTvU%VKns3(<#Sbe~0>2PZXFn3tTzgHD%|ot+Z6T1J3Va@KwXUjAzxM`|hO zeWsN#7GL_CHF@u^1GY3{5>)AbJAmI)mp~UMr7t4xyF=4 zM7VNQ{&CabPP|6)q2Re~%Lrm-{T;9!NH8uYpq?F&c22!fn5LTzPB?FqjXJJ#Usghy zugW{@`D$`8)^f4Wvlyy%=QVlX+E~LGf7(qRl9p@{SSKZ(2iZBJAxlgO(`zpO0DL(z zu_#+7aUxAiI4-yK@a_iLqP!+#a$fH|z4@dya*}4CD%h~+C8vZOYY;ZrwnIBGD%|9< zCWwiysqWPvCm<-LF1?6~YZy0CPh4$^2;an7a)hf7h^b`Rem|I#+XflLkIZi7eWN1F$5O{Z`Z!@1rF|D`7|>=dW=9mYMscfni5y^0Jp7XEqc&v$52gqx!rP9u6=I13_A_jO}Ar-V?*qG+>=YAthFE90joE|PKN((oz zIDN_2sQb0d#ERHVe=ylmA5z_NTc^VXxd3v_4^cvIK_3(@XJIJrPY;J98@~)xxNwB4 zcvUwfrNiQc&QP@kg%=I>hH?*CY+F9d?c!6m(XwN&QnPMc_sx~TXsy}9c$p{;UbQV7 zB*8UYEr#UAq}#3U1qe&3HS%peN(UQ|z9sAGt_+Ikz;qIJe?!%zL|z=PA@M~|VWtL; zzCM_#kk-SQdJBS^#Om=3W|kT`N^6b>vcJU?CZpLpde|ss1d2+8a*MuXl{lkL;1*Io zW|HKnTwO60)D|J90>`dF5%@|P2mzj1bIR@?@WH~4u&Z~+#lse=TrnX}m29rI;t_#k zN&<1ln=uvPf8yc!z^}QfNts#U8&`L6j$K?(!c=(4ev`Z&pX&=4s7gwS5!|KI0->sC zcR5lv_^s(47$DU|Q(o=Oxp3Ng@kPYwjH4Q@EcZMv6~s|$Tqe&^ux&!EP;OpTw_bJP z;SnG8lv*Q6d3}zx^e_57>R8JaOVx1&C$}>%nFOg7f6cM3Xl`YaXT!=nTy74!*z;eP z{KDf};_^O)O5w%Aw0U_%^>VW0g4DS9L;5CT8(Fq^Jd zSXGR@5>8<}RMiS_SaSBrizj$)AD&)+I4pTIYQn|a`&B6L5Xw1q!)Tds+`e(9_B3ul z9bDcqe^MeOE*_NUm%1RK45NiEL?k0<^p^>1{NfN&dd>nKA?o;6AqR4;tB0u`LR?-F zCGIbO-2%iAP77WpuGZU1;{O1&RfrF^Z;YMa0NfW_@8L}%h%2=+Ua&~GavN>$_r=Qv zL1N=h21?_iBt90>?Ee6wMFMzUY+e;djF5VUf8XKO1|))(q9kH!E{YL*HkND8=LA7r zrKTRWH?5fxU2$~z>E{>NDuzN$$sh^t$0E46h@ind+oC*Neb$%rfT;xu0PzcMkJ>1f zAVQq(Q87~aaIbVr5JOWPnDcLlt@ff=f(CpD>_IYftB80(BJ356l+iMIppd!JKF(2A ze*_DY$}eZbZe2gRCrJ#-&I^*Z+9ajKS7szD215_cAibF~BfA@J9<1OjK=Sn2nS;5) zYFr3Y!^QbU*mtHslc%#OIlV7jH+2(eMFAaxp#8~#D{{2AnF#RPtXRr419k1rh$DYb z3|z4fHunZdJR<|RBPd47;LpF>)+{Q7e{JLw5+Mw=5w*@N{P{#y1{eE^{?j&@YyLWShiSErg@`GNIs^;?aS+A_*ob^4+H+oC}+tQu>?7Xst71P7Kfr+yqwKR>q4N*)%w8f4^o31HyKmYsw)ERl=UlV0gab%@lSY{+% zb;e%Et}Mh!J*?q3(VzN<_cY>)k6CdDrdH&?IJ;OikW&vb%*ihUVQ&!JoGa#$Y`;?e z146C@jne@K zRM_4*m}H7tp-bZF0vm1n^3F7VkyMzU5`TG3u6jG|8lcG})YDVJ)F3llBl6EE57V)> zIXq}z5~o)5ea3X~wc0DMDGJ<8Sm6qN(SC~>7n6cmhI3`OhLFo>fBg=m$PPnsGNr@s zT%eg4cdd`?N#Pxq)$8;7vZuRGmJ+m|Cd^AOX%S$G^%4!;OQnS#p3ItGWV=>eTw3#8 zesP`T#@^(!a;nMZ<84j}3Xt2vMekIEx`R=?yt6Y>#gS-U1M1J*u)2uL##sQ}K;%HC zCEm8vL9~f@e(xwre}UnX$bU!1!cR8b)#Pp$>j#CnV@VHtU%0W}dBfw+CyD#nZ}l;`Dqu0c(G8aqLp z;FFP+WCttFfA=;xiSdM4Dc zbjys@X=f(xy<_Uf zIVZ&EFNr;*jz>3-PNg(2*xS2w?v85TckeGC*B|*V9A0*ynnxZe7d)6^>(<(QQ`WjVM8>HIXC-k2WO6duWnqt{9 za%z@oe=#C+1G|1q$uhaMPUK5hUz}QT^2*rG`8L*3a;&+tZKcj(xx(H#Pd)?C#neqR zPD;AQ>@Kh6@j4uQ%Ia<`H?>L0S!r2G7aDC`m~6=16Kd28vrN}bP`ka+x7K0Gzhivi z-$+3EwaJGZ5kYF*(M%|BNiIzglO==xqaIqpe?Zy>(Wf!g%+$>LmdHwh7=YvgDc{RP z`Nuyu9lVU$v26I6x9m&(uiKs`si)$*&`!c1w?*k)}5zEycVQ2XD^V;?FxxSCOZ3=^IhZ z`J-mWk=w)c)7;aiC1xIFw%DC@%$)3PlGp;^c-%|4a)+GU*;Kt@EwWJXJe!h(FbvEy zG-M-EWkj7ky5_2CIcq&5X_-+Gfi>VM zC4yC{X2QOZQkBL?suR-hOnS>D-zAoP(C;JeY(5m#V3v2}PwFi6JaHpkHf0=n(q*=- z2UJqmSK17+$Q3+A-xXyBdl9=xZmtx0I)xm9W3sw~G6lphrTH#Scjlb!{*jICe+)G; zhr;;MW=^on%e`g7A`u3-*etbBZ!Rpd=0zH@`v(kdn)9-lw+UPX4|ax>i`HBsqvoDT z+@0|3%hEK_+c%K@&*M4Db60N2NW%Gb0^T*RHRZvtRHBEA)Sgu5q~k0LGU3qbldR4OF7{p56oj?OY(>$2 zagmekrlft9ylJ!v!8T;EB{ioBv0KTGq0M7P9Eutyfnj~3Nnu&>EIVGSP)|b>VZ{Lx zPFxzMY3CZMujCs`8!f1evxqSU0>`uqDxjhX-q>xwRMF1fB*D*WIV0-;Kq_?=O* zpQK)65mY{?uhM7ewGBs%ca0J!HN7DW>Mt)BekRB*ph)8Ct8+5*%}i5f0Y+k8Xq*=a z{{ZyYO(G<$_?TTO8ZAm^U2 zUgd@-AxiY#O>v9;AvS$aoGG5&R%9(vCWfGH>z<^UDnUkO299bCpl$mA*C*#DWu@vv z+A_4xc*Kj}XL7-=e{7-ApssAsZP;FmwhTW~mwKqo+cT|z@gcX7a4vk8kw%>~mXA}T zrCUvx?SbiT0h1@(Hiz15*EB5oE_Y}D0BG}^jDHi+@ZZ$LZ;E8`&PnfC;sW9U4;NNi zcYV?4-8Irlij(s7rlmu6$gs-maBA5Xcjuf`PJwGlHA$Q*e@^UNa(rI60@j>Icd5ds zpSY7xFUlUi!gSJKFy1X~xskX)S(*X7Uzb+JpwA|cG|M|y7?qG27}^*@wLM9B#1|!I zCfNev-&!K^prfFOxoDxpKKP652X7+iRPU@P?Y5H+xiCAqRwfvb;TIHk< zrbKokPtIKRh%J@2ur}R)EpkKCJvd4n zvG(!>;F+P-9%vbyA>8_c02Z?y(iWMfOV5Xr`zhxGf3|?x7D>E3PfaLe z&|D)PTnUmY#1_k``Euur^$BVbYtJ zXkQ_`k>fFaH3PHR$2}x%6z2#M0EB z&sM;5OE3m3+~nkesH3{4Jdm#ZF~`e~^!&xjf2v{3n?52_*hcHp`H)&BCDrd9QSWiS z`~@V``-}8oWAUweI^s!rSzXNhysL9kz9uGAE?m`njctxU0gWY0GZUh`O^c{unMA ze}6Si{P+G*^VVYFKAmITVqTNUG7=aE5`SZHwI;5D1XS6^rmZg$frE-G1J8#kF z(q?JDN9ufp(QfBlX@}Y&ac>uqGF^1$k&~0iS${el#93~F_eZv&~D>_LtD?2JW@jWhW4qr7y zjCf&TR>K#Nv(i7JypC}Vg?FR|lB^p#S3x;@Wx%rgkzcfG$U!jNH*-Bwa}vr3#+lO~D>fuNf^^zZ7E21M%Sk?Izt&ZCK;y zRvB7_m6s;Z?8hG}8WFZgvd?s6$tH;IJ(U|_CDf3UA}v$J8JUBIBxS^*On*DL@r>-& z1geJ~{{Z2OX9&R!+N2n;FH7AQ526WXRTou;E9@$T7JT98;ldaxba5fd;n2v-S*^TK zSSgN(WOo$G#BAXR@)*$*p{Ox&yN}t2fj!1k zLvK3@+~H#8c16y0>kX+ybiZ3h(y23Y7F;C_ z)BwePVsQxlqKcRaAZSl#cZLy0EE9w-YXZ@sgI9z{U)hOZC%6t2GJm&=;)bhirBRge zTLh|GsTlDZI5v;{pw&#v>>~SLHu3EfaD233HlWYS1qr%L=O0(u zgtCDuTc~qn_KI2#QB67|scECkTx0EB!wO=HofNLO;)BNlrHf5H&H+yhXqGf;33+=? z-)bd|BcUFslHpX#jDPp63a}$-iF>q$kZbjE5yi8LnAs&skrvN5E9|Jn_APX|TWy9z z>~i}PQnL4WV$Crd9m}!j7h=@rU!-zrMso|vp?`4erTjTZr~VvGu&z?Kq;lJ``~;~U z%{`Aj5z`d5mT%$D z*uG+?tTHD!PfLV;Lm1+ATNT#ZOY`vb@pVGQOtB*>B3h{M+XG@*$I_+JhCER_l^(;1 zlDU83_rV7eG?;GWx5d@d6*HjJ1NVrVBA}7S_Plk_@aQdOy{Oc&)S7gv2&0~ zIcRHru*`GzE`PQYcOFVGF2g$u%Y#lB7;!HqZoKpFi@6~|o1A&~3=$|&I{*(6Y5OsH zfE(P&IH%4pdJCXV`;R5?#NNR&Q#^U7#lns0Q$47Ky!-l~VF>==24Qo}Mke4}9!EGm zl<&4B$xvIu%Qb_r#XycYS=zi@U&;Y;>?u(!p8+Rve}6~}t75FMV|T7&^2(cBtq)&_+obo#!1dRmzec@;!q5xpTii`uxY zi`10L9k(uIRJyu-s8C&E+;EytbT?8~q(hDijFNZzFu9Es3gs6yiWm*T&f@MUfXs*@ z%rgC$I8wcH2r4}IVl?Qudj)LnT&+Kc;(u5z$XaO?&;m;e2UQj#mJ;y|z7mKn-GUOK ze|HT=BxD>*@P=y(n9*?T6L_o7q`oMiqqu_4iqYxfhAM>}hJ}!`p6}v_$^{0_WCv<; z5rvVI801=ihr<_8YI*yu&ggko*N|*4G(}hyA7cvh)v1qdvhF;VJ`;?yLl9J&KzkDTz$4N0E#`bB-6u#pWm|(I3%ce9h zu`C6_^@E>G4T71m!v2*Ma;M_lOxIBMr@s<=Q7k=Uq~+E|o`I&5XeU($Z4*f(omUtR3l ziei89M(Foqvo}#S^qtd75qc^-(pVzpUU1ik+}GtA>(f(T7u(4BLtMhuO7_?=+@&sE z*{YMm+Y;=wH#kOyNw1Q5uX1Y~&y`NCwbsYeU6`=D5y660H=3HnQw%rTU4LkkcbXF}h*%w3&tj_@whHks&8LuRv# zr)rp7X$@4Y22iITde2uZXMc7^*l}E{nWeud^yuvPypJ}xaVB$voRi)2j#<$kQCM-htr09f!Pc(LuqiB$*iM9H}^35Ta6{rII@zgha(FZLH1eg>TKJ@Q_+!jYr z*$LXSfa!4Bun{{LKary(oXqsQg=t&rt>PkF$0%VAyV>5`rYRdPW`BO6JwHR6a#et+ z%JzGTrgGPIcL8~V<)Zgx7^NCKM=PlniLupBA5Z;bRe9H@rU5P@E3Hhl9bnYta^I+6 z+v;2?{um=4$eO~aLtByP;dE{M;zZac@#!Z%mPrzt*}l(b)NMvh_6MI8zzhEX3`O!M z2^tvV+9Gc#gqHNX8-EV6sk!zOinn_Q#2A0KIz6KhO3wB>cpb^R6!Y^-a*Uja7nO8w zb!OIELiR_eCz&)Neo2tC)xMAU z*%KeDs!crC{;1ktx;gEUjVbwd7&GWciRDa%9+Gjsu74*jwgP>}f468%;g3SPy-mSc z`N>%^@d%CIhAYzh1mB)n@ismU^ee+Or$p*4l|YC6;Vr+CWfEadZePr___1-sUW~X( zt%K$%jMC-g+i|55CL6SiB-8i8*Vs-zV||Vztok-rHgkwxCnR6&8}b$iTT&Y{?}?W# z&gTb~vw!ATWOgf8uS+)T6q%nXaTYr3FyB#*=VjMN-!-{5};G@lO@o@?b(RT;+ zmqlXTCewe;0v1CTIMvdHEe3vUb?S(rtv>EldTdJVO(c##r$(VdrJ?HY47T zvKW{I<=pJ&ZxHx%k2%W2^*g+8%C<3_@1l`{4(?z`OQzvY80NPf&8hrAzaG2c`VPO9N{lcG6(te8V0p{fKkoZR1VV;NBOMeis{hvk3c!w0zqC?>y^MRf? z5Bx)xMVF%;GR%@-zSX=?K8U#fAPaIund|BKR16PP3>!CH4G0jLVx=tAVjv1j|?6sI^2GGxveHYLwdsE@mfO~ z@f|3e87b_KLg9k2Y3@i%&b%yUg=}1@(8M{FXS6&d;PHD=N;*CKNlABrh(zzY-XDqt z(2MpTsAkBG-}i_yXGUaZ9g$DgUB#E(7g0mmIqS@~rNejci?CG;Gr=zL3YQ5*!heOm z6EvJ9H;krf@>H9qr|Jfx04=FDD4r!gN%zArFkV*xQdw&{|`Yi0$r5@vDvnFloxd&f% zUD-yZal`kkN0&x=^ww&B9QS4l*r`UxmcyryepP)3>A#B$-blr z+3xzUwQbM334duAL(H9-r#_4g%ql? zWMV(EJyBEGf=#8|mVEkzNNv&zq-FwJkb5!pY2i{HsHOJ{)Dzp8zb-x;`bBu61L|m4 zP6J?9HtBy2v1%^n0;B`e{6hgUGUmPXo~BLWO_V$mMD6nP4a^;$Fcd7|Z5Htl9%YQD zB26}LPZE)C*=?jE^?!UmC0%mM-2lBuN?2`DB&agU4db~REo`m0`Tgsc^nhs4N$eg@ zt3Nn|@E+{B{3uIXy%hmzo$eXl${{ZO)aV>;)FEr;86fjnpSn3(|e7sQ#21It~Bbffo zLKGdY`aDr~tA8!NCWca^8IVjh!*cvyaHqzyI}T>X#)hnVF5+xE+AWclm;uX(2?&He z$i482Q0vDl6O_8<5vfz=MY)PR`)=LDTaywY_xC_NeMcr`a#s!PlN42@nR-&1?3xiR z=KGty=zA)jQ6X4U@^K-w_Ghx%#uq!LF(+rVzfmgMx_{Xxzdb$BYbhQ^JlvI^iLqr$ zn=Z~y_KAY=6}H_JuYk)gW%^mH$)5GE7i?Abyhj(s6a(po?KwH6Ec(wLmh!h<7xQZ4-CRw+V@m&Wn zK`PQY&3~}s_5%GA{vnSa-)v2QMShcxUYe*K0;!4@%{y55EKJL^^UaN;c-^FQvBJ7o=jOuE)H$A$W?U_4mYV_T zux*FZOzJS~vkyEYywjwJv*Ac$hp8l`+hUumvrP(+^<4t)vE&09gVM7J;oYTS=Ot#P zMr(Z0Ty10|Dv74QINNpc_ zl}Ri=TY1-2jPvl@er>IS?g~UHl!vny;1rN zO_`ZuwA(H(i9MJXjjcjf(Z9BPyPA2rjWxn;mw^HHho?V!dB-1@g_Fq8t+Mw;!+-SZ zelwMw5q6t1dR$C&x|w(9&oz#0<*!Mh6YOq`6F8Fi_D>K~rkazk%9CKzr@}KRmgFm! zyGZ9gUG$PVPL{=+Dxhu3l`&UGCnz}DgC?^boCyi30#P?l*xh@CL40+c$BwFnl8HXj(@H!w_cO6Eq)b0kZnDcw7VzyrABr<7oxa^ghM}|vXfksWR9TME zRThi1IxV3!Bixs&>3_K)@jnxn$>g*V^jd&nS!W$&W!|c}FyCmHwOc1q6E!6BUwjk0 zM@pQnG?TQQw^S+M@;5N}vVU9GMW=F^XiapzbJWiwgx!fKVoz%ml5=9LG7e2|#_LH$ z^UI!Gm3`3>m7L=x#Zdrk%~Feo*SQORJkB1?{?W zpDDa$x|1f`Op~NJzR_l*nYouhWLb#)(J(VdkfWn^CAU?@PYk=W5`R?z?MTy4?t3%& zDrt}x3vI^5q^L?*_N4U1CcRIbmz9-zRpy!G6%v?4f zt2N0r%~w@Vl0&fj^nZ?4B7Gz<(adc6dC7*|ntifaCJaW=X}rm8Lr!iNzmi8UlaKkM z@jp?>Q(lNN`*6igx)ipgXJo`!mtb?U&TG6s?+sVZI{Y*`bK-PWKhX~n;fX0)a7)Uz ztcZz_%m(aS$a4G1BSZ2IL)6%tJM?L$I2-x*otT)ndIK)Xh=1kD*40*J_B$% zp5^XH&lyK;WaMqLdM?XXg7(|ozRr7x+UgYY`FX>uaVoTKlR@JRo5)eBPe@}5X|~IN ztuxN|j2hE=oJ9bgGj8sqyC% z1`Mz96O$sEe-L^~Rav;%#S>G4=eF93m**W6;J4JrIdK#^-=rm0agu3gr(6;*6N*2| zG^YVXzb9TLZhL9dlg31|lU!UxB;u54jxwp5b8Ly2#j`Jecvk(WWMNnaz|s8 za(^j@T}NYwk?rJc&4}$YwNhDk8ld!Agm5emj<0?|Oh$OtY1BwoARefrne3TsX?uje}qd~ARjdWh^0aPZhcu38sTfMhU5ie^r z?tgSO&)O7&?i)AMcIF3{Dy*2&`y9O6a_hY?ac-PbLfs-F`#@W=`!ORXx=lE_EgnQG zlv5UEM5W!A0c)~_J_QVT`PLj^G(h?O@DreFPMu>s%C0=XsVw)_@d@m1otJ`a$&cK z`(ZOK;G^VyE_&ZqnXghNTJu;eOu#xX#IV1Jf`?cpT6ksU#8jOn0I)O9MFqPk zCSe+pG7kXG2)6OI4U2+7ZtmQg$$i=x^MZ&0kmbKfygoQn8Iuzdq9<_(gMP3|+kbeo zPDva1ZlWWxX!I+`#^WK#MCy7pze`vO$IZON`FGsQk5)YGYy<6PGJY zj^b#K!>5#R_|K;5GhrUAjGo-KbyULj#>R27lY4L_#wOW;1C8sii!_aVydI+2h4K?! zXXcaAPh&6oJ?-xmV8;c~Ofyn;O@E?ysn$_*ZGT{fHhvS5*)dhTl*H|>qv}7yc zXpqJB@8c9y={}znwO~SJOPRpwSj5xkrku~VHZ|W-gfCc*FP4rnzPf}1Ilfm(Ek7)VfmJK z!!=HDLhdV0!c5cU96!t>P7aeAD)}Fu-$&m{Oi77pbX*x*oH@yQY=6UILg^_u!;IYB zspOHji;EUq9;ws7_iCNzf$9kLDYafCwIeoFuZgA0g#>?3Z5NRhu$8c|#p=%}7H}3e zU3NzGwfTE`ZtC`9XCK^cPOGJ2Z|vS4DwEP9Qf8XMzU;c?>p)XA%g;?=JdCQHP2y*2 z`7tYku{O>9CH8LJdVh-}My*nam5HW_kQXJ5d@Gmb^NE{|nK634Oa0`3ZkILoHu^sH zQ;a5DIXM?-b8c~8D(48Q`;lP@$5`<*Q>fQ<@1ovvPGfmM#Z65hmk3M>U2H{04;r*L zk+f<14a681(bwv^wpyB)k$gyZ1#_NDq;+`Nb(L9y{DZbniGQlxcXoLf341;$_xN)9 zi)B32#)0p>{{X%m8SUq~DTRgyJffI5aDrEAkVQ+_skl~#vG~oHDVq(PF7%AgBpbLx zeo9b{Y)khVyyA`7bix?{0xuC;4MOZ>ULJg-W;6^S`{QOVlS(p$ZS3`jsX;)yq1u3~ zdf^^-Ln1U0?SJQ-T0j!QUU?JZjR*@c)ya#ab|v3PxnYFk*K*;F*i(H%a>)@kUMSmz z42?>UTFbKLP!ZVs3yn*seoN`{N2w#Ih}m|ymH z)nh@#B65e6n|w;LE?@^Y+q23MK>>LZIUgy!&QJ1^EYapKeYv8Bm0b|rN_P-9gM_m<971)q3S{=d#{Ey zAf{qAYGqrwSB4lJk2WGapvEegBDYn8WeUVSTA(DkCl#?3K!14IU7C91B~QZ^5-UI#CW`S?<@+!( z1NuxX)!M8Bpe(8crZaCOzAMub<2i|oOpzh5nkj5GdSiH5{{W(is1Brj#KoE@WqL!+AojLoVu*Q{J6H>f7#j6ZuA2wHLkpf`tvv_L^D9>TK z2Efc7`nJ>QiC|era#EQdoiISEu{Tc}bny%lxZg?1$q~3SQG$mV79?3`;kqrHd_l>d zNE4q~ZEBY+y%O}{{5h<9U*WadnR)MARev-v;qWVwW7qCZAMFUuJY?C;3tOUzO&)M= z*4Tp$Rp~7+b2OI|+aclnF^`iyCXOog7E1G*&Nb#jgg=nSJ|e?qcEl?q{{S>VqBKg% zx4hLom>PpBNU+%1n#D?iQ4bZm^+A##X)@=Z4bZptGB-X5sAn*`lL$p*~s;<_pn73l=u>lQ7TDUAyz zZsp1rEky|ehC$(rn9%7^qE`lAis%Z7gbd*z+m{v5p$lcu9rB)vUM`zpC5We-9Yt5C z+3>`M8!ULlyZBNmg)3;w%V=rDF>s?}+zD|8XjfhB6dj8e%bT+-`l7WYR(}-Dt(JTR z?}G`4BIflnUKoH%9+h!FY6XQliza*4BD#dBur4LKaC={TM{wz&2=d=;zlsD0Z5eWF zAjyBy!o!bq7TOu{=LZZ&_AoCKp|4j|H+mG4VPh~$)%boG34NN3EXfVY-3k=&DVIA& zmi$)q!{e~HotYQ7GFYn`On(;@tJCbos~P4PKAqm^r!K7{a(Q>!7oQP8ph+4$^TZn6 z_Moa7h$7$|xM5bQbJ$hF;pV+o{g^hmJCKZ$uQB5IVRJSpL1=e>iY~-3=P^{`xMD=; zU_qBGiD0e@p0v5daE}O;nRM|$17TZ|u$Jx^H+_V7me3y;_@Du)n}4oc{6+0VB|*(L zSLB^nAi><06s?V!dod-#6yte^2M2w_$dQtCmS*DhB3F+;>|$W&CU&-S3_8VWRZjl}_Rr_CbC@K@Qu@ZYf5k!bk;h35x_iU1TP{LSX@aFbw5R%6udT3JF zm&cU@7QlN`2<@ZOnxP_phAOr+#O+Qs7A%>yZMnZ@4O>7<4&^^v4sY6LxR)IAzU^Wa zG6_XW)Vc#w7Ju|xwkqK_kLl{1R=S1xD8Xt1soG_S-l=*~RGdLSSvt$NTAVqw3;vcg ztm%clz9i?SMHOqFje{>~yz9q2cJ&sB)~WA~gm~E7huf&-KS%9|NNh$;%vD%yT*S2i zP-5XiDHaR4J?q~XGF3Xp@`+Q-)uE=!EY<$s~h^YG{Fr_MN;S+lXH4lOT< z&vg1ta~NW&iAP1(gAxH9F4EyShBZc!JlBab=fsfoRfRR9^xZm3r3Ykl8%Ws8)!A`2 z=QmCX2EM7lT$B|%mxq$VUJWta=X^zmXWTLaZPMK4LGb0yHF20$WN)dw{SX{m7sgZMx^W-u-dWedBVQIpB= z!eUo%$35XeGZ1Rb**8+WNVvVOqJUBsiX`AnJb&AUA$Xb)@`53@ez2Ux_-;*^k~f~7 zd`OSPEseS-yC}a;Kt5d;M;J#fQRV$1Gdej8`w$|HnBC00F(P&bpq8PzyQ<5Y4c@g2 z?uj3?sd|_1r7~P6eHidez;O(%MFfaPgjAX@`C)Nr$~T71EO6EzdfGd~)9X;bbZ@cK zR)5jaeuh%ARV|p~iFf2>W~N)5k$tIyA6UY1<{|U%`=NOR=53of;R}7AWMegqKAREK z;)LErP*XY$5QQIB_Pir6ZS$Q)gSY${c zMd3Xn_XHL$XQTlIZ){CU3dEpMa_&)kg0%wBgikLNO~BE2d-$Tv;{O0gmHsBRW*&wS zDd}w7l^KS*xW?guZU$eep);^HY0b&4g4;uHghefka+DeBFm2ciI=7*J3{X~aEP#p@ zLjy?^?94642bx7x6)5{i9pteJ^M)s3+k=t4A|!7IjR);;$7AvR%#ZGK#D9ryYr=`c z#l`cFO^ew z2=(X@gEkK2=QrAqA;^jYu

- - -
-
- - - -SPAWN.uncontrolled - -
-
- - -
diff --git a/docs/Documentation/Task_A2G_Dispatcher.html b/docs/Documentation/Task_A2G_Dispatcher.html index dfdf88251..b653d16ca 100644 --- a/docs/Documentation/Task_A2G_Dispatcher.html +++ b/docs/Documentation/Task_A2G_Dispatcher.html @@ -138,12 +138,6 @@ Find a summary below describing for which situation a task type is created:

TASK_A2G_DISPATCHER.ClassName - - - - TASK_A2G_DISPATCHER.CommandCenter - - @@ -183,7 +177,7 @@ Find a summary below describing for which situation a task type is created:

- TASK_A2G_DISPATCHER:New(SetGroup, Detection, Mission, CommandCenter) + TASK_A2G_DISPATCHER:New(The, SetGroup, Detection, Mission)

TASKA2GDISPATCHER constructor.

@@ -236,20 +230,6 @@ Find a summary below describing for which situation a task type is created:

- - -
-
- - Wrapper.Group#GROUP - -TASK_A2G_DISPATCHER.CommandCenter - -
-
- - -
@@ -416,7 +396,7 @@ If there are no targets to be set.

-TASK_A2G_DISPATCHER:New(SetGroup, Detection, Mission, CommandCenter) +TASK_A2G_DISPATCHER:New(The, SetGroup, Detection, Mission)
@@ -427,23 +407,26 @@ If there are no targets to be set.

Return value

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